Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth-api-lib): add support for delegation types to api-scopes #14887

Merged
merged 4 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

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 @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions libs/auth-api-lib/src/lib/clients/clients.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -41,6 +42,7 @@ import { DelegationProviderModel } from '../delegations/models/delegation-provid
Domain,
ApiScope,
ApiScopeUserClaim,
ApiScopeDelegationType,
]),
TranslationModule,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -60,7 +62,10 @@ export class DelegationTypeModel extends Model<
description!: string

@BelongsToMany(() => Client, () => ClientDelegationType)
clients!: Client[]
clients!: CreationOptional<Client[]>

@BelongsToMany(() => ApiScope, () => ApiScopeDelegationType)
apiScopes!: CreationOptional<ApiScope[]>

@CreatedAt
readonly created!: CreationOptional<Date>
Expand Down
251 changes: 244 additions & 7 deletions libs/auth-api-lib/src/lib/resources/admin/admin-scope.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -50,6 +54,9 @@ export class AdminScopeService {
domainName: tenantId,
enabled: true,
},
include: [
{ model: ApiScopeDelegationType, as: 'supportedDelegationTypes' },
],
})

const translations =
Expand Down Expand Up @@ -84,6 +91,9 @@ export class AdminScopeService {
domainName: tenantId,
enabled: true,
},
include: [
{ model: ApiScopeDelegationType, as: 'supportedDelegationTypes' },
],
})

if (!apiScope) {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 }),
},
Expand All @@ -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)
})

Expand Down Expand Up @@ -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,
},
},
)
}
}
}
Loading
Loading