Compare commits

...

3 Commits

Author SHA1 Message Date
snipe
dc0c95acb0 Change button classes based on inheritance 2025-11-12 19:12:38 +00:00
snipe
5d0807110e Try to get the overrides visible 2025-11-12 13:49:45 +00:00
snipe
33e72f6183 Finesse effective permissions 2025-11-11 18:26:02 +00:00
17 changed files with 203 additions and 43 deletions

View File

@@ -201,6 +201,8 @@ class IconHelper
return 'fa-solid fa-lightbulb';
case 'highlight':
return 'fa-solid fa-highlighter';
case 'inherit':
return 'fa-solid fa-layer-group';
}
}
}

View File

@@ -308,6 +308,13 @@ class UsersController extends Controller
$permissions_array['superuser'] = $orig_superuser;
}
// Unset any of the inherited user permissions (0), since that behavior is the default anyway
foreach ($permissions_array as $permission => $value) {
if ($value == '0') {
unset($permissions_array[$permission]);
}
}
$user->permissions = json_encode($permissions_array);
// Only save groups if the user is a superuser

View File

@@ -68,7 +68,8 @@ class UsersTransformer
] : null,
'notes'=> Helper::parseEscapedMarkedownInline($user->notes),
'role' => $role,
'permissions' => $user->decodePermissions(),
'user_permissions' => $user->decodePermissions(),
'effective_permissions' => $user->getEffectivePermissions('true'),
'activated' => ($user->activated == '1') ? true : false,
'autoassign_licenses' => ($user->autoassign_licenses == '1') ? true : false,
'ldap_import' => ($user->ldap_import == '1') ? true : false,

View File

@@ -93,6 +93,9 @@ class Group extends SnipeModel
if (!is_integer($permission)) {
$permissions[$permission] = (int) $value;
if ($permission == 'superuser') {
break;
}
} else {
\Log::info('Weird data here - skipping it');
unset($permissions[$permission]);

View File

@@ -253,10 +253,13 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
*
* @return bool
*/
protected function checkPermissionSection($section)
protected function checkPermissionSection($section, $return_explicit = false)
{
$user_groups = $this->groups;
if (($this->permissions == '') && (count($user_groups) == 0)) {
// The user has no permissions and is not in any groups
if ((($this->permissions == '') || ($this->permissions == 'null')) && (count($user_groups) == 0)) {
return false;
}
@@ -271,11 +274,14 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
}
$is_user_section_permissions_set = ($user_permissions != '') && array_key_exists($section, $user_permissions);
$is_user_section_permissions_set = array_key_exists($section, $user_permissions);
// If the user is explicitly granted, return true
if ($is_user_section_permissions_set && ($user_permissions[$section] == '1')) {
return true;
}
// If the user is explicitly denied, return false
if ($is_user_section_permissions_set && ($user_permissions[$section] == '-1')) {
return false;
@@ -285,6 +291,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
foreach ($user_groups as $user_group) {
$group_permissions = (array) json_decode($user_group->permissions, true);
if (((array_key_exists($section, $group_permissions)) && ($group_permissions[$section] == '1'))) {
\Log::debug('user '.$this->id.' is granted '.$section.' permission via group membership');
return true;
}
}
@@ -292,6 +299,64 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return false;
}
// This gets the permissions including group associations
public function getEffectivePermissions($return_explicit = false) : array{
// The user has no permissions and is not in any groups
if (($this->permissions == '') || ($this->permissions == 'null')) {
return [];
}
$user_permissions = $this->permissions;
if (is_object($this->permissions)) {
$user_permissions = json_decode(json_encode($this->permissions), true);
}
if (is_string($this->permissions)) {
$user_permissions = json_decode($this->permissions, true);
}
$effective_permissions_array = [];
foreach (config('permissions') as $section => $section_permissions) {
for ($x = 0; $x < count($section_permissions); $x++) {
$permission_from_config = $section_permissions[$x]['permission'];
\Log::debug(print_r($user_permissions, true));
if ($user_permissions && array_key_exists($permission_from_config, $user_permissions)) {
// If the user has an explicit permission set, use that
if ($return_explicit) {
\Log::debug('using explicit');
if ($user_permissions[$permission_from_config] == '1') {
$effective_permissions_array[$permission_from_config] = 'grant';
} elseif ($user_permissions[$permission_from_config] == '-1') {
$effective_permissions_array[$permission_from_config] = 'deny';
} else {
$effective_permissions_array[$permission_from_config] = $this->hasAccess($permission_from_config) ? 'inherit-grant' : 'inherit-deny';
// $effective_permissions_array[$permission_from_config] = 'inherit';
}
} else {
$effective_permissions_array[$permission_from_config] = $this->hasAccess($permission_from_config) ? 'grant' : 'deny';
}
} else {
\Log::debug('fallthrough');
//$effective_permissions_array[$permission_from_config] = 'not in user perms';
$effective_permissions_array[$permission_from_config] = $this->hasAccess($permission_from_config) ? 'inherit-grant' : 'inherit-deny';
}
// $effective_permissions_array[$permission_from_config] = $this->hasAccess($permission_from_config) ? 1 : 0;
}
}
return $effective_permissions_array;
}
/**
* Check user permissions
*
@@ -302,13 +367,17 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
* @since [v1.0]
* @return bool
*/
public function hasAccess($section)
public function hasAccess($section, $return_explicit = false)
{
if ($this->isSuperUser()) {
return true;
}
return $this->checkPermissionSection($section);
if (($section!='superuser') && ($this->isAdmin())) {
return true;
}
return $this->checkPermissionSection($section, $return_explicit);
}
/**

View File

@@ -342,7 +342,8 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('groups.edit', fn (Trail $trail, Group $group) =>
$trail->parent('groups.index', route('groups.index'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $group->name]), route('groups.edit', $group))
->push($group->name, route('groups.show', $group))
->push(trans('general.breadcrumb_button_actions.edit'), route('groups.edit', $group))
);
@@ -590,7 +591,6 @@ class BreadcrumbsServiceProvider extends ServiceProvider
);
Breadcrumbs::for('users.show', fn (Trail $trail, User $user) =>
$trail->parent('users.index', route('users.index'))
->push($user->display_name ?? 'Missing Username!', route('users.show', $user))
@@ -598,6 +598,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('users.edit', fn (Trail $trail, User $user) =>
$trail->parent('users.index', route('users.index'))
->push($user->display_name, route('users.show', $user))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $user->name]), route('users.edit', $user))
);

View File

@@ -1592,6 +1592,11 @@ Radio toggle styles for permission settings and check/uncheck all
.js-copy-link {
color: grey;
}
.label {
font-size: 11px;
line-height: 22px;
margin-left: 5px;
}
/*# sourceMappingURL=app.css.map*/

File diff suppressed because one or more lines are too long

View File

@@ -1216,6 +1216,11 @@ Radio toggle styles for permission settings and check/uncheck all
.js-copy-link {
color: grey;
}
.label {
font-size: 11px;
line-height: 22px;
margin-left: 5px;
}
/*# sourceMappingURL=overrides.css.map*/

File diff suppressed because one or more lines are too long

View File

@@ -22928,6 +22928,11 @@ Radio toggle styles for permission settings and check/uncheck all
.js-copy-link {
color: grey;
}
.label {
font-size: 11px;
line-height: 22px;
margin-left: 5px;
}
/*# sourceMappingURL=app.css.map*/
@@ -24631,6 +24636,11 @@ Radio toggle styles for permission settings and check/uncheck all
.js-copy-link {
color: grey;
}
.label {
font-size: 11px;
line-height: 22px;
margin-left: 5px;
}
/*# sourceMappingURL=overrides.css.map*/

View File

@@ -2,8 +2,8 @@
"/js/dist/all.js": "/js/dist/all.js?id=525664c0ec56444ba1c48c125346918a",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=68b775c727b842ea7206e45ef7dc6f7a",
"/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=be05d91a777b604b23d1133117c55401",
"/css/build/overrides.css": "/css/build/overrides.css?id=31f0c9a27245a3b3a37a7d08ba914311",
"/css/build/app.css": "/css/build/app.css?id=a1fc9deca6a89a62a0f3cadb2ce5ca6c",
"/css/build/overrides.css": "/css/build/overrides.css?id=6a5c2fa1e03294a1d874dad4ae41ccd4",
"/css/build/app.css": "/css/build/app.css?id=a67c78b8239b51e9461002afc46df25f",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=ee0ed88465dd878588ed044eefb67723",
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=3d8a3d2035ea28aaad4a703c2646f515",
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=bc6704edc9f0e6a211a8f2e66737f611",
@@ -19,7 +19,7 @@
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=b2cd9f59d7e8587939ce27b2d3363d82",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=a29f618515fa0199a4b4b68fa1d680b3",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=cbd06cc1d58197ccc81d4376bbaf0d28",
"/css/dist/all.css": "/css/dist/all.css?id=fc0d990b94339685469761b7ffd7876d",
"/css/dist/all.css": "/css/dist/all.css?id=beeacb3c7087305bbba564dfcd91d23f",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/js/select2/i18n/af.js": "/js/select2/i18n/af.js?id=4f6fcd73488ce79fae1b7a90aceaecde",

View File

@@ -1365,3 +1365,9 @@ Radio toggle styles for permission settings and check/uncheck all
.js-copy-link {
color: grey;
}
.label {
font-size: 11px;
line-height: 22px;
margin-left: 5px;
}

View File

@@ -144,7 +144,7 @@ return [
'generate' => 'Generate',
'generate_labels' => 'Generate Labels',
'github_markdown' => 'This field accepts <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>.',
'groups' => 'Groups',
'groups' => 'Permission Groups',
'gravatar_email' => 'Gravatar Email Address',
'gravatar_url' => '<a href="http://gravatar.com"><small>Change your avatar at Gravatar.com</small></a>.',
'history' => 'History',
@@ -661,6 +661,7 @@ return [
],
'breadcrumb_button_actions' => [
'edit' => 'Edit',
'edit_item' => 'Edit :name',
'checkout_item' => 'Checkout :name',
'checkin_item' => 'Checkin :name',

View File

@@ -34,6 +34,10 @@ return array(
'note' => 'Determines whether the user has access to the Reports section of the application.',
],
'reportsview' => [
'name' => 'Reports Access',
],
'assets' =>
[
'name' => 'Assets',
@@ -384,7 +388,7 @@ return array(
'note' => 'Allows users to create, view, and revoke their own API tokens. User tokens will have the same permissions as the user who created them.',
],
'selfedit-location' => [
'name' => 'Edit Location',
'name' => 'Edit Own Location',
'note' => 'Allows users to edit the location associated with their own user account.',
],
'selfcheckout-assets' => [

View File

@@ -44,13 +44,18 @@
</div>
<div class="col-md-3">
<h3>{{ trans('general.permissions') }}</h3>
@if (is_array($group->decodePermissions()))
<ul class="list-unstyled">
@foreach ($group->decodePermissions() as $permission_name => $permission)
<li>{!! ($permission == '1') ? '<i class="fas fa-check text-success" aria-hidden="true"></i><span class="sr-only">'.trans('general.yes').': </span>' : '<i class="fas fa-times text-danger" aria-hidden="true"></i><span class="sr-only">'.trans('general.no').': </span>' !!} {{ e(str_replace('.', ': ', ucwords($permission_name))) }} </li>
<span class="label label-{{ ($permission == '1') ? 'success' : 'danger' }}" style="margin-left: 5px;">
<x-icon type="{{ ($permission == '1') ? 'checkmark' : 'x' }}" class="text-white" />
{{ trans('permissions.'.str_slug($permission_name).'.name') }}
</span>
@endforeach
</ul>
@else
<p>{{ trans('admin/groups/titles.no_permissions') }}</p>
@endif

View File

@@ -418,29 +418,6 @@
@endif
<!-- groups -->
<div class="row">
<div class="col-md-3">
{{ trans('general.groups') }}
</div>
<div class="col-md-9">
@if ($user->groups->count() > 0)
@foreach ($user->groups as $group)
@can('superadmin')
<a href="{{ route('groups.show', $group->id) }}" class="label label-default">{{ $group->name }}</a>
@else
{{ $group->name }}
@endcan
@endforeach
@else
--
@endif
@if ($user->hasIndividualPermissions())
<span class="text-warning"><x-icon type="warning" /> {{ trans('admin/users/general.individual_override') }}</span>
@endif
</div>
</div>
<!-- start date -->
@if ($user->start_date)
@@ -787,6 +764,70 @@
@endif
<!-- groups -->
<div class="row">
<div class="col-md-3">
{{ trans('general.groups') }}
</div>
<div class="col-md-9">
@if ($user->groups->count() > 0)
@foreach ($user->groups as $group)
@can('superadmin')
<a href="{{ route('groups.show', $group->id) }}" class="label label-default">{{ $group->name }}</a>
@else
{{ $group->name }}
@endcan
@endforeach
@else
--
@endif
@if ($user->hasIndividualPermissions())
<span class="text-warning"><x-icon type="warning" /> {{ trans('admin/users/general.individual_override') }}</span>
@endif
</div>
</div>
<!-- permissions -->
<div class="row">
<div class="col-md-3">
{{ trans('general.permissions') }}
</div>
<div class="col-md-9">
@if (($user->groups->count() > 0) || ($user->hasIndividualPermissions()))
@foreach ($user->getEffectivePermissions(true) as $permission_name => $permissions_value)
@if($permissions_value=='grant')
<span class="label label-default label-success">
<x-icon type="checkmark" class="text-white" />
{{ trans('permissions.'.str_slug($permission_name).'.name') }}
</span>
@elseif($permissions_value=='inherit-grant')
<span class="label label-default label-warning">
<x-icon type="inherit" class="text-white" />
{{ trans('permissions.'.str_slug($permission_name).'.name') }}
</span>
@elseif($permissions_value=='inherit-deny')
<span class="label label-default label-warning">
<x-icon type="inherit" class="text-white" />
{{ trans('permissions.'.str_slug($permission_name).'.name') }}
</span>
@elseif($permissions_value=='deny')
<span class="label label-default label-danger">
<x-icon type="x" class="text-white" />
{{ trans('permissions.'.str_slug($permission_name).'.name') }}
</span>
@endif
@endforeach
@else
--
@endif
</div>
</div>
@if ($user->notes)
<!-- empty -->
<div class="row">