From e0ebfdae2d2e4d200ab52254a87f3fb3fc33556f Mon Sep 17 00:00:00 2001 From: Matteo Restelli Date: Mon, 15 Jul 2024 08:45:40 +0200 Subject: [PATCH] feat: support for OIDC providers in identitySource (#173) Adding support for OIDC providers in IdentitySource Construct --------- Signed-off-by: github-actions Co-authored-by: github-actions --- .projen/deps.json | 2 +- .projenrc.ts | 2 +- API.md | 343 ++++++++++++++++-- README.md | 111 +++++- package.json | 4 +- src/identity-source.ts | 267 ++++++++++++-- test/identity-source.test.ts | 680 ++++++++++++++++++++++++++++++++++- test/policy-store.test.ts | 27 ++ yarn.lock | 10 +- 9 files changed, 1375 insertions(+), 71 deletions(-) diff --git a/.projen/deps.json b/.projen/deps.json index 3332a2a..8afdaa3 100644 --- a/.projen/deps.json +++ b/.projen/deps.json @@ -104,7 +104,7 @@ }, { "name": "aws-cdk-lib", - "version": "^2.139.0", + "version": "^2.148.0", "type": "peer" }, { diff --git a/.projenrc.ts b/.projenrc.ts index 91de150..5929623 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -4,7 +4,7 @@ const project = new CdklabsConstructLibrary({ authorAddress: 'aws-avp-cdk-dev@amazon.com', description: 'L2 AWS CDK Constructs for Amazon Verified Permissions', keywords: ['cdk', 'aws-cdk', 'awscdk', 'aws', 'verified-permissions', 'authorization', 'verifiedpermissions'], - cdkVersion: '2.139.0', + cdkVersion: '2.148.0', defaultReleaseBranch: 'main', devDeps: ['cdklabs-projen-project-types'], bundledDeps: ['@cedar-policy/cedar-wasm@3.2.3'], diff --git a/API.md b/API.md index 96c6a15..c519aea 100644 --- a/API.md +++ b/API.md @@ -46,7 +46,9 @@ new IdentitySource(scope: Construct, id: string, props: IdentitySourceProps) | --- | --- | | toString | Returns a string representation of this construct. | | applyRemovalPolicy | Apply the given removal policy to this resource. | -| addUserPoolClient | Add a User Pool Client. | +| addAudience | Add an audience to the list. | +| addClientId | Add a clientId to the list The method can be called only when the Identity Source is configured with one of these configs: - Cognito auth provider - OIDC auth provider and ID Token Selection mode. | +| addUserPoolClient | Add a User Pool Client The method can be called only when the Identity Source is configured with Cognito auth provider. | --- @@ -80,13 +82,47 @@ account for data recovery and cleanup later (`RemovalPolicy.RETAIN`). --- +##### `addAudience` + +```typescript +public addAudience(audience: string): void +``` + +Add an audience to the list. + +The method can be called only when the Identity Source is configured with OIDC auth provider and Access Token Selection mode + +###### `audience`Required + +- *Type:* string + +the audience to be added. + +--- + +##### `addClientId` + +```typescript +public addClientId(clientId: string): void +``` + +Add a clientId to the list The method can be called only when the Identity Source is configured with one of these configs: - Cognito auth provider - OIDC auth provider and ID Token Selection mode. + +###### `clientId`Required + +- *Type:* string + +The clientId to be added. + +--- + ##### `addUserPoolClient` ```typescript public addUserPoolClient(userPoolClient: IUserPoolClient): void ``` -Add a User Pool Client. +Add a User Pool Client The method can be called only when the Identity Source is configured with Cognito auth provider. ###### `userPoolClient`Required @@ -233,13 +269,16 @@ The Identity Source identifier. | node | constructs.Node | The tree node. | | env | aws-cdk-lib.ResourceEnvironment | The environment this resource belongs to. | | stack | aws-cdk-lib.Stack | The stack in which this resource is defined. | +| audiencesOIDC | string[] | *No description.* | | clientIds | string[] | *No description.* | -| discoveryUrl | string | *No description.* | | identitySourceId | string | Identity Source identifier. | -| openIdIssuer | string | *No description.* | +| issuer | string | *No description.* | | policyStore | IPolicyStore | *No description.* | -| userPoolArn | string | *No description.* | | cognitoGroupEntityType | string | *No description.* | +| groupConfigGroupClaimOIDC | string | *No description.* | +| groupConfigGroupEntityTypeOIDC | string | *No description.* | +| principalIdClaimOIDC | string | *No description.* | +| userPoolArn | string | *No description.* | --- @@ -286,23 +325,23 @@ The stack in which this resource is defined. --- -##### `clientIds`Required +##### `audiencesOIDC`Required ```typescript -public readonly clientIds: string[]; +public readonly audiencesOIDC: string[]; ``` - *Type:* string[] --- -##### `discoveryUrl`Required +##### `clientIds`Required ```typescript -public readonly discoveryUrl: string; +public readonly clientIds: string[]; ``` -- *Type:* string +- *Type:* string[] --- @@ -318,10 +357,10 @@ Identity Source identifier. --- -##### `openIdIssuer`Required +##### `issuer`Required ```typescript -public readonly openIdIssuer: string; +public readonly issuer: string; ``` - *Type:* string @@ -338,20 +377,50 @@ public readonly policyStore: IPolicyStore; --- -##### `userPoolArn`Required +##### `cognitoGroupEntityType`Optional ```typescript -public readonly userPoolArn: string; +public readonly cognitoGroupEntityType: string; ``` - *Type:* string --- -##### `cognitoGroupEntityType`Optional +##### `groupConfigGroupClaimOIDC`Optional ```typescript -public readonly cognitoGroupEntityType: string; +public readonly groupConfigGroupClaimOIDC: string; +``` + +- *Type:* string + +--- + +##### `groupConfigGroupEntityTypeOIDC`Optional + +```typescript +public readonly groupConfigGroupEntityTypeOIDC: string; +``` + +- *Type:* string + +--- + +##### `principalIdClaimOIDC`Optional + +```typescript +public readonly principalIdClaimOIDC: string; +``` + +- *Type:* string + +--- + +##### `userPoolArn`Optional + +```typescript +public readonly userPoolArn: string; ``` - *Type:* string @@ -1749,7 +1818,7 @@ const identitySourceAttributes: IdentitySourceAttributes = { ... } | **Name** | **Type** | **Description** | | --- | --- | --- | -| identitySourceId | string | The identity Source identifier. | +| identitySourceId | string | *No description.* | --- @@ -1761,8 +1830,6 @@ public readonly identitySourceId: string; - *Type:* string -The identity Source identifier. - --- ### IdentitySourceConfiguration @@ -1780,21 +1847,36 @@ const identitySourceConfiguration: IdentitySourceConfiguration = { ... } | **Name** | **Type** | **Description** | | --- | --- | --- | | cognitoUserPoolConfiguration | CognitoUserPoolConfiguration | Cognito User Pool Configuration. | +| openIdConnectConfiguration | OpenIdConnectConfiguration | OpenID Connect Idp configuration. | --- -##### `cognitoUserPoolConfiguration`Required +##### `cognitoUserPoolConfiguration`Optional ```typescript public readonly cognitoUserPoolConfiguration: CognitoUserPoolConfiguration; ``` - *Type:* CognitoUserPoolConfiguration +- *Default:* no Cognito User Pool Config Cognito User Pool Configuration. --- +##### `openIdConnectConfiguration`Optional + +```typescript +public readonly openIdConnectConfiguration: OpenIdConnectConfiguration; +``` + +- *Type:* OpenIdConnectConfiguration +- *Default:* no OpenID Provider config + +OpenID Connect Idp configuration. + +--- + ### IdentitySourceProps #### Initializer @@ -1852,6 +1934,227 @@ Principal entity type. --- +### OpenIdConnectAccessTokenConfiguration + +#### Initializer + +```typescript +import { OpenIdConnectAccessTokenConfiguration } from '@cdklabs/cdk-verified-permissions' + +const openIdConnectAccessTokenConfiguration: OpenIdConnectAccessTokenConfiguration = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| audiences | string[] | The access token aud claim values that you want to accept in your policy store. | +| principalIdClaim | string | The claim that determines the principal in OIDC access tokens. | + +--- + +##### `audiences`Optional + +```typescript +public readonly audiences: string[]; +``` + +- *Type:* string[] +- *Default:* no audiences + +The access token aud claim values that you want to accept in your policy store. + +--- + +##### `principalIdClaim`Optional + +```typescript +public readonly principalIdClaim: string; +``` + +- *Type:* string +- *Default:* no principal claim + +The claim that determines the principal in OIDC access tokens. + +--- + +### OpenIdConnectConfiguration + +#### Initializer + +```typescript +import { OpenIdConnectConfiguration } from '@cdklabs/cdk-verified-permissions' + +const openIdConnectConfiguration: OpenIdConnectConfiguration = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| issuer | string | The issuer URL of an OIDC identity provider. | +| accessTokenOnly | OpenIdConnectAccessTokenConfiguration | The configuration for processing access tokens from your OIDC identity provider Exactly one between accessTokenOnly and identityTokenOnly must be defined. | +| entityIdPrefix | string | A descriptive string that you want to prefix to user entities from your OIDC identity provider. | +| groupConfiguration | OpenIdConnectGroupConfiguration | The claim in OIDC identity provider tokens that indicates a user's group membership, and the entity type that you want to map it to. | +| identityTokenOnly | OpenIdConnectIdentityTokenConfiguration | The configuration for processing identity (ID) tokens from your OIDC identity provider Exactly one between accessTokenOnly and identityTokenOnly must be defined. | + +--- + +##### `issuer`Required + +```typescript +public readonly issuer: string; +``` + +- *Type:* string + +The issuer URL of an OIDC identity provider. + +This URL must have an OIDC discovery endpoint at the path .well-known/openid-configuration + +--- + +##### `accessTokenOnly`Optional + +```typescript +public readonly accessTokenOnly: OpenIdConnectAccessTokenConfiguration; +``` + +- *Type:* OpenIdConnectAccessTokenConfiguration +- *Default:* no Access Token Config + +The configuration for processing access tokens from your OIDC identity provider Exactly one between accessTokenOnly and identityTokenOnly must be defined. + +--- + +##### `entityIdPrefix`Optional + +```typescript +public readonly entityIdPrefix: string; +``` + +- *Type:* string +- *Default:* no Entity ID Prefix + +A descriptive string that you want to prefix to user entities from your OIDC identity provider. + +--- + +##### `groupConfiguration`Optional + +```typescript +public readonly groupConfiguration: OpenIdConnectGroupConfiguration; +``` + +- *Type:* OpenIdConnectGroupConfiguration +- *Default:* no Group Config + +The claim in OIDC identity provider tokens that indicates a user's group membership, and the entity type that you want to map it to. + +--- + +##### `identityTokenOnly`Optional + +```typescript +public readonly identityTokenOnly: OpenIdConnectIdentityTokenConfiguration; +``` + +- *Type:* OpenIdConnectIdentityTokenConfiguration +- *Default:* no ID Token Config + +The configuration for processing identity (ID) tokens from your OIDC identity provider Exactly one between accessTokenOnly and identityTokenOnly must be defined. + +--- + +### OpenIdConnectGroupConfiguration + +#### Initializer + +```typescript +import { OpenIdConnectGroupConfiguration } from '@cdklabs/cdk-verified-permissions' + +const openIdConnectGroupConfiguration: OpenIdConnectGroupConfiguration = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| groupClaim | string | The token claim that you want Verified Permissions to interpret as group membership. | +| groupEntityType | string | The policy store entity type that you want to map your users' group claim to. | + +--- + +##### `groupClaim`Required + +```typescript +public readonly groupClaim: string; +``` + +- *Type:* string + +The token claim that you want Verified Permissions to interpret as group membership. + +--- + +##### `groupEntityType`Required + +```typescript +public readonly groupEntityType: string; +``` + +- *Type:* string + +The policy store entity type that you want to map your users' group claim to. + +--- + +### OpenIdConnectIdentityTokenConfiguration + +#### Initializer + +```typescript +import { OpenIdConnectIdentityTokenConfiguration } from '@cdklabs/cdk-verified-permissions' + +const openIdConnectIdentityTokenConfiguration: OpenIdConnectIdentityTokenConfiguration = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| clientIds | string[] | The ID token audience, or client ID, claim values that you want to accept in your policy store from an OIDC identity provider. | +| principalIdClaim | string | The claim that determines the principal in OIDC access tokens. | + +--- + +##### `clientIds`Optional + +```typescript +public readonly clientIds: string[]; +``` + +- *Type:* string[] +- *Default:* no client IDs + +The ID token audience, or client ID, claim values that you want to accept in your policy store from an OIDC identity provider. + +--- + +##### `principalIdClaim`Optional + +```typescript +public readonly principalIdClaim: string; +``` + +- *Type:* string +- *Default:* no principal claim + +The claim that determines the principal in OIDC access tokens. + +--- + ### PolicyAttributes #### Initializer diff --git a/README.md b/README.md index 45078e9..e46f51f 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ const policyStore = new PolicyStore(scope, "PolicyStore", { ## Schemas -If you want to have type safety when defining a schema, you can accomplish this in typescript. Simply use the `Schema` type exported by the `@cedar-policy/cedar-wasm`. +If you want to have type safety when defining a schema, you can accomplish this **only** in typescript. Simply use the `Schema` type exported by the `@cedar-policy/cedar-wasm`. You can also generate a simple schema from a swagger file using the static function `schemaFromOpenApiSpec` in the PolicyStore construct. This functionality replicates what you can find in the AWS Verified Permissions console. @@ -85,7 +85,7 @@ const policyStore = new PolicyStore(scope, "PolicyStore", { ## Identity Source -Define Identity Source with required properties: +Define Identity Source with Cognito Configuration and required properties: ```ts const userPool = new UserPool(scope, "UserPool"); // Creating a new Cognito UserPool @@ -125,7 +125,7 @@ new IdentitySource(scope, "IdentitySource", { }); ``` -Define Identity Source with all the properties: +Define Identity Source with Cognito Configuration and all properties: ```ts const validationSettingsStrict = { @@ -171,6 +171,111 @@ new IdentitySource(scope, "IdentitySource", { }); ``` +Define Identity Source with OIDC Configuration and Access Token selection config: +```ts +const validationSettingsStrict = { + mode: ValidationSettingsMode.STRICT, +}; +const cedarJsonSchema = { + PhotoApp: { + entityTypes: { + User: {}, + Photo: {}, + }, + actions: { + viewPhoto: { + appliesTo: { + principalTypes: ["User"], + resourceTypes: ["Photo"], + }, + }, + }, + }, +}; +const cedarSchema = { + cedarJson: JSON.stringify(cedarJsonSchema), +}; +const policyStore = new PolicyStore(scope, "PolicyStore", { + schema: cedarSchema, + validationSettings: validationSettingsStrict, +}); +const issuer = 'https://iamanidp.com'; +const principalIdClaim = 'sub'; +const entityIdPrefix = 'prefix'; +const groupClaim = 'group'; +const groupEntityType = 'GroupType'; +new IdentitySource(scope, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: issuer, + entityIdPrefix: entityIdPrefix, + groupConfiguration: { + groupClaim: groupClaim, + groupEntityType: groupEntityType, + }, + accessTokenOnly: { + audiences: ['testAudience'], + principalIdClaim: principalIdClaim, + }, + }, + }, + policyStore: policyStore, + principalEntityType: 'TestType', +}); +``` + +Define Identity Source with OIDC Configuration and Identity Token selection config: +```ts +const validationSettingsStrict = { + mode: ValidationSettingsMode.STRICT, +}; +const cedarJsonSchema = { + PhotoApp: { + entityTypes: { + User: {}, + Photo: {}, + }, + actions: { + viewPhoto: { + appliesTo: { + principalTypes: ["User"], + resourceTypes: ["Photo"], + }, + }, + }, + }, +}; +const cedarSchema = { + cedarJson: JSON.stringify(cedarJsonSchema), +}; +const policyStore = new PolicyStore(scope, "PolicyStore", { + schema: cedarSchema, + validationSettings: validationSettingsStrict, +}); +const issuer = 'https://iamanidp.com'; +const entityIdPrefix = 'prefix'; +const groupClaim = 'group'; +const groupEntityType = 'UserGroup'; +const principalIdClaim = 'sub'; +new IdentitySource(scope, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: issuer, + entityIdPrefix: entityIdPrefix, + groupConfiguration: { + groupClaim: groupClaim, + groupEntityType: groupEntityType, + }, + identityTokenOnly: { + clientIds: [], + principalIdClaim: principalIdClaim, + }, + }, + }, + policyStore: policyStore, +}); +``` + ## Policy Load all the `.cedar` files in a given folder and define Policy objects for each of them. All policies will be associated with the same policy store. diff --git a/package.json b/package.json index b46764c..8ecf730 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@types/node": "^18", "@typescript-eslint/eslint-plugin": "^7", "@typescript-eslint/parser": "^7", - "aws-cdk-lib": "2.139.0", + "aws-cdk-lib": "2.148.0", "cdklabs-projen-project-types": "^0.1.199", "constructs": "10.0.5", "eslint": "^8", @@ -69,7 +69,7 @@ "typescript": "^5.5.3" }, "peerDependencies": { - "aws-cdk-lib": "^2.139.0", + "aws-cdk-lib": "^2.148.0", "constructs": "^10.0.5" }, "dependencies": { diff --git a/src/identity-source.ts b/src/identity-source.ts index ee9d5b1..d85f37a 100644 --- a/src/identity-source.ts +++ b/src/identity-source.ts @@ -4,6 +4,11 @@ import { IResource, Lazy, Resource } from 'aws-cdk-lib/core'; import { Construct } from 'constructs'; import { IPolicyStore } from './policy-store'; +enum ConfigurationMode { + COGNITO = 'COGNITO', + OIDC_ACCESS_TOKEN = 'OIDC_ACCESS_TOKEN', + OIDC_ID_TOKEN = 'OIDC_ID_TOKEN' +} export interface CognitoGroupConfiguration { /** @@ -35,38 +40,112 @@ export interface CognitoUserPoolConfiguration { readonly userPool: IUserPool; } -export interface IdentitySourceConfiguration { +export interface OpenIdConnectGroupConfiguration { /** - * Cognito User Pool Configuration. + * The token claim that you want Verified Permissions to interpret as group membership + * + */ + readonly groupClaim: string; + + /** + * The policy store entity type that you want to map your users' group claim to * - * @attribute */ - readonly cognitoUserPoolConfiguration: CognitoUserPoolConfiguration; + readonly groupEntityType: string; } -export interface IIdentitySource extends IResource { +export interface OpenIdConnectAccessTokenConfiguration { /** - * Identity Source identifier. + * The access token aud claim values that you want to accept in your policy store + * + * @default - no audiences * - * @attribute */ - readonly identitySourceId: string; + readonly audiences?: string[]; + + /** + * The claim that determines the principal in OIDC access tokens + * + * @default - no principal claim + */ + readonly principalIdClaim?: string; } -abstract class IdentitySourceBase extends Resource implements IIdentitySource { - abstract readonly identitySourceId: string; +export interface OpenIdConnectIdentityTokenConfiguration { + /** + * The ID token audience, or client ID, claim values that you want to accept in your policy store from an OIDC identity provider + * + * @default - no client IDs + * + */ + readonly clientIds?: string[]; + + /** + * The claim that determines the principal in OIDC access tokens + * + * @default - no principal claim + */ + readonly principalIdClaim?: string; } -export interface IdentitySourceAttributes { +export interface OpenIdConnectConfiguration { /** - * The identity Source identifier + * A descriptive string that you want to prefix to user entities from your OIDC identity provider * - * @attribute + * @default - no Entity ID Prefix */ - readonly identitySourceId: string; + readonly entityIdPrefix?: string; + + /** + * The claim in OIDC identity provider tokens that indicates a user's group membership, and the entity type that you want to map it to + * + * @default - no Group Config + */ + readonly groupConfiguration?: OpenIdConnectGroupConfiguration; + + /** + * The issuer URL of an OIDC identity provider. This URL must have an OIDC discovery endpoint at the path .well-known/openid-configuration + * + */ + readonly issuer: string; + + /** + * The configuration for processing access tokens from your OIDC identity provider + * Exactly one between accessTokenOnly and identityTokenOnly must be defined + * + * @default - no Access Token Config + */ + readonly accessTokenOnly?: OpenIdConnectAccessTokenConfiguration; + + /** + * The configuration for processing identity (ID) tokens from your OIDC identity provider + * Exactly one between accessTokenOnly and identityTokenOnly must be defined + * + * @default - no ID Token Config + */ + readonly identityTokenOnly?: OpenIdConnectIdentityTokenConfiguration; +} + +export interface IdentitySourceConfiguration { + /** + * Cognito User Pool Configuration. + * + * @default - no Cognito User Pool Config + * + */ + readonly cognitoUserPoolConfiguration?: CognitoUserPoolConfiguration; + + /** + * OpenID Connect Idp configuration + * + * @default - no OpenID Provider config + * + */ + readonly openIdConnectConfiguration?: OpenIdConnectConfiguration; } + export interface IdentitySourceProps { /** * Identity Source configuration. @@ -87,6 +166,24 @@ export interface IdentitySourceProps { readonly principalEntityType?: string; } +export interface IIdentitySource extends IResource { + /** + * Identity Source identifier. + * + * @attribute + */ + readonly identitySourceId: string; +} + +abstract class IdentitySourceBase extends Resource implements IIdentitySource { + abstract readonly identitySourceId: string; +} + +export interface IdentitySourceAttributes { + readonly identitySourceId: string; +} + + export class IdentitySource extends IdentitySourceBase { /** * Creates Identity Source from its attributes @@ -132,52 +229,152 @@ export class IdentitySource extends IdentitySourceBase { identitySourceId, }); } - + private readonly configurationMode: ConfigurationMode; private readonly identitySource: CfnIdentitySource; readonly clientIds: string[]; - readonly discoveryUrl: string; readonly identitySourceId: string; - readonly openIdIssuer: string; - readonly userPoolArn: string; + readonly issuer: string; + readonly userPoolArn?: string; readonly cognitoGroupEntityType?: string; readonly policyStore: IPolicyStore; + readonly audiencesOIDC: string[]; + readonly principalIdClaimOIDC?: string; + readonly groupConfigGroupClaimOIDC?: string; + readonly groupConfigGroupEntityTypeOIDC?: string; constructor(scope: Construct, id: string, props: IdentitySourceProps) { super(scope, id); - this.clientIds = - props.configuration.cognitoUserPoolConfiguration.clientIds ?? []; - this.userPoolArn = - props.configuration.cognitoUserPoolConfiguration.userPool.userPoolArn; - const cognitoGroupConfiguration = props.configuration.cognitoUserPoolConfiguration.groupConfiguration?.groupEntityType - ? { - groupEntityType: props.configuration.cognitoUserPoolConfiguration.groupConfiguration.groupEntityType, - } - : undefined; - this.identitySource = new CfnIdentitySource(this, id, { - configuration: { + if (props.configuration.cognitoUserPoolConfiguration && props.configuration.openIdConnectConfiguration) { + throw new Error('Only one between cognitoUserPoolConfiguration or openIdConnectConfiguration must be defined'); + } + + let cfnConfiguration: CfnIdentitySource.IdentitySourceConfigurationProperty; + let issuer: string; + if (props.configuration.cognitoUserPoolConfiguration) { + this.clientIds = props.configuration.cognitoUserPoolConfiguration.clientIds ?? []; + this.audiencesOIDC = []; + const cognitoGroupConfiguration = props.configuration.cognitoUserPoolConfiguration.groupConfiguration?.groupEntityType + ? { + groupEntityType: props.configuration.cognitoUserPoolConfiguration.groupConfiguration.groupEntityType, + } + : undefined; + cfnConfiguration = { cognitoUserPoolConfiguration: { clientIds: Lazy.list({ produce: () => this.clientIds }), - userPoolArn: this.userPoolArn, + userPoolArn: props.configuration.cognitoUserPoolConfiguration.userPool.userPoolArn, groupConfiguration: cognitoGroupConfiguration, }, - }, + }; + this.cognitoGroupEntityType = cognitoGroupConfiguration?.groupEntityType; + issuer = 'COGNITO'; + this.configurationMode = ConfigurationMode.COGNITO; + } else if (props.configuration.openIdConnectConfiguration) { + + if (props.configuration.openIdConnectConfiguration.accessTokenOnly && + props.configuration.openIdConnectConfiguration.identityTokenOnly) { + throw new Error('Exactly one token selection method between accessTokenOnly and identityTokenOnly must be defined'); + } + + let tokenSelection: CfnIdentitySource.OpenIdConnectTokenSelectionProperty; + if (props.configuration.openIdConnectConfiguration.accessTokenOnly) { + if (!props.configuration.openIdConnectConfiguration.accessTokenOnly.audiences || + props.configuration.openIdConnectConfiguration.accessTokenOnly.audiences.length == 0) { + throw new Error('At least one audience is expected in OIDC Access token selection mode'); + } + this.clientIds = []; + this.audiencesOIDC = props.configuration.openIdConnectConfiguration.accessTokenOnly.audiences; + tokenSelection = { + accessTokenOnly: { + audiences: Lazy.list({ produce: () => this.audiencesOIDC }), + principalIdClaim: props.configuration.openIdConnectConfiguration.accessTokenOnly.principalIdClaim, + }, + }; + this.principalIdClaimOIDC = props.configuration.openIdConnectConfiguration.accessTokenOnly.principalIdClaim; + this.configurationMode = ConfigurationMode.OIDC_ACCESS_TOKEN; + } else if (props.configuration.openIdConnectConfiguration.identityTokenOnly) { + this.clientIds = props.configuration.openIdConnectConfiguration.identityTokenOnly.clientIds ?? []; + this.audiencesOIDC = []; + tokenSelection = { + identityTokenOnly: { + clientIds: Lazy.list({ produce: () => this.clientIds }), + principalIdClaim: props.configuration.openIdConnectConfiguration.identityTokenOnly.principalIdClaim, + }, + }; + this.principalIdClaimOIDC = props.configuration.openIdConnectConfiguration.identityTokenOnly.principalIdClaim; + this.configurationMode = ConfigurationMode.OIDC_ID_TOKEN; + } else { + throw new Error('One token selection method between accessTokenOnly and identityTokenOnly must be defined'); + } + cfnConfiguration = { + openIdConnectConfiguration: { + issuer: props.configuration.openIdConnectConfiguration.issuer, + entityIdPrefix: props.configuration.openIdConnectConfiguration.entityIdPrefix, + groupConfiguration: props.configuration.openIdConnectConfiguration.groupConfiguration ? { + groupClaim: props.configuration.openIdConnectConfiguration.groupConfiguration.groupClaim, + groupEntityType: props.configuration.openIdConnectConfiguration.groupConfiguration.groupEntityType, + } : undefined, + tokenSelection: tokenSelection, + }, + }; + this.groupConfigGroupClaimOIDC = props.configuration.openIdConnectConfiguration.groupConfiguration?.groupClaim; + this.groupConfigGroupEntityTypeOIDC = props.configuration.openIdConnectConfiguration.groupConfiguration?.groupEntityType; + issuer = props.configuration.openIdConnectConfiguration.issuer; + } else { + throw new Error('One Identity provider configuration between cognitoUserPoolConfiguration and openIdConnectConfiguration must be defined'); + } + this.identitySource = new CfnIdentitySource(this, id, { + configuration: cfnConfiguration, policyStoreId: props.policyStore.policyStoreId, principalEntityType: props.principalEntityType, }); - this.discoveryUrl = this.identitySource.attrDetailsDiscoveryUrl; + + this.userPoolArn = props.configuration.cognitoUserPoolConfiguration?.userPool.userPoolArn || undefined; this.identitySourceId = this.identitySource.attrIdentitySourceId; - this.openIdIssuer = this.identitySource.attrDetailsOpenIdIssuer; + this.issuer = issuer; this.policyStore = props.policyStore; - this.cognitoGroupEntityType = cognitoGroupConfiguration?.groupEntityType; + } /** * Add a User Pool Client + * The method can be called only when the Identity Source is configured with Cognito auth provider * * @param userPoolClient The User Pool Client Construct. */ public addUserPoolClient(userPoolClient: IUserPoolClient): void { - this.clientIds.push(userPoolClient.userPoolClientId); + if (this.configurationMode != ConfigurationMode.COGNITO) { + throw new Error('Cannot add User Pool Client when IdentitySource auth provider is not Cognito'); + } + this.addClientId(userPoolClient.userPoolClientId); } + + /** + * Add a clientId to the list + * The method can be called only when the Identity Source is configured with one of these configs: + * - Cognito auth provider + * - OIDC auth provider and ID Token Selection mode + * + * @param clientId The clientId to be added + */ + public addClientId(clientId: string) { + if (this.configurationMode != ConfigurationMode.COGNITO && this.configurationMode != ConfigurationMode.OIDC_ID_TOKEN) { + throw new Error('Adding a Client ID is only supported for the auth providers Cognito or OIDC with configured with ID Token'); + } + this.clientIds.push(clientId); + } + + /** + * Add an audience to the list. + * The method can be called only when the Identity Source is configured with OIDC auth provider and Access Token Selection mode + * + * @param audience the audience to be added + */ + public addAudience(audience: string) { + if (this.configurationMode != ConfigurationMode.OIDC_ACCESS_TOKEN) { + throw new Error('Cannot add audience when IdentitySource auth provider is not OIDC with Access Token'); + } + this.audiencesOIDC.push(audience); + } + } diff --git a/test/identity-source.test.ts b/test/identity-source.test.ts index b9ece42..1328e1f 100644 --- a/test/identity-source.test.ts +++ b/test/identity-source.test.ts @@ -7,9 +7,9 @@ import { IdentitySource } from '../src/identity-source'; import { PolicyStore, ValidationSettingsMode } from '../src/policy-store'; -describe('Identity Source creation', () => { +describe('Identity Source creation with Cognito config', () => { - test('Creating Identity Source with required properties', () => { + test('Creating Identity Source with Cognito config', () => { // GIVEN const stack = new Stack(undefined, 'Stack'); @@ -48,7 +48,7 @@ describe('Identity Source creation', () => { }); }); - test('Creating Identity Source with all properties', () => { + test('Creating Identity Source with Cognito config and all the properties', () => { // GIVEN const stack = new Stack(undefined, 'Stack'); @@ -120,7 +120,7 @@ describe('Identity Source reference existing Identity Source', () => { }); describe('User Pool Client addition', () => { - test('Adding a User Pool Client', () => { + test('Adding a User Pool Client to Identity Source configured with Cognito', () => { // GIVEN const stack = new Stack(undefined, 'Stack'); @@ -166,4 +166,676 @@ describe('User Pool Client addition', () => { }, }); }); + + test('Adding a User Pool Client to Identity Source configured with OIDC, should throw', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const userPool = new UserPool(stack, 'UserPool'); + const userPoolClient = new UserPoolClient(stack, 'UserPoolClient', { userPool: userPool }); + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + const identitySource = new IdentitySource(stack, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: 'https://iamanidp.com', + accessTokenOnly: { + audiences: ['aud1'], + principalIdClaim: 'sub', + }, + }, + }, + policyStore: policyStore, + principalEntityType: 'TestType', + }); + + // THEN + expect(() => { + identitySource.addUserPoolClient(userPoolClient); + }).toThrow('Cannot add User Pool Client when IdentitySource auth provider is not Cognito'); + + }); +}); + +describe('Client addition to OIDC configured Identity Source', () => { + test('Adding a Client to Identity Source configured with OIDC and token selection = access token - should throw', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + const identitySource = new IdentitySource(stack, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: 'https://iamanidp.com', + accessTokenOnly: { + audiences: ['aud1'], + principalIdClaim: 'sub', + }, + }, + }, + policyStore: policyStore, + principalEntityType: 'TestType', + }); + + // THEN + expect(() => { + identitySource.addClientId('testClientId'); + }).toThrow('Adding a Client ID is only supported for the auth providers Cognito or OIDC with configured with ID Token'); + }); + + test('Adding a Client to Identity Source configured with OIDC and token selection = identity token', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + const policyStoreLogicalId = getResourceLogicalId(policyStore, CfnPolicyStore); + const issuer = 'https://iamanidp.com'; + const entityIdPrefix = 'prefix'; + const groupClaim = 'group'; + const groupEntityType = 'UserGroup'; + const principalIdClaim = 'sub'; + const identitySource = new IdentitySource(stack, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: issuer, + entityIdPrefix: entityIdPrefix, + groupConfiguration: { + groupClaim: groupClaim, + groupEntityType: groupEntityType, + }, + identityTokenOnly: { + clientIds: [], + principalIdClaim: principalIdClaim, + }, + }, + }, + policyStore: policyStore, + }); + + const clientToBeAdded = 'client1'; + identitySource.addClientId(clientToBeAdded); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::VerifiedPermissions::IdentitySource', { + Configuration: { + OpenIdConnectConfiguration: { + Issuer: issuer, + EntityIdPrefix: entityIdPrefix, + GroupConfiguration: { + GroupClaim: groupClaim, + GroupEntityType: groupEntityType, + }, + TokenSelection: { + IdentityTokenOnly: { + ClientIds: [clientToBeAdded], + PrincipalIdClaim: principalIdClaim, + }, + }, + }, + }, + PolicyStoreId: { + 'Fn::GetAtt': [policyStoreLogicalId, 'PolicyStoreId'], + }, + }); + + }); + + test('Adding a Client to Identity Source configured with Cognito', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const userPool = new UserPool(stack, 'UserPool'); + const userPoolClient = new UserPoolClient(stack, 'UserPoolClient', { userPool: userPool }); + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + const policyStoreLogicalId = getResourceLogicalId(policyStore, CfnPolicyStore); + const identitySource = new IdentitySource(stack, 'IdentitySource', { + configuration: { + cognitoUserPoolConfiguration: { + userPool: userPool, + }, + }, + policyStore: policyStore, + }); + + identitySource.addClientId(userPoolClient.userPoolClientId); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::VerifiedPermissions::IdentitySource', { + Configuration: { + CognitoUserPoolConfiguration: { + ClientIds: [ + Match.objectEquals({ + Ref: Match.stringLikeRegexp('UserPoolClient*'), + }), + ], + UserPoolArn: { + 'Fn::GetAtt': [ + getResourceLogicalId(userPool, CfnUserPool), + 'Arn', + ], + }, + }, + }, + PolicyStoreId: { + 'Fn::GetAtt': [policyStoreLogicalId, 'PolicyStoreId'], + }, + }); + }); + +}); + +describe('Audience addition to OIDC configured Identity Source', () => { + test('Adding an Audience to Identity Source configured with OIDC and token selection = id token - should throw', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + const identitySource = new IdentitySource(stack, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: 'https://iamanidp.com', + identityTokenOnly: { + clientIds: ['client1'], + principalIdClaim: 'sub', + }, + }, + }, + policyStore: policyStore, + principalEntityType: 'TestType', + }); + + // THEN + expect(() => { + identitySource.addAudience('TestAudience'); + }).toThrow('Cannot add audience when IdentitySource auth provider is not OIDC with Access Token'); + }); + test('Adding a Client to Identity Source configured with Cognito - should throw', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const userPool = new UserPool(stack, 'UserPool'); + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + const identitySource = new IdentitySource(stack, 'IdentitySource', { + configuration: { + cognitoUserPoolConfiguration: { + userPool: userPool, + }, + }, + policyStore: policyStore, + }); + + // THEN + expect(() => { + identitySource.addAudience('TestAudience'); + }).toThrow('Cannot add audience when IdentitySource auth provider is not OIDC with Access Token'); + + }); + test('Adding an Audience to Identity Source configured with OIDC and token selection = access token', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + const policyStoreLogicalId = getResourceLogicalId(policyStore, CfnPolicyStore); + const issuer = 'https://iamanidp.com'; + const principalIdClaim = 'sub'; + const entityIdPrefix = 'prefix'; + const groupClaim = 'group'; + const groupEntityType = 'GroupType'; + const existingAudience = 'audience'; + const identitySource = new IdentitySource(stack, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: issuer, + entityIdPrefix: entityIdPrefix, + groupConfiguration: { + groupClaim: groupClaim, + groupEntityType: groupEntityType, + }, + accessTokenOnly: { + audiences: [existingAudience], + principalIdClaim: principalIdClaim, + }, + }, + }, + policyStore: policyStore, + principalEntityType: 'TestType', + }); + + const audienceToBeAdded = 'TestAudience'; + identitySource.addAudience(audienceToBeAdded); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::VerifiedPermissions::IdentitySource', { + Configuration: { + OpenIdConnectConfiguration: { + Issuer: issuer, + EntityIdPrefix: entityIdPrefix, + GroupConfiguration: { + GroupClaim: groupClaim, + GroupEntityType: groupEntityType, + }, + TokenSelection: { + AccessTokenOnly: { + Audiences: [existingAudience, audienceToBeAdded], + PrincipalIdClaim: principalIdClaim, + }, + }, + }, + }, + PolicyStoreId: { + 'Fn::GetAtt': [policyStoreLogicalId, 'PolicyStoreId'], + }, + }); + }); }); + +describe('Identity Source creation with OIDC config', () => { + test('Creating Identity Source with OIDC and token selection = access token', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + const policyStoreLogicalId = getResourceLogicalId(policyStore, CfnPolicyStore); + const issuer = 'https://iamanidp.com'; + const principalIdClaim = 'sub'; + const entityIdPrefix = 'prefix'; + const groupClaim = 'group'; + const groupEntityType = 'GroupType'; + const audience = 'testAudience'; + new IdentitySource(stack, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: issuer, + entityIdPrefix: entityIdPrefix, + groupConfiguration: { + groupClaim: groupClaim, + groupEntityType: groupEntityType, + }, + accessTokenOnly: { + audiences: [audience], + principalIdClaim: principalIdClaim, + }, + }, + }, + policyStore: policyStore, + principalEntityType: 'TestType', + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::VerifiedPermissions::IdentitySource', { + Configuration: { + OpenIdConnectConfiguration: { + Issuer: issuer, + EntityIdPrefix: entityIdPrefix, + GroupConfiguration: { + GroupClaim: groupClaim, + GroupEntityType: groupEntityType, + }, + TokenSelection: { + AccessTokenOnly: { + Audiences: [audience], + PrincipalIdClaim: principalIdClaim, + }, + }, + }, + }, + PolicyStoreId: { + 'Fn::GetAtt': [policyStoreLogicalId, 'PolicyStoreId'], + }, + }); + }); + + test('Creating Identity Source with OIDC and token selection = access token and audiences not set - should throw', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + const issuer = 'https://iamanidp.com'; + const principalIdClaim = 'sub'; + const entityIdPrefix = 'prefix'; + const groupClaim = 'group'; + const groupEntityType = 'GroupType'; + + // THEN + expect(() => { + new IdentitySource(stack, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: issuer, + entityIdPrefix: entityIdPrefix, + groupConfiguration: { + groupClaim: groupClaim, + groupEntityType: groupEntityType, + }, + accessTokenOnly: { + principalIdClaim: principalIdClaim, + }, + }, + }, + policyStore: policyStore, + principalEntityType: 'TestType', + }); + }).toThrow('At least one audience is expected in OIDC Access token selection mode'); + }); + + + test('Creating Identity Source with OIDC and token selection = access token and audiences empty - should throw', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + const issuer = 'https://iamanidp.com'; + const principalIdClaim = 'sub'; + const entityIdPrefix = 'prefix'; + const groupClaim = 'group'; + const groupEntityType = 'GroupType'; + + // THEN + expect(() => { + new IdentitySource(stack, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: issuer, + entityIdPrefix: entityIdPrefix, + groupConfiguration: { + groupClaim: groupClaim, + groupEntityType: groupEntityType, + }, + accessTokenOnly: { + principalIdClaim: principalIdClaim, + audiences: [], + }, + }, + }, + policyStore: policyStore, + principalEntityType: 'TestType', + }); + }).toThrow('At least one audience is expected in OIDC Access token selection mode'); + }); + + test('Creating Identity Source with OIDC and token selection = identity token', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + const policyStoreLogicalId = getResourceLogicalId(policyStore, CfnPolicyStore); + const issuer = 'https://iamanidp.com'; + const entityIdPrefix = 'prefix'; + const groupClaim = 'group'; + const groupEntityType = 'UserGroup'; + const principalIdClaim = 'sub'; + new IdentitySource(stack, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: issuer, + entityIdPrefix: entityIdPrefix, + groupConfiguration: { + groupClaim: groupClaim, + groupEntityType: groupEntityType, + }, + identityTokenOnly: { + clientIds: [], + principalIdClaim: principalIdClaim, + }, + }, + }, + policyStore: policyStore, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::VerifiedPermissions::IdentitySource', { + Configuration: { + OpenIdConnectConfiguration: { + Issuer: issuer, + EntityIdPrefix: entityIdPrefix, + GroupConfiguration: { + GroupClaim: groupClaim, + GroupEntityType: groupEntityType, + }, + TokenSelection: { + IdentityTokenOnly: { + ClientIds: [], + PrincipalIdClaim: principalIdClaim, + }, + }, + }, + }, + PolicyStoreId: { + 'Fn::GetAtt': [policyStoreLogicalId, 'PolicyStoreId'], + }, + }); + }); + + test('Creating Identity Source with OIDC and token selection = identity token and client ids not set', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + const policyStoreLogicalId = getResourceLogicalId(policyStore, CfnPolicyStore); + const issuer = 'https://iamanidp.com'; + const entityIdPrefix = 'prefix'; + const groupClaim = 'group'; + const groupEntityType = 'UserGroup'; + const principalIdClaim = 'sub'; + new IdentitySource(stack, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: issuer, + entityIdPrefix: entityIdPrefix, + groupConfiguration: { + groupClaim: groupClaim, + groupEntityType: groupEntityType, + }, + identityTokenOnly: { + principalIdClaim: principalIdClaim, + }, + }, + }, + policyStore: policyStore, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::VerifiedPermissions::IdentitySource', { + Configuration: { + OpenIdConnectConfiguration: { + Issuer: issuer, + EntityIdPrefix: entityIdPrefix, + GroupConfiguration: { + GroupClaim: groupClaim, + GroupEntityType: groupEntityType, + }, + TokenSelection: { + IdentityTokenOnly: { + ClientIds: [], + PrincipalIdClaim: principalIdClaim, + }, + }, + }, + }, + PolicyStoreId: { + 'Fn::GetAtt': [policyStoreLogicalId, 'PolicyStoreId'], + }, + }); + }); + + test('Creating Identity Source with OIDC and token selection = access token and identity token - should throw', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + expect(() => { + new IdentitySource(stack, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: 'https://test.com', + entityIdPrefix: 'prefix', + groupConfiguration: { + groupClaim: 'group', + groupEntityType: 'GroupType', + }, + accessTokenOnly: { + audiences: ['testAudience'], + principalIdClaim: 'sub', + }, + identityTokenOnly: { + clientIds: [], + principalIdClaim: 'sub', + }, + }, + }, + policyStore: policyStore, + principalEntityType: 'TestType', + }); + }).toThrow('Exactly one token selection method between accessTokenOnly and identityTokenOnly must be defined'); + }); + + test('Creating Identity Source with OIDC and without token selection - should throw', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + expect(() => { + new IdentitySource(stack, 'IdentitySource', { + configuration: { + openIdConnectConfiguration: { + issuer: 'https://test.com', + entityIdPrefix: 'prefix', + groupConfiguration: { + groupClaim: 'group', + groupEntityType: 'GroupType', + }, + }, + }, + policyStore: policyStore, + principalEntityType: 'TestType', + }); + }).toThrow('One token selection method between accessTokenOnly and identityTokenOnly must be defined'); + }); +}); + + +describe('Limit cases tests', () => { + test('Creating Identity Source without Cognito and OIDC configurations - should throw', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + expect(() => { + new IdentitySource(stack, 'IdentitySource', { + configuration: { + }, + policyStore: policyStore, + principalEntityType: 'TestType', + }); + }).toThrow('One Identity provider configuration between cognitoUserPoolConfiguration and openIdConnectConfiguration must be defined'); + }); + test('Creating Identity Source with both Cognito and OIDC configurations - should throw', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const userPool = new UserPool(stack, 'UserPool'); + const policyStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.OFF, + }, + }); + expect(() => { + new IdentitySource(stack, 'IdentitySource', { + configuration: { + cognitoUserPoolConfiguration: { + userPool: userPool, + }, + openIdConnectConfiguration: { + issuer: 'https://test.com', + entityIdPrefix: 'prefix', + groupConfiguration: { + groupClaim: 'group', + groupEntityType: 'GroupType', + }, + accessTokenOnly: { + audiences: ['testAudience'], + principalIdClaim: 'sub', + }, + }, + }, + policyStore: policyStore, + principalEntityType: 'TestType', + }); + }).toThrow('Only one between cognitoUserPoolConfiguration or openIdConnectConfiguration must be defined'); + }); +}); \ No newline at end of file diff --git a/test/policy-store.test.ts b/test/policy-store.test.ts index 0b71ab7..29b87f8 100644 --- a/test/policy-store.test.ts +++ b/test/policy-store.test.ts @@ -661,4 +661,31 @@ describe('generating schemas from OpenApi specs', () => { // it should have the eight explicitly defined actions plus the 6 derived from the 'any' definition expect(Object.keys(schema.PodcastApp.actions).length).toEqual(8 + 6); }); + test('generate schema from openApi spec without userGroups', () => { + // GIVEN + const stack = new Stack(undefined, 'Stack'); + + // WHEN + const schema = PolicyStore.schemaFromOpenApiSpec( + path.join(__dirname, 'podcastappswagger.json'), + + ); + const pStore = new PolicyStore(stack, 'PolicyStore', { + validationSettings: { + mode: ValidationSettingsMode.STRICT, + }, + schema: { + cedarJson: JSON.stringify(schema), + }, + }); + + // THEN + expect(pStore.schema?.cedarJson).toBeDefined(); + expect(Object.keys(schema.PodcastApp.entityTypes)).toStrictEqual([ + 'User', + 'Application', + ]); + // it should have the eight explicitly defined actions plus the 6 derived from the 'any' definition + expect(Object.keys(schema.PodcastApp.actions).length).toEqual(8 + 6); + }); }); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 5a318df..23311d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1204,10 +1204,10 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -aws-cdk-lib@2.139.0: - version "2.139.0" - resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.139.0.tgz#bee393c979d74cf58c087850ce896df145b04776" - integrity sha512-G9yoc+VFwF10kpgf4omtrAVmUNPeAP708oF5fc7XlRTzoTXMmAdUJW9cRGOMtAkFY83SxiJP0wm8n5Z9tjAdUA== +aws-cdk-lib@2.148.0: + version "2.148.0" + resolved "https://registry.yarnpkg.com/aws-cdk-lib/-/aws-cdk-lib-2.148.0.tgz#5d41ed56005fdfc928dfd31bcda268cf9acfcb41" + integrity sha512-Pa0pyIHlhnsqtMkPJS3tnptYhoOSNDOgoFurNB4Qfa0vnAkjYQ+JKQkR1tNNr8+UtO9jUfXRklQgjEqlFlrgBA== dependencies: "@aws-cdk/asset-awscli-v1" "^2.2.202" "@aws-cdk/asset-kubectl-v20" "^2.1.2" @@ -1220,7 +1220,7 @@ aws-cdk-lib@2.139.0: mime-types "^2.1.35" minimatch "^3.1.2" punycode "^2.3.1" - semver "^7.6.0" + semver "^7.6.2" table "^6.8.2" yaml "1.10.2"