Skip to content

Commit

Permalink
feat(portals-admin-ids-admin): Add LegalRepresentative delegation type (
Browse files Browse the repository at this point in the history
#16069)

* Add missing translation for legal representation delegation type in IDS Admin

* Refactor delegation type filtering from client and scope creation to account for LegalRepresentative as superuser only.

* Update patch super user delegation type check.
Add tests.

* Handle legal representative in scope patch.
Add tests.

* Hide it from the UI.

* Fix duplicated message id

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
saevarma and kodiakhq[bot] committed Sep 26, 2024
1 parent b28f762 commit dfd6e3e
Show file tree
Hide file tree
Showing 14 changed files with 231 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import {
AdminPatchClientDto,
Client,
clientBaseAttributes,
ClientDelegationType,
ClientGrantType,
defaultAcrValue,
RefreshTokenExpiration,
SequelizeConfigService,
SUPER_USER_DELEGATION_TYPES,
translateRefreshTokenExpiration,
} from '@island.is/auth-api-lib'
import { User } from '@island.is/auth-nest-tools'
Expand Down Expand Up @@ -50,6 +52,10 @@ const createTestClientData = async (app: TestApp, user: User) => {
AuthDelegationType.LegalGuardian,
AuthDelegationProvider.NationalRegistry,
],
[
AuthDelegationType.LegalRepresentative,
AuthDelegationProvider.DistrictCommissionersRegistry,
],
].map(async ([delegationType, provider]) =>
fixtureFactory.createDelegationType({
id: delegationType,
Expand Down Expand Up @@ -619,6 +625,7 @@ describe('MeClientsController with auth', () => {
AuthDelegationType.PersonalRepresentative,
AuthDelegationType.ProcurationHolder,
AuthDelegationType.LegalGuardian,
AuthDelegationType.LegalRepresentative,
],
}

Expand All @@ -635,6 +642,7 @@ describe('MeClientsController with auth', () => {
AuthDelegationType.PersonalRepresentative,
AuthDelegationType.ProcurationHolder,
AuthDelegationType.LegalGuardian,
AuthDelegationType.LegalRepresentative,
]),
)
})
Expand Down Expand Up @@ -985,6 +993,7 @@ describe('MeClientsController with auth', () => {
AuthDelegationType.LegalGuardian,
AuthDelegationType.PersonalRepresentative,
AuthDelegationType.ProcurationHolder,
AuthDelegationType.LegalRepresentative,
],
}

Expand All @@ -997,6 +1006,7 @@ describe('MeClientsController with auth', () => {
AuthDelegationType.LegalGuardian,
AuthDelegationType.PersonalRepresentative,
AuthDelegationType.ProcurationHolder,
AuthDelegationType.LegalRepresentative,
],
supportsCustomDelegation: true,
supportsLegalGuardians: true,
Expand Down Expand Up @@ -1025,6 +1035,7 @@ describe('MeClientsController with auth', () => {
AuthDelegationType.LegalGuardian,
AuthDelegationType.PersonalRepresentative,
AuthDelegationType.ProcurationHolder,
AuthDelegationType.LegalRepresentative,
],
supportsCustomDelegation: true,
supportsLegalGuardians: true,
Expand All @@ -1036,6 +1047,7 @@ describe('MeClientsController with auth', () => {
AuthDelegationType.LegalGuardian,
AuthDelegationType.PersonalRepresentative,
AuthDelegationType.ProcurationHolder,
AuthDelegationType.LegalRepresentative,
],
supportsCustomDelegation: true,
supportsLegalGuardians: true,
Expand All @@ -1049,6 +1061,7 @@ describe('MeClientsController with auth', () => {
AuthDelegationType.LegalGuardian,
AuthDelegationType.PersonalRepresentative,
AuthDelegationType.ProcurationHolder,
AuthDelegationType.LegalRepresentative,
],
}

Expand All @@ -1063,6 +1076,66 @@ describe('MeClientsController with auth', () => {
supportsProcuringHolders: false,
})
})

it.each`
action
${'added'}
${'removed'}
`(
'should not have $action super user delegation type as normal',
async ({ action }) => {
// Arrange
const app = await setupApp({
AppModule,
SequelizeConfigService,
user,
dbType: 'postgres',
})
const server = request(app.getHttpServer())
await createTestClientData(app, user)

// Act
const res = await Promise.all(
SUPER_USER_DELEGATION_TYPES.map((delegationType) =>
server
.patch(
`/v2/me/tenants/${tenantId}/clients/${encodeURIComponent(
clientId,
)}`,
)
.send({
[`${action}DelegationTypes`]: [delegationType],
}),
),
)

// Assert
res.forEach((r) => {
expect(r.status).toEqual(403)
expect(r.body).toEqual({
type: 'https://httpstatuses.org/403',
title: 'Forbidden',
status: 403,
detail:
'User does not have access to update admin controlled fields.',
})
})

// DB assert
const clientDelegationTypeModel = app.get(
getModelToken(ClientDelegationType),
)
const clientDelegationTypes = await clientDelegationTypeModel.findAll(
{
where: {
clientId,
},
},
)

expect(clientDelegationTypes.length).toEqual(0)
},
)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ApiScopeDelegationType,
AdminPatchScopeDto,
ApiScope,
SUPER_USER_DELEGATION_TYPES,
} from '@island.is/auth-api-lib'
import { FixtureFactory } from '@island.is/services/auth/testing'
import {
Expand Down Expand Up @@ -126,6 +127,10 @@ const createTestData = async ({
AuthDelegationType.LegalGuardian,
AuthDelegationProvider.NationalRegistry,
],
[
AuthDelegationType.LegalRepresentative,
AuthDelegationProvider.DistrictCommissionersRegistry,
],
].map(async ([delegationType, provider]) =>
fixtureFactory.createDelegationType({
id: delegationType,
Expand Down Expand Up @@ -373,6 +378,8 @@ interface PatchTestCase {
allowExplicitDelegationGrant?: boolean
grantToPersonalRepresentatives?: boolean
isAccessControlled?: boolean
addedDelegationTypes?: AuthDelegationType[]
removedDelegationTypes?: AuthDelegationType[]
}
expected: {
status: number
Expand Down Expand Up @@ -512,6 +519,44 @@ const patchTestCases: Record<string, PatchTestCase> = {
},
}

const expected403Response = {
status: 403,
body: {
title: 'Forbidden',
status: 403,
detail: 'User does not have access to update admin controlled fields',
type: 'https://httpstatuses.org/403',
},
}

SUPER_USER_DELEGATION_TYPES.map((delegationType) => {
const delegationTypeName = AuthDelegationType[delegationType]

patchTestCases[
`should return a forbidden exception when adding super user delegation type: ${delegationTypeName}`
] = {
user: currentUser,
tenantId: TENANT_ID,
scopeName: mockedPatchApiScope.name,
input: {
addedDelegationTypes: [delegationType],
},
expected: expected403Response,
}

patchTestCases[
`should return a forbidden exception when removing super user delegation type: ${delegationTypeName}`
] = {
user: currentUser,
tenantId: TENANT_ID,
scopeName: mockedPatchApiScope.name,
input: {
removedDelegationTypes: [delegationType],
},
expected: expected403Response,
}
})

describe('MeScopesController', () => {
describe('with auth', () => {
// GET: /v2/me/tenants/:tenantId/scopes
Expand Down Expand Up @@ -760,7 +805,7 @@ describe('MeScopesController', () => {
})
})

describe('PATCH: /v2/me/tenants/:tenantId/scopes/:scopeName', () => {
describe('PATCH: /v2/me/tenants/:tenantId/scopes/:scopeName as super user', () => {
let app: TestApp
let server: request.SuperTest<request.Test>
let apiScopeDelegationTypeModel: typeof ApiScopeDelegationType
Expand Down Expand Up @@ -830,6 +875,7 @@ describe('MeScopesController', () => {
AuthDelegationType.LegalGuardian,
AuthDelegationType.ProcurationHolder,
AuthDelegationType.PersonalRepresentative,
AuthDelegationType.LegalRepresentative,
],
},
expected: {
Expand All @@ -842,6 +888,7 @@ describe('MeScopesController', () => {
AuthDelegationType.LegalGuardian,
AuthDelegationType.ProcurationHolder,
AuthDelegationType.PersonalRepresentative,
AuthDelegationType.LegalRepresentative,
],
},
})
Expand All @@ -855,6 +902,7 @@ describe('MeScopesController', () => {
AuthDelegationType.LegalGuardian,
AuthDelegationType.ProcurationHolder,
AuthDelegationType.PersonalRepresentative,
AuthDelegationType.LegalRepresentative,
],
},
expected: {
Expand All @@ -867,6 +915,7 @@ describe('MeScopesController', () => {
AuthDelegationType.LegalGuardian,
AuthDelegationType.ProcurationHolder,
AuthDelegationType.PersonalRepresentative,
AuthDelegationType.LegalRepresentative,
],
},
})
Expand All @@ -878,6 +927,7 @@ describe('MeScopesController', () => {
AuthDelegationType.LegalGuardian,
AuthDelegationType.ProcurationHolder,
AuthDelegationType.PersonalRepresentative,
AuthDelegationType.LegalRepresentative,
],
},
expected: {
Expand Down Expand Up @@ -944,7 +994,7 @@ describe('MeScopesController', () => {
})
})

describe('POST: /v2/me/tenants/:tenantId/scopes', () => {
describe('POST: /v2/me/tenants/:tenantId/scopes as super user', () => {
let app: TestApp
let server: request.SuperTest<request.Test>
let apiScopeDelegationTypeModel: typeof ApiScopeDelegationType
Expand Down Expand Up @@ -1028,7 +1078,7 @@ describe('MeScopesController', () => {
})
})

describe('POST: /v2/me/tenants/:tenantId/scopes', () => {
describe('POST: /v2/me/tenants/:tenantId/scopes as normal user', () => {
let app: TestApp
let server: request.SuperTest<request.Test>
let apiScopeDelegationTypeModel: typeof ApiScopeDelegationType
Expand Down
1 change: 1 addition & 0 deletions libs/auth-api-lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export * from './lib/resources/admin/dto/admin-create-scope.dto'
export * from './lib/resources/admin/dto/admin-patch-scope.dto'
export * from './lib/resources/resource-translation.service'
export * from './lib/resources/scope.service'
export { SUPER_USER_DELEGATION_TYPES } from './lib/resources/utils/filters'

// Clients module
export * from './lib/clients/clients.module'
Expand Down
35 changes: 17 additions & 18 deletions libs/auth-api-lib/src/lib/clients/admin/admin-clients.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ import {
superUserFields,
} from './dto/admin-patch-client.dto'
import { ClientDelegationType } from '../models/client-delegation-type.model'
import { filterPersonalRepresentative } from '../../resources/utils/personalRepresentativeFilter'
import {
delegationTypeSuperUserFilter,
SUPER_USER_DELEGATION_TYPES,
} from '../../resources/utils/filters'

export const clientBaseAttributes: Partial<Client> = {
absoluteRefreshTokenLifetime: 8 * 60 * 60, // 8 hours
Expand Down Expand Up @@ -179,7 +182,7 @@ export class AdminClientsService {
// Remove defined super admin fields
...omit(clientDto, superUserFields),
// Remove personal representative from delegation types since it is not allowed for non-super admins
supportedDelegationTypes: filterPersonalRepresentative(
supportedDelegationTypes: delegationTypeSuperUserFilter(
clientDto.supportedDelegationTypes ?? [],
),
}
Expand Down Expand Up @@ -659,29 +662,25 @@ export class AdminClientsService {
) {
const isSuperUser = this.isSuperAdmin(user)

const updatedFields = Object.keys(input)
const superUserUpdatedFields = updatedFields.filter((field) =>
superUserFields.includes(field),
)

// Verify that the user is super admin, so they can update PersonalRepresentative in the delegation type
// Verify if superuser delegation types are being updated that user is super user
const allDelegationTypes = [
...(input.removedDelegationTypes ?? []),
...(input.addedDelegationTypes ?? []),
]

if (!isSuperUser && allDelegationTypes.length > 0) {
for (const delegationType of allDelegationTypes) {
if (
delegationType.startsWith(
`${AuthDelegationType.PersonalRepresentative}:`,
)
) {
return false
}
}
const hasSuperUserDelegationType = allDelegationTypes.some(
(delegationType) => SUPER_USER_DELEGATION_TYPES.includes(delegationType),
)

if (!isSuperUser && hasSuperUserDelegationType) {
return false
}

const updatedFields = Object.keys(input)
const superUserUpdatedFields = updatedFields.filter((field) =>
superUserFields.includes(field),
)

if (superUserUpdatedFields.length === 0) {
// There are no superuser fields to update
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
IsString,
} from 'class-validator'

import { AuthDelegationType } from '@island.is/shared/types'

import { ClientType } from '../../../types'
import { AdminPatchClientDto } from './admin-patch-client.dto'

Expand Down Expand Up @@ -50,9 +52,10 @@ export class AdminCreateClientDto extends OmitType(AdminPatchClientDto, [

@IsArray()
@IsOptional()
@IsEnum(AuthDelegationType, { each: true })
@ApiProperty({
example: ['Custom'],
type: [String],
})
supportedDelegationTypes?: string[]
supportedDelegationTypes?: AuthDelegationType[]
}
Loading

0 comments on commit dfd6e3e

Please sign in to comment.