Feat: Show Immutable Id field if user has a SAML App
- added a ImmuatableId field in manage roles - Generate ImmutableId using user Id if not available
This commit is contained in:
parent
4849736c60
commit
a37b6a91be
@ -19,6 +19,7 @@ public function __construct(
|
|||||||
#[DataCollectionOf(class: RoleData::class)]
|
#[DataCollectionOf(class: RoleData::class)]
|
||||||
public ?DataCollection $roles = null,
|
public ?DataCollection $roles = null,
|
||||||
public ?array $restrictedPermissionIds = null,
|
public ?array $restrictedPermissionIds = null,
|
||||||
|
public ?string $immutableId = null,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public static function fromModel(User $user)
|
public static function fromModel(User $user)
|
||||||
@ -29,7 +30,8 @@ public static function fromModel(User $user)
|
|||||||
email: $user->email,
|
email: $user->email,
|
||||||
initials: $user->initials(),
|
initials: $user->initials(),
|
||||||
roles: RoleData::collect($user->roles, DataCollection::class),
|
roles: RoleData::collect($user->roles, DataCollection::class),
|
||||||
restrictedPermissionIds: $user->restrictedPermissions->pluck('id')->toArray()
|
restrictedPermissionIds: $user->restrictedPermissions->pluck('id')->toArray(),
|
||||||
|
immutableId: $user->immutable_id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,4 +24,10 @@ class AssignRolesForm extends Form
|
|||||||
*/
|
*/
|
||||||
#[Validate('nullable|array')]
|
#[Validate('nullable|array')]
|
||||||
public array $permissions = [];
|
public array $permissions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the SSO Immutable ID for the user.
|
||||||
|
*/
|
||||||
|
#[Validate('nullable|string|max:255')]
|
||||||
|
public string $immutableId = '';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
*
|
*
|
||||||
* @method static Builder<Role> visible()
|
* @method static Builder<Role> visible()
|
||||||
*/
|
*/
|
||||||
#[Fillable(['priority'])]
|
#[Fillable(['name', 'guard_name', 'priority'])]
|
||||||
class Role extends SpatieRole
|
class Role extends SpatieRole
|
||||||
{
|
{
|
||||||
use LogsActivity;
|
use LogsActivity;
|
||||||
|
|||||||
@ -54,17 +54,20 @@ public function getRestrictedPermissionIds(int $userId): array
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign roles to a user.
|
* Assign roles to a user and update their SSO settings.
|
||||||
*/
|
*/
|
||||||
public function assignRoles(int $userId, array $roleIds): void
|
public function assignRoles(int $userId, array $roleIds, ?string $immutableId = null): void
|
||||||
{
|
{
|
||||||
if ($userId <= 0) {
|
if ($userId <= 0) {
|
||||||
throw new InvalidArgumentException('Invalid id');
|
throw new InvalidArgumentException('Invalid id');
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::transaction(function () use ($userId, $roleIds): void {
|
DB::transaction(function () use ($userId, $roleIds, $immutableId): void {
|
||||||
$user = User::query()->findOrFail($userId);
|
$user = User::query()->findOrFail($userId);
|
||||||
|
|
||||||
|
$user->immutable_id = $immutableId ?: null;
|
||||||
|
$user->save();
|
||||||
|
|
||||||
$userPriority = auth()->user()?->roles()->min('priority') ?? 999999;
|
$userPriority = auth()->user()?->roles()->min('priority') ?? 999999;
|
||||||
|
|
||||||
// Get user's existing roles that have priority <= $userPriority (which are forbidden to the logged in user)
|
// Get user's existing roles that have priority <= $userPriority (which are forbidden to the logged in user)
|
||||||
|
|||||||
@ -53,6 +53,23 @@ public function getUsers(): PaginatedDataCollection
|
|||||||
return $this->userService->getAll();
|
return $this->userService->getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Computed]
|
||||||
|
public function showSamlSettings(): bool
|
||||||
|
{
|
||||||
|
if (empty($this->form->roleIds)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return \App\Models\ConnectedApp::query()
|
||||||
|
->whereHas('roles', function ($query) {
|
||||||
|
$query->whereIn('roles.id', $this->form->roleIds);
|
||||||
|
})
|
||||||
|
->whereHas('protocol', function ($query) {
|
||||||
|
$query->where('name', 'saml');
|
||||||
|
})
|
||||||
|
->exists();
|
||||||
|
}
|
||||||
|
|
||||||
public function delete(int $userId): void
|
public function delete(int $userId): void
|
||||||
{
|
{
|
||||||
$this->attempt(
|
$this->attempt(
|
||||||
@ -80,6 +97,7 @@ public function openAssignRolesModal(int $userId): void
|
|||||||
$this->form->reset();
|
$this->form->reset();
|
||||||
$this->searchRoles();
|
$this->searchRoles();
|
||||||
$this->form->roleIds = $user->roles()->pluck('id')->toArray();
|
$this->form->roleIds = $user->roles()->pluck('id')->toArray();
|
||||||
|
$this->form->immutableId = $user->immutable_id ?? '';
|
||||||
$this->hydratePermissionInForm();
|
$this->hydratePermissionInForm();
|
||||||
$this->showAssignRolesModal = true;
|
$this->showAssignRolesModal = true;
|
||||||
},
|
},
|
||||||
@ -185,7 +203,7 @@ public function saveRoles(): void
|
|||||||
action: function () {
|
action: function () {
|
||||||
$this->form->validate();
|
$this->form->validate();
|
||||||
$deactivatedPermissions = $this->getDeactivatedPermissions();
|
$deactivatedPermissions = $this->getDeactivatedPermissions();
|
||||||
$this->userService->assignRoles($this->currentUserId, $this->form->roleIds);
|
$this->userService->assignRoles($this->currentUserId, $this->form->roleIds, $this->form->immutableId);
|
||||||
$this->userService->syncRestrictedPermissions($this->currentUserId, $deactivatedPermissions);
|
$this->userService->syncRestrictedPermissions($this->currentUserId, $deactivatedPermissions);
|
||||||
$this->showAssignRolesModal = false;
|
$this->showAssignRolesModal = false;
|
||||||
$this->roleWithPermission = null;
|
$this->roleWithPermission = null;
|
||||||
@ -314,6 +332,17 @@ class="btn-sm btn-circle btn-error btn-soft"
|
|||||||
label="Roles"
|
label="Roles"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@if($this->showSamlSettings)
|
||||||
|
<div class="mt-5 border-t border-base-300 pt-4">
|
||||||
|
<h3 class="font-semibold text-sm text-sky-400 mb-3">SAML SSO Settings</h3>
|
||||||
|
<x-mary-input
|
||||||
|
wire:model="form.immutableId"
|
||||||
|
:label="__('SAML Immutable ID')"
|
||||||
|
placeholder="e.g. user-123-immutable (leave blank to auto-generate)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
@if ($roleWithPermission)
|
@if ($roleWithPermission)
|
||||||
<fieldset class="mt-5 space-y-4 w-full">
|
<fieldset class="mt-5 space-y-4 w-full">
|
||||||
<legend class="flex w-full items-center justify-between gap-4">
|
<legend class="flex w-full items-center justify-between gap-4">
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Models\{ConnectedApp, ConnectionProtocol, ConnectionProvider, ConnectionStatus, User};
|
use App\Models\{ConnectedApp, ConnectionProtocol, ConnectionProvider, ConnectionStatus, User};
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
|
||||||
beforeEach(function (): void {
|
beforeEach(function (): void {
|
||||||
// Generate testing keys if not already present
|
// Generate testing keys if not already present
|
||||||
@ -111,3 +112,48 @@
|
|||||||
// Verify session was cleared
|
// Verify session was cleared
|
||||||
expect(session('saml_pending_request'))->toBeNull();
|
expect(session('saml_pending_request'))->toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('user sso settings and immutable id can be assigned via service and mapped to DTO', function (): void {
|
||||||
|
$userService = app(App\Services\UserService::class);
|
||||||
|
|
||||||
|
// Assign roles and update immutable ID
|
||||||
|
$userService->assignRoles($this->user->id, [], 'new-custom-sso-immutable-id');
|
||||||
|
|
||||||
|
// Retrieve fresh user and assert
|
||||||
|
$freshUser = $this->user->fresh();
|
||||||
|
expect($freshUser->immutable_id)->toBe('new-custom-sso-immutable-id');
|
||||||
|
|
||||||
|
// Assert UserIndexData maps it correctly
|
||||||
|
$dto = App\Data\User\UserIndexData::fromModel($freshUser);
|
||||||
|
expect($dto->immutableId)->toBe('new-custom-sso-immutable-id');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('saml settings field is only visible if selected roles contain a SAML app', function (): void {
|
||||||
|
// Create a role with a SAML connected app
|
||||||
|
$samlRole = App\Models\Role::findOrCreate('SAML User');
|
||||||
|
$samlRole->update(['priority' => 10]);
|
||||||
|
$samlRole->apps()->attach($this->samlApp->id, ['duration' => 365]);
|
||||||
|
|
||||||
|
// Create a role with no connected apps
|
||||||
|
$emptyRole = App\Models\Role::findOrCreate('Empty User');
|
||||||
|
$emptyRole->update(['priority' => 20]);
|
||||||
|
|
||||||
|
// Log in the administrator so they can view users
|
||||||
|
$admin = User::where('email', 'admin@example.com')->firstOrFail();
|
||||||
|
$this->actingAs($admin);
|
||||||
|
|
||||||
|
// 1. Livewire component with no roles selected should return false for showSamlSettings
|
||||||
|
Livewire::test('pages::access-manager.users.index')
|
||||||
|
->set('form.roleIds', [])
|
||||||
|
->assertSet('showSamlSettings', false);
|
||||||
|
|
||||||
|
// 2. Livewire component with emptyRole selected should return false
|
||||||
|
Livewire::test('pages::access-manager.users.index')
|
||||||
|
->set('form.roleIds', [$emptyRole->id])
|
||||||
|
->assertSet('showSamlSettings', false);
|
||||||
|
|
||||||
|
// 3. Livewire component with samlRole selected should return true
|
||||||
|
Livewire::test('pages::access-manager.users.index')
|
||||||
|
->set('form.roleIds', [$samlRole->id])
|
||||||
|
->assertSet('showSamlSettings', true);
|
||||||
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user