From 68abd551d6e8d4265656091f831592acd2eb482b Mon Sep 17 00:00:00 2001 From: Herdismaria Date: Wed, 22 May 2024 13:33:14 +0000 Subject: [PATCH 1/3] add support for delegation types to api-scopes --- .../src/app/v2/scopes/test/me-scopes.spec.ts | 331 +++++++++++++++++- libs/auth-api-lib/src/index.ts | 1 + .../src/lib/clients/clients.module.ts | 2 + .../models/delegation-type.model.ts | 5 + .../resources/admin/admin-scope.service.ts | 251 ++++++++++++- .../admin/dto/admin-create-scope.dto.ts | 17 +- .../admin/dto/admin-patch-scope.dto.ts | 16 + .../resources/dto/base/api-scope-base.dto.ts | 10 +- .../models/api-scope-delegation-type.model.ts | 4 +- .../lib/resources/models/api-scope.model.ts | 15 + 10 files changed, 637 insertions(+), 15 deletions(-) diff --git a/apps/services/auth/admin-api/src/app/v2/scopes/test/me-scopes.spec.ts b/apps/services/auth/admin-api/src/app/v2/scopes/test/me-scopes.spec.ts index 2aba9d0457f2..799cd7c5a026 100644 --- a/apps/services/auth/admin-api/src/app/v2/scopes/test/me-scopes.spec.ts +++ b/apps/services/auth/admin-api/src/app/v2/scopes/test/me-scopes.spec.ts @@ -9,9 +9,14 @@ import { ApiScopeUserClaim, SequelizeConfigService, TranslatedValueDto, + ApiScopeDelegationType, + AdminPatchScopeDto, } from '@island.is/auth-api-lib' import { FixtureFactory } from '@island.is/services/auth/testing' -import { AuthDelegationType } from '@island.is/shared/types' +import { + AuthDelegationProvider, + AuthDelegationType, +} from '@island.is/shared/types' import { isDefined } from '@island.is/shared/utils' import { createCurrentUser, @@ -104,6 +109,29 @@ const createTestData = async ({ ), ) } + + await Promise.all( + [ + [AuthDelegationType.Custom, AuthDelegationProvider.Custom], + [ + AuthDelegationType.ProcurationHolder, + AuthDelegationProvider.CompanyRegistry, + ], + [ + AuthDelegationType.PersonalRepresentative, + AuthDelegationProvider.PersonalRepresentativeRegistry, + ], + [ + AuthDelegationType.LegalGuardian, + AuthDelegationProvider.NationalRegistry, + ], + ].map(async ([delegationType, provider]) => + fixtureFactory.createDelegationType({ + id: delegationType, + providerId: provider, + }), + ), + ) } interface GetTestCase { @@ -240,6 +268,7 @@ const createInput = { const expectedCreateOutput = { ...mockedCreateApiScope, ...createInput, + supportedDelegationTypes: [], } const createTestCases: Record = { @@ -276,6 +305,7 @@ const createTestCases: Record = { ...expectedCreateOutput, grantToAuthenticatedUser: true, grantToLegalGuardians: true, + supportedDelegationTypes: [AuthDelegationType.LegalGuardian], }, }, }, @@ -397,6 +427,7 @@ const patchExpectedOutput = { order: 0, required: false, showInDiscoveryDocument: true, + supportedDelegationTypes: [], ...inputPatch, } @@ -435,6 +466,7 @@ const patchTestCases: Record = { grantToProcuringHolders: false, allowExplicitDelegationGrant: false, isAccessControlled: false, + supportedDelegationTypes: [], }, }, }, @@ -445,7 +477,14 @@ const patchTestCases: Record = { input: inputPatch, expected: { status: 200, - body: patchExpectedOutput, + body: { + ...patchExpectedOutput, + supportedDelegationTypes: [ + AuthDelegationType.Custom, + AuthDelegationType.LegalGuardian, + AuthDelegationType.ProcurationHolder, + ], + }, }, }, 'should return a bad request because of invalid input': { @@ -596,6 +635,7 @@ describe('MeScopesController', () => { const testCase = createTestCases[testCaseName] let app: TestApp let server: request.SuperTest + let apiScopeDelegationTypeModel: typeof ApiScopeDelegationType beforeAll(async () => { app = await setupApp({ @@ -605,6 +645,9 @@ describe('MeScopesController', () => { dbType: 'postgres', }) server = request(app.getHttpServer()) + apiScopeDelegationTypeModel = await app.get( + getModelToken(ApiScopeDelegationType), + ) await createTestData({ app, @@ -655,6 +698,17 @@ describe('MeScopesController', () => { const dbApiScopeUserClaim = await apiScopeUserClaim.findByPk( response.body.name, ) + const apiScopeDelegationTypes = + await apiScopeDelegationTypeModel.findAll({ + where: { + apiScopeName: response.body.name, + }, + }) + + expect(apiScopeDelegationTypes).toHaveLength( + (testCase.expected.body.supportedDelegationTypes as string[]) + .length, + ) expect(dbApiScopeUserClaim).toMatchObject({ apiScopeName: testCase.expected.body.name, @@ -672,6 +726,7 @@ describe('MeScopesController', () => { const testCase = patchTestCases[testCaseName] let app: TestApp let server: request.SuperTest + let apiScopeDelegationTypeModel: typeof ApiScopeDelegationType beforeAll(async () => { app = await setupApp({ @@ -682,6 +737,10 @@ describe('MeScopesController', () => { }) server = request(app.getHttpServer()) + apiScopeDelegationTypeModel = await app.get( + getModelToken(ApiScopeDelegationType), + ) + await createTestData({ app, tenantId: testCase.tenantId, @@ -702,6 +761,274 @@ describe('MeScopesController', () => { // Assert response expect(response.status).toEqual(testCase.expected.status) expect(response.body).toEqual(testCase.expected.body) + + // Assert - db record + if (testCase.expected.body.supportedDelegationTypes) { + const apiScopeDelegationTypes = + await apiScopeDelegationTypeModel.findAll({ + where: { + apiScopeName: testCase.scopeName, + }, + }) + + expect(apiScopeDelegationTypes).toHaveLength( + (testCase.expected.body.supportedDelegationTypes as string[]) + .length, + ) + } + }) + }) + }) + + describe('PATCH: /v2/me/tenants/:tenantId/scopes/:scopeName', () => { + let app: TestApp + let server: request.SuperTest + let apiScopeDelegationTypeModel: typeof ApiScopeDelegationType + + beforeAll(async () => { + app = await setupApp({ + AppModule, + SequelizeConfigService, + user: superUser, + dbType: 'postgres', + }) + server = request(app.getHttpServer()) + + apiScopeDelegationTypeModel = await app.get( + getModelToken(ApiScopeDelegationType), + ) + + await createTestData({ + app, + tenantId: TENANT_ID, + tenantOwnerNationalId: superUser.nationalId, + }) + }) + + const patchAndAssert = async ({ + input, + expected, + }: { + input: AdminPatchScopeDto + expected: Partial + }) => { + const response = await server + .patch( + `/v2/me/tenants/${TENANT_ID}/scopes/${encodeURIComponent( + mockedPatchApiScope.name, + )}`, + ) + .send(input) + + expect(response.status).toEqual(200) + expect(response.body).toMatchObject({ + ...expected, + supportedDelegationTypes: expect.arrayContaining( + expected?.supportedDelegationTypes || [], + ), + }) + const apiScopeDelegationTypes = await apiScopeDelegationTypeModel.findAll( + { + where: { + apiScopeName: mockedPatchApiScope.name, + }, + }, + ) + + expect(apiScopeDelegationTypes).toHaveLength( + expected.supportedDelegationTypes?.length || 0, + ) + } + + it('should delete rows from api_scope_delegation_types table when removing types with boolean fields', async () => { + // add delegation types that we can then remove + await patchAndAssert({ + input: { + grantToPersonalRepresentatives: true, + grantToLegalGuardians: true, + grantToProcuringHolders: true, + allowExplicitDelegationGrant: true, + }, + expected: { + grantToPersonalRepresentatives: true, + grantToLegalGuardians: true, + grantToProcuringHolders: true, + allowExplicitDelegationGrant: true, + supportedDelegationTypes: [ + AuthDelegationType.Custom, + AuthDelegationType.LegalGuardian, + AuthDelegationType.ProcurationHolder, + AuthDelegationType.PersonalRepresentative, + ], + }, + }) + + await patchAndAssert({ + input: { + grantToPersonalRepresentatives: false, + grantToLegalGuardians: false, + grantToProcuringHolders: false, + allowExplicitDelegationGrant: false, + }, + expected: { + grantToPersonalRepresentatives: false, + grantToLegalGuardians: false, + grantToProcuringHolders: false, + allowExplicitDelegationGrant: false, + supportedDelegationTypes: [], + }, + }) + }) + + it('should be able to add supported delegation types to api scope with array property', async () => { + await patchAndAssert({ + input: { + addedDelegationTypes: [ + AuthDelegationType.Custom, + AuthDelegationType.LegalGuardian, + AuthDelegationType.ProcurationHolder, + AuthDelegationType.PersonalRepresentative, + ], + }, + expected: { + grantToPersonalRepresentatives: true, + grantToLegalGuardians: true, + grantToProcuringHolders: true, + allowExplicitDelegationGrant: true, + supportedDelegationTypes: [ + AuthDelegationType.Custom, + AuthDelegationType.LegalGuardian, + AuthDelegationType.ProcurationHolder, + AuthDelegationType.PersonalRepresentative, + ], + }, + }) + }) + + it('should be able to remove supported delegation types to api scope with array property', async () => { + await patchAndAssert({ + input: { + addedDelegationTypes: [ + AuthDelegationType.Custom, + AuthDelegationType.LegalGuardian, + AuthDelegationType.ProcurationHolder, + AuthDelegationType.PersonalRepresentative, + ], + }, + expected: { + grantToPersonalRepresentatives: true, + grantToLegalGuardians: true, + grantToProcuringHolders: true, + allowExplicitDelegationGrant: true, + supportedDelegationTypes: [ + AuthDelegationType.Custom, + AuthDelegationType.LegalGuardian, + AuthDelegationType.ProcurationHolder, + AuthDelegationType.PersonalRepresentative, + ], + }, + }) + + await patchAndAssert({ + input: { + removedDelegationTypes: [ + AuthDelegationType.Custom, + AuthDelegationType.LegalGuardian, + AuthDelegationType.ProcurationHolder, + AuthDelegationType.PersonalRepresentative, + ], + }, + expected: { + grantToPersonalRepresentatives: false, + grantToLegalGuardians: false, + grantToProcuringHolders: false, + allowExplicitDelegationGrant: false, + supportedDelegationTypes: [], + }, + }) + }) + }) + + describe('POST: /v2/me/tenants/:tenantId/scopes', () => { + let app: TestApp + let server: request.SuperTest + let apiScopeDelegationTypeModel: typeof ApiScopeDelegationType + + beforeAll(async () => { + app = await setupApp({ + AppModule, + SequelizeConfigService, + user: superUser, + dbType: 'postgres', + }) + server = request(app.getHttpServer()) + + apiScopeDelegationTypeModel = await app.get( + getModelToken(ApiScopeDelegationType), + ) + + await createTestData({ + app, + tenantId: TENANT_ID, + tenantOwnerNationalId: superUser.nationalId, + }) + }) + + const createAndAssert = async ({ + input, + expected, + }: { + input: AdminCreateScopeDto + expected: Partial + }) => { + const response = await server + .post(`/v2/me/tenants/${TENANT_ID}/scopes`) + .send(input) + + expect(response.status).toEqual(200) + expect(response.body).toMatchObject({ + ...expected, + supportedDelegationTypes: expect.arrayContaining( + expected?.supportedDelegationTypes || [], + ), + }) + + const apiScopeDelegationTypes = await apiScopeDelegationTypeModel.findAll( + { + where: { + apiScopeName: response.body.name, + }, + }, + ) + + expect(apiScopeDelegationTypes).toHaveLength( + expected.supportedDelegationTypes?.length || 0, + ) + } + + it('should be able to create api scope using supportedDelegationTypes property', async () => { + await createAndAssert({ + input: { + ...createInput, + supportedDelegationTypes: [ + AuthDelegationType.Custom, + AuthDelegationType.LegalGuardian, + AuthDelegationType.ProcurationHolder, + AuthDelegationType.PersonalRepresentative, + ], + }, + expected: { + grantToPersonalRepresentatives: true, + grantToLegalGuardians: true, + grantToProcuringHolders: true, + allowExplicitDelegationGrant: true, + supportedDelegationTypes: [ + AuthDelegationType.Custom, + AuthDelegationType.LegalGuardian, + AuthDelegationType.ProcurationHolder, + AuthDelegationType.PersonalRepresentative, + ], + }, }) }) }) diff --git a/libs/auth-api-lib/src/index.ts b/libs/auth-api-lib/src/index.ts index 741b813dc206..eb64824319c5 100644 --- a/libs/auth-api-lib/src/index.ts +++ b/libs/auth-api-lib/src/index.ts @@ -66,6 +66,7 @@ export * from './lib/resources/models/api-resource-secret.model' export * from './lib/resources/models/api-resource-user-claim.model' export * from './lib/resources/models/api-scope.model' export * from './lib/resources/models/api-scope-user-claim.model' +export * from './lib/resources/models/api-scope-delegation-type.model' export * from './lib/resources/models/api-scope-group.model' export * from './lib/resources/models/api-scope-user-access.model' export * from './lib/resources/models/api-scope-user.model' diff --git a/libs/auth-api-lib/src/lib/clients/clients.module.ts b/libs/auth-api-lib/src/lib/clients/clients.module.ts index 98ccce2b13aa..79d2b674b5eb 100644 --- a/libs/auth-api-lib/src/lib/clients/clients.module.ts +++ b/libs/auth-api-lib/src/lib/clients/clients.module.ts @@ -22,6 +22,7 @@ import { AdminTranslationService } from '../resources/admin/services/admin-trans import { ClientDelegationType } from './models/client-delegation-type.model' import { DelegationTypeModel } from '../delegations/models/delegation-type.model' import { DelegationProviderModel } from '../delegations/models/delegation-provider.model' +import { ApiScopeDelegationType } from '../resources/models/api-scope-delegation-type.model' @Module({ imports: [ @@ -41,6 +42,7 @@ import { DelegationProviderModel } from '../delegations/models/delegation-provid Domain, ApiScope, ApiScopeUserClaim, + ApiScopeDelegationType, ]), TranslationModule, ], diff --git a/libs/auth-api-lib/src/lib/delegations/models/delegation-type.model.ts b/libs/auth-api-lib/src/lib/delegations/models/delegation-type.model.ts index f70cfe50e26d..8c3f7c3f5ec2 100644 --- a/libs/auth-api-lib/src/lib/delegations/models/delegation-type.model.ts +++ b/libs/auth-api-lib/src/lib/delegations/models/delegation-type.model.ts @@ -19,6 +19,8 @@ import { import { DelegationProviderModel } from './delegation-provider.model' import { ClientDelegationType } from '../../clients/models/client-delegation-type.model' import { Client } from '../../clients/models/client.model' +import { ApiScopeDelegationType } from '../../resources/models/api-scope-delegation-type.model' +import { ApiScope } from '../../resources/models/api-scope.model' @Table({ tableName: 'delegation_type', @@ -62,6 +64,9 @@ export class DelegationTypeModel extends Model< @BelongsToMany(() => Client, () => ClientDelegationType) clients!: Client[] + @BelongsToMany(() => ApiScope, () => ApiScopeDelegationType) + apiScopes!: ApiScope[] + @CreatedAt readonly created!: CreationOptional diff --git a/libs/auth-api-lib/src/lib/resources/admin/admin-scope.service.ts b/libs/auth-api-lib/src/lib/resources/admin/admin-scope.service.ts index a4fa03d845ff..94ca6aa64e81 100644 --- a/libs/auth-api-lib/src/lib/resources/admin/admin-scope.service.ts +++ b/libs/auth-api-lib/src/lib/resources/admin/admin-scope.service.ts @@ -5,14 +5,13 @@ import { Injectable, } from '@nestjs/common' import { InjectModel } from '@nestjs/sequelize' -import { Transaction } from 'sequelize' +import { Op, Transaction } from 'sequelize' import omit from 'lodash/omit' import { validatePermissionId } from '@island.is/auth/shared' import { isDefined } from '@island.is/shared/utils' import { ApiScope } from '../models/api-scope.model' -import { Client } from '../../clients/models/client.model' import { AdminCreateScopeDto } from './dto/admin-create-scope.dto' import { ApiScopeUserClaim } from '../models/api-scope-user-claim.model' import { AdminScopeDTO } from './dto/admin-scope.dto' @@ -26,6 +25,9 @@ import { TranslatedValueDto } from '../../translation/dto/translated-value.dto' import { TranslationService } from '../../translation/translation.service' import { User } from '@island.is/auth-nest-tools' import { AdminPortalScope } from '@island.is/auth/scopes' +import { AuthDelegationProvider, AuthDelegationType } from 'delegation' +import { ApiScopeDelegationType } from '../models/api-scope-delegation-type.model' +import { DelegationTypeModel } from '../../delegations/models/delegation-type.model' /** * This is a service that is used to access the admin scopes @@ -35,10 +37,12 @@ export class AdminScopeService { constructor( @InjectModel(ApiScope) private readonly apiScope: typeof ApiScope, - @InjectModel(Client) - private readonly clientModel: typeof Client, @InjectModel(ApiScopeUserClaim) private readonly apiScopeUserClaim: typeof ApiScopeUserClaim, + @InjectModel(ApiScopeDelegationType) + private readonly apiScopeDelegationType: typeof ApiScopeDelegationType, + @InjectModel(DelegationTypeModel) + private readonly delegationTypeModel: typeof DelegationTypeModel, private readonly adminTranslationService: AdminTranslationService, private readonly translationService: TranslationService, private sequelize: Sequelize, @@ -50,6 +54,9 @@ export class AdminScopeService { domainName: tenantId, enabled: true, }, + include: [ + { model: ApiScopeDelegationType, as: 'supportedDelegationTypes' }, + ], }) const translations = @@ -84,6 +91,9 @@ export class AdminScopeService { domainName: tenantId, enabled: true, }, + include: [ + { model: ApiScopeDelegationType, as: 'supportedDelegationTypes' }, + ], }) if (!apiScope) { @@ -149,10 +159,10 @@ export class AdminScopeService { throw new BadRequestException(translatedValuesErrorMsg) } - const apiScope = await this.sequelize.transaction(async (transaction) => { + await this.sequelize.transaction(async (transaction) => { const scope = await this.apiScope.create( { - ...input, + ...omit(input, ['displayName', 'description']), displayName, description, domainName: tenantId, @@ -177,9 +187,30 @@ export class AdminScopeService { transaction, ) + await this.addScopeDelegationTypes({ + apiScopeName: scope.name, + delegationBooleanTypes: input, + delegationTypes: input.supportedDelegationTypes, + transaction, + }) + return scope }) + const apiScope = await this.apiScope.findOne({ + where: { + name: input.name, + domainName: tenantId, + }, + include: [ + { model: ApiScopeDelegationType, as: 'supportedDelegationTypes' }, + ], + }) + + if (!apiScope) { + throw new Error('Failed to create scope') + } + const translations = await this.adminTranslationService.getApiScopeTranslations([ apiScope.name, @@ -302,7 +333,14 @@ export class AdminScopeService { // Update apiScope row and get the Icelandic translations for displayName and description await this.apiScope.update( { - ...omit(input, ['displayName', 'description']), + ...omit(input, [ + 'displayName', + 'description', + 'grantToProcuringHolders', + 'grantToLegalGuardians', + 'grantToPersonalRepresentatives', + 'allowExplicitDelegationGrant', + ]), ...(displayName && { displayName }), ...(description && { description }), }, @@ -314,6 +352,18 @@ export class AdminScopeService { }, ) + await this.addScopeDelegationTypes({ + apiScopeName: scopeName, + delegationBooleanTypes: input, + delegationTypes: input.addedDelegationTypes, + transaction, + }) + await this.removeScopeDelegationTypes({ + apiScopeName: scopeName, + delegationBooleanTypes: input, + delegationTypes: input.removedDelegationTypes, + transaction, + }) await this.updateScopeTranslatedValueFields(scopeName, input, transaction) }) @@ -341,4 +391,191 @@ export class AdminScopeService { // If there is a superUser field in the updated fields, the user must be a superUser return superUserUpdatedFields.length > 0 && isSuperUser } + + private async addScopeDelegationTypes({ + apiScopeName, + delegationBooleanTypes, + delegationTypes, + transaction, + }: { + apiScopeName: string + delegationTypes?: string[] + delegationBooleanTypes: { + allowExplicitDelegationGrant?: boolean + grantToLegalGuardians?: boolean + grantToProcuringHolders?: boolean + grantToPersonalRepresentatives?: boolean + } + transaction: Transaction + }) { + // boolean fields + const grantToProcuringHolders = + delegationTypes?.includes(AuthDelegationType.ProcurationHolder) || + delegationBooleanTypes.grantToProcuringHolders + const grantToLegalGuardians = + delegationTypes?.includes(AuthDelegationType.LegalGuardian) || + delegationBooleanTypes.grantToLegalGuardians + const grantToPersonalRepresentatives = + delegationTypes?.some((delegationType) => + delegationType.startsWith(AuthDelegationType.PersonalRepresentative), + ) || delegationBooleanTypes.grantToPersonalRepresentatives + const allowExplicitDelegationGrant = + delegationTypes?.includes(AuthDelegationType.Custom) || + delegationBooleanTypes.allowExplicitDelegationGrant + + // delegation types to add to api_scope_delegation_types table + const delegationTypesToAdd: string[] = [ + ...(allowExplicitDelegationGrant ? [AuthDelegationType.Custom] : []), + ...(grantToLegalGuardians ? [AuthDelegationType.LegalGuardian] : []), + ...(grantToProcuringHolders + ? [AuthDelegationType.ProcurationHolder] + : []), + ] + + if (grantToPersonalRepresentatives) { + const personalRepresentativeDelegationTypes = + await this.delegationTypeModel.findAll({ + where: { + provider: AuthDelegationProvider.PersonalRepresentativeRegistry, + }, + }) + + delegationTypesToAdd.push( + ...personalRepresentativeDelegationTypes.map( + (delegationType) => delegationType.id, + ), + ) + } + + // create delegation type rows + if (delegationTypesToAdd.length > 0) { + await Promise.all( + delegationTypesToAdd.map((delegationType) => + this.apiScopeDelegationType.upsert( + { + apiScopeName, + delegationType, + }, + { transaction }, + ), + ), + ) + } + + // update boolean fields + if ( + grantToLegalGuardians || + grantToPersonalRepresentatives || + grantToProcuringHolders || + allowExplicitDelegationGrant + ) { + await this.apiScope.update( + { + grantToLegalGuardians, + grantToPersonalRepresentatives, + grantToProcuringHolders, + allowExplicitDelegationGrant, + }, + { + transaction, + where: { + name: apiScopeName, + }, + }, + ) + } + } + + private async removeScopeDelegationTypes({ + apiScopeName, + delegationBooleanTypes, + delegationTypes, + transaction, + }: { + apiScopeName: string + delegationTypes?: string[] + delegationBooleanTypes: { + allowExplicitDelegationGrant?: boolean + grantToLegalGuardians?: boolean + grantToProcuringHolders?: boolean + grantToPersonalRepresentatives?: boolean + } + transaction: Transaction + }) { + // boolean fields + const grantToProcuringHolders = delegationTypes?.includes( + AuthDelegationType.ProcurationHolder, + ) + ? false + : delegationBooleanTypes.grantToProcuringHolders + const grantToLegalGuardians = delegationTypes?.includes( + AuthDelegationType.LegalGuardian, + ) + ? false + : delegationBooleanTypes.grantToLegalGuardians + const grantToPersonalRepresentatives = delegationTypes?.some( + (delegationType) => + delegationType.startsWith(AuthDelegationType.PersonalRepresentative), + ) + ? false + : delegationBooleanTypes.grantToPersonalRepresentatives + const allowExplicitDelegationGrant = delegationTypes?.includes( + AuthDelegationType.Custom, + ) + ? false + : delegationBooleanTypes.allowExplicitDelegationGrant + + // delegation types to remove from api_scope_delegation_types table + const delegationTypesToRemove = [ + ...(allowExplicitDelegationGrant === false + ? [AuthDelegationType.Custom] + : []), + ...(grantToLegalGuardians === false + ? [AuthDelegationType.LegalGuardian] + : []), + ...(grantToProcuringHolders === false + ? [AuthDelegationType.ProcurationHolder] + : []), + ...(grantToPersonalRepresentatives === false + ? [AuthDelegationType.PersonalRepresentative] + : []), + ] + + // remove delegation type rows + + await Promise.all( + delegationTypesToRemove.map((delegationType) => + this.apiScopeDelegationType.destroy({ + transaction, + where: { + apiScopeName, + delegationType: { [Op.startsWith]: delegationType }, + }, + }), + ), + ) + + // update boolean fields + if ( + grantToLegalGuardians === false || + grantToPersonalRepresentatives === false || + grantToProcuringHolders === false || + allowExplicitDelegationGrant === false + ) { + await this.apiScope.update( + { + grantToLegalGuardians, + grantToPersonalRepresentatives, + grantToProcuringHolders, + allowExplicitDelegationGrant, + }, + { + transaction, + where: { + name: apiScopeName, + }, + }, + ) + } + } } diff --git a/libs/auth-api-lib/src/lib/resources/admin/dto/admin-create-scope.dto.ts b/libs/auth-api-lib/src/lib/resources/admin/dto/admin-create-scope.dto.ts index fec53e21ec81..fbc5f678fdb8 100644 --- a/libs/auth-api-lib/src/lib/resources/admin/dto/admin-create-scope.dto.ts +++ b/libs/auth-api-lib/src/lib/resources/admin/dto/admin-create-scope.dto.ts @@ -1,11 +1,22 @@ -import { ApiProperty } from '@nestjs/swagger' -import { IsNotEmpty, IsString } from 'class-validator' +import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger' +import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator' import { AdminPatchScopeDto } from './admin-patch-scope.dto' -export class AdminCreateScopeDto extends AdminPatchScopeDto { +export class AdminCreateScopeDto extends OmitType(AdminPatchScopeDto, [ + 'removedDelegationTypes', + 'addedDelegationTypes', +]) { @IsString() @IsNotEmpty() @ApiProperty({ example: '@island.is' }) name!: string + + @IsArray() + @IsOptional() + @ApiPropertyOptional({ + type: [String], + example: ['Custom'], + }) + supportedDelegationTypes?: string[] } diff --git a/libs/auth-api-lib/src/lib/resources/admin/dto/admin-patch-scope.dto.ts b/libs/auth-api-lib/src/lib/resources/admin/dto/admin-patch-scope.dto.ts index a55b5b46b6a0..e7873e7084a9 100644 --- a/libs/auth-api-lib/src/lib/resources/admin/dto/admin-patch-scope.dto.ts +++ b/libs/auth-api-lib/src/lib/resources/admin/dto/admin-patch-scope.dto.ts @@ -84,6 +84,22 @@ export class AdminPatchScopeDto { example: false, }) grantToPersonalRepresentatives?: boolean + + @IsArray() + @IsOptional() + @ApiPropertyOptional({ + type: [String], + example: ['Custom'], + }) + addedDelegationTypes?: string[] + + @IsArray() + @IsOptional() + @ApiPropertyOptional({ + type: [String], + example: ['Custom'], + }) + removedDelegationTypes?: string[] } export const superUserScopeFields = [ diff --git a/libs/auth-api-lib/src/lib/resources/dto/base/api-scope-base.dto.ts b/libs/auth-api-lib/src/lib/resources/dto/base/api-scope-base.dto.ts index 36adfc2e643f..c46dfdd256a7 100644 --- a/libs/auth-api-lib/src/lib/resources/dto/base/api-scope-base.dto.ts +++ b/libs/auth-api-lib/src/lib/resources/dto/base/api-scope-base.dto.ts @@ -6,8 +6,9 @@ import { IsInt, Min, Max, + IsArray, } from 'class-validator' -import { ApiProperty } from '@nestjs/swagger' +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' export class ApiScopeBaseDTO { @IsBoolean() @@ -107,6 +108,13 @@ export class ApiScopeBaseDTO { }) readonly automaticDelegationGrant!: boolean + @IsArray() + @ApiPropertyOptional({ + type: [String], + example: ['Custom'], + }) + supportedDelegationTypes?: string[] + @IsBoolean() @IsNotEmpty() @ApiProperty({ diff --git a/libs/auth-api-lib/src/lib/resources/models/api-scope-delegation-type.model.ts b/libs/auth-api-lib/src/lib/resources/models/api-scope-delegation-type.model.ts index 77ece38fe869..6b3de98bd8ca 100644 --- a/libs/auth-api-lib/src/lib/resources/models/api-scope-delegation-type.model.ts +++ b/libs/auth-api-lib/src/lib/resources/models/api-scope-delegation-type.model.ts @@ -24,7 +24,7 @@ export class ApiScopeDelegationType extends Model { }) @ForeignKey(() => ApiScope) @ApiProperty() - api_scope_name!: string + apiScopeName!: string @PrimaryKey @Column({ @@ -33,7 +33,7 @@ export class ApiScopeDelegationType extends Model { }) @ForeignKey(() => DelegationTypeModel) @ApiProperty() - delegation_type!: string + delegationType!: string @CreatedAt @ApiProperty() diff --git a/libs/auth-api-lib/src/lib/resources/models/api-scope.model.ts b/libs/auth-api-lib/src/lib/resources/models/api-scope.model.ts index 8b476db0417e..4ecc2ef2be44 100644 --- a/libs/auth-api-lib/src/lib/resources/models/api-scope.model.ts +++ b/libs/auth-api-lib/src/lib/resources/models/api-scope.model.ts @@ -2,6 +2,7 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' import { Optional } from 'sequelize' import { BelongsTo, + BelongsToMany, Column, CreatedAt, DataType, @@ -20,6 +21,8 @@ import { ApiScopeGroup } from './api-scope-group.model' import { ApiScopeUserAccess } from './api-scope-user-access.model' import { ApiScopeUserClaim } from './api-scope-user-claim.model' import { Domain } from './domain.model' +import { ApiScopeDelegationType } from './api-scope-delegation-type.model' +import { DelegationTypeModel } from '../../delegations/models/delegation-type.model' interface ModelAttributes { name: string @@ -254,10 +257,18 @@ export class ApiScope extends Model { @HasMany(() => ApiScopeUserAccess) apiScopeUserAccesses?: ApiScopeUserAccess[] + @HasMany(() => ApiScopeDelegationType) + supportedDelegationTypes?: ApiScopeDelegationType[] + @BelongsTo(() => Domain) @ApiPropertyOptional({ type: () => Domain }) domain?: Domain + @BelongsToMany(() => DelegationTypeModel, () => ApiScopeDelegationType) + delegationTypes?: Array< + DelegationTypeModel & { ApiScopeDelegationType: ApiScopeDelegationType } + > + toDTO(): ApiScopeDTO { return { name: this.name, @@ -277,6 +288,10 @@ export class ApiScope extends Model { emphasize: this.emphasize, domainName: this.domainName, isAccessControlled: this.isAccessControlled ?? undefined, + supportedDelegationTypes: + this.supportedDelegationTypes?.map( + ({ delegationType }) => delegationType, + ) ?? [], } } } From 9a4b0430a6cd1be22f530f31aa559a4fae0233cc Mon Sep 17 00:00:00 2001 From: Herdismaria Date: Wed, 22 May 2024 15:16:29 +0000 Subject: [PATCH 2/3] fix tsc --- libs/services/auth/testing/src/fixtures/fixture-factory.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/services/auth/testing/src/fixtures/fixture-factory.ts b/libs/services/auth/testing/src/fixtures/fixture-factory.ts index 6b701c2ca02d..563b36792762 100644 --- a/libs/services/auth/testing/src/fixtures/fixture-factory.ts +++ b/libs/services/auth/testing/src/fixtures/fixture-factory.ts @@ -600,6 +600,7 @@ export class FixtureFactory { providerId: delegationProvider.id, clients: [], provider: delegationProvider, + apiScopes: [], }, }, ) From b6989833cc5bad7642865274279d3ccc912e6ee3 Mon Sep 17 00:00:00 2001 From: Herdismaria Date: Wed, 22 May 2024 15:45:19 +0000 Subject: [PATCH 3/3] make clients and apiScopes optional when creating delegation type --- .../src/lib/delegations/models/delegation-type.model.ts | 4 ++-- libs/services/auth/testing/src/fixtures/fixture-factory.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/libs/auth-api-lib/src/lib/delegations/models/delegation-type.model.ts b/libs/auth-api-lib/src/lib/delegations/models/delegation-type.model.ts index 8c3f7c3f5ec2..d63c2f480387 100644 --- a/libs/auth-api-lib/src/lib/delegations/models/delegation-type.model.ts +++ b/libs/auth-api-lib/src/lib/delegations/models/delegation-type.model.ts @@ -62,10 +62,10 @@ export class DelegationTypeModel extends Model< description!: string @BelongsToMany(() => Client, () => ClientDelegationType) - clients!: Client[] + clients!: CreationOptional @BelongsToMany(() => ApiScope, () => ApiScopeDelegationType) - apiScopes!: ApiScope[] + apiScopes!: CreationOptional @CreatedAt readonly created!: CreationOptional diff --git a/libs/services/auth/testing/src/fixtures/fixture-factory.ts b/libs/services/auth/testing/src/fixtures/fixture-factory.ts index 563b36792762..2cdd5e6f2168 100644 --- a/libs/services/auth/testing/src/fixtures/fixture-factory.ts +++ b/libs/services/auth/testing/src/fixtures/fixture-factory.ts @@ -598,9 +598,7 @@ export class FixtureFactory { name, description, providerId: delegationProvider.id, - clients: [], provider: delegationProvider, - apiScopes: [], }, }, )