diff --git a/.github/workflows/ci-build-deploy.yaml b/.github/workflows/ci-build-deploy.yaml index c66a7c0db..a9030810c 100644 --- a/.github/workflows/ci-build-deploy.yaml +++ b/.github/workflows/ci-build-deploy.yaml @@ -97,7 +97,7 @@ jobs: image: registry: docker.pkg.github.com repository: bcgov-dss/api-serv-infra/mongodb - tag: 4.4.15-2941ec1e + tag: 5.0-7a639fba pullPolicy: IfNotPresent pullSecrets: - dev-github-read-packages-creds @@ -111,7 +111,17 @@ jobs: rbac: create: true - strategyType: Recreate + updateStrategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 100% + + readinessProbe: + timeoutSeconds: 20 + + livenessProbe: + timeoutSeconds: 20 persistence: enabled: true @@ -134,7 +144,7 @@ jobs: runAsUser: ${{ secrets.RUNNING_UID_GID }} ' > values.yaml helm repo add bitnami https://charts.bitnami.com/bitnami - helm upgrade --install proto-asp-${{ steps.set-deploy-id.outputs.DEPLOY_ID }}-db --version 10.31.5 -f values.yaml bitnami/mongodb + helm upgrade --install proto-asp-${{ steps.set-deploy-id.outputs.DEPLOY_ID }}-db --version 12.1.31 -f values.yaml bitnami/mongodb - name: 'Deploy Backend' if: github.ref != 'refs/heads/dev' diff --git a/local/db/keystone-init.sql b/local/db/keystone-init.sql index 4e6e2a929..25d905bf9 100644 --- a/local/db/keystone-init.sql +++ b/local/db/keystone-init.sql @@ -2326,3 +2326,23 @@ ALTER TABLE public."Activity" ADD COLUMN "filterKey2" text, ADD COLUMN "filterKey3" text, ADD COLUMN "filterKey4" text; + +ALTER TABLE public."Metric" + ADD COLUMN "namespace" text; + +ALTER TABLE public."CredentialIssuer" + ADD COLUMN "isShared" boolean NOT NULL DEFAULT false, + ADD COLUMN "inheritFrom" integer; + +-- +-- Name: credentialissuer_inheritfrom_index; Type: INDEX; Schema: public; Owner: keystonejsuser +-- + +CREATE INDEX credentialissuer_inheritfrom_index ON public."CredentialIssuer" USING btree ("inheritFrom"); + +-- +-- Name: CredentialIssuer credentialissuer_inheritfrom_foreign; Type: FK CONSTRAINT; Schema: public; Owner: keystonejsuser +-- + +ALTER TABLE ONLY public."CredentialIssuer" + ADD CONSTRAINT credentialissuer_inheritfrom_foreign FOREIGN KEY ("inheritFrom") REFERENCES public."CredentialIssuer"(id); diff --git a/local/feeder-init/init.sh b/local/feeder-init/init.sh index ed3e924c4..b613570b9 100755 --- a/local/feeder-init/init.sh +++ b/local/feeder-init/init.sh @@ -1,5 +1,4 @@ #!/bin/bash - apk add --no-cache curl cd /tmp @@ -13,20 +12,21 @@ while true; do curl http://feeder.localtest.me:6000/push -F yaml=@developer-user.yaml curl http://feeder.localtest.me:6000/push -F yaml=@mark-user.yaml curl http://feeder.localtest.me:6000/push -F yaml=@platform-authz-profile.yaml + curl http://feeder.localtest.me:6000/push -F yaml=@shared-idp.yaml curl http://feeder.localtest.me:6000/push -F yaml=@platform-gwa-api.yaml curl http://feeder.localtest.me:6000/push -F yaml=@organization-unit.yaml curl http://feeder.localtest.me:6000/push -F yaml=@dataset-gwa.yaml - curl http://feeder.localtest.me:6000/push -F yaml=@product-initializer.yaml - curl http://feeder.localtest.me:6000/push -F yaml=@product-initializer-permission.yaml +# curl http://feeder.localtest.me:6000/push -F yaml=@product-initializer.yaml +# curl http://feeder.localtest.me:6000/push -F yaml=@product-initializer-permission.yaml curl http://feeder.localtest.me:6000/push -F yaml=@cc-dataset-gwa.yaml - curl http://feeder.localtest.me:6000/push -F yaml=@cc-product-initializer.yaml +# curl http://feeder.localtest.me:6000/push -F yaml=@cc-product-initializer.yaml curl http://feeder.localtest.me:6000/push -F yaml=@cr-dataset-gwa.yaml - curl http://feeder.localtest.me:6000/push -F yaml=@cr-product-initializer.yaml +# curl http://feeder.localtest.me:6000/push -F yaml=@cr-product-initializer.yaml curl http://feeder.localtest.me:6000/push -F yaml=@permission-dataset-gwa.yaml curl http://feeder.localtest.me:6000/push -F yaml=@api-dataset-gwa.yaml - curl http://feeder.localtest.me:6000/push -F yaml=@product-initializer-api.yaml +# curl http://feeder.localtest.me:6000/push -F yaml=@product-initializer-api.yaml curl http://feeder.localtest.me:6000/push -F yaml=@preview-dataset-gwa.yaml - curl http://feeder.localtest.me:6000/push -F yaml=@preview-product-initializer.yaml +# curl http://feeder.localtest.me:6000/push -F yaml=@preview-product-initializer.yaml break else diff --git a/local/feeder-init/shared-idp.yaml b/local/feeder-init/shared-idp.yaml new file mode 100644 index 000000000..622e9928e --- /dev/null +++ b/local/feeder-init/shared-idp.yaml @@ -0,0 +1,19 @@ +entity: CredentialIssuer +record: + id: 'Sample Shared IdP' + namespace: newplatform + description: 'A Shared IdP for Teams to use' + flow: client-credentials + mode: auto + clientAuthenticator: client-secret + authPlugin: jwt-keycloak + clientRoles: [] + availableScopes: [] + owner: janis@idir + isShared: true + environmentDetails: + - environment: prod + issuerUrl: http://keycloak.localtest.me:9080/auth/realms/master + clientId: gwa-api + clientRegistration: managed + clientSecret: '18900468-3db1-43f7-a8af-e75f079eb742' diff --git a/local/oauth2-proxy/oauth2-proxy-local.cfg b/local/oauth2-proxy/oauth2-proxy-local.cfg index cd8aacfc6..749890067 100644 --- a/local/oauth2-proxy/oauth2-proxy-local.cfg +++ b/local/oauth2-proxy/oauth2-proxy-local.cfg @@ -22,7 +22,7 @@ skip_jwt_bearer_tokens="false" set_authorization_header="false" pass_authorization_header="false" skip_auth_regex="/login|/health|/public|/docs|/redirect|/_next|/images|/devportal|/manager|/about|/maintenance|/admin/session|/ds/api|/feed/|/signout|^[/]$" -whitelist_domains="*" +whitelist_domains="keycloak.localtest.me:9080" upstreams=["http://apsportal.localtest.me:3000"] skip_provider_button='true' redis_connection_url="redis://redis-master:6379" diff --git a/src/authz/graphql-whitelist/httplocalhost4180managerauthorizationprofiles-274a66.gql b/src/authz/graphql-whitelist/httplocalhost4180managerauthorizationprofiles-274a66.gql new file mode 100644 index 000000000..367dbcf42 --- /dev/null +++ b/src/authz/graphql-whitelist/httplocalhost4180managerauthorizationprofiles-274a66.gql @@ -0,0 +1,26 @@ + + query GetCredentialIssuers { + allCredentialIssuersByNamespace { + id + name + flow + mode + owner { + name + username + email + } + environmentDetails + inheritFrom { + name + } + availableScopes + clientAuthenticator + clientRoles + clientMappers + apiKeyName + resourceType + resourceScopes + resourceAccessScope + } + } diff --git a/src/authz/graphql-whitelist/httplocalhost4180managerauthorizationprofiles-681d06.gql b/src/authz/graphql-whitelist/httplocalhost4180managerauthorizationprofiles-681d06.gql new file mode 100644 index 000000000..1a5603d08 --- /dev/null +++ b/src/authz/graphql-whitelist/httplocalhost4180managerauthorizationprofiles-681d06.gql @@ -0,0 +1,26 @@ + + query GetCredentialIssuers { + allCredentialIssuersByNamespace { + id + name + flow + mode + owner { + name + username + email + } + environmentDetails + inheritFrom { + environmentDetails + } + availableScopes + clientAuthenticator + clientRoles + clientMappers + apiKeyName + resourceType + resourceScopes + resourceAccessScope + } + } diff --git a/src/authz/graphql-whitelist/httplocalhost4180managerauthorizationprofiles-a7c98d.gql b/src/authz/graphql-whitelist/httplocalhost4180managerauthorizationprofiles-a7c98d.gql new file mode 100644 index 000000000..9b40a6046 --- /dev/null +++ b/src/authz/graphql-whitelist/httplocalhost4180managerauthorizationprofiles-a7c98d.gql @@ -0,0 +1,8 @@ + + query SharedIdPPreview($profileName: String) { + sharedIdPs(profileName: $profileName) { + id + name + environmentDetails + } + } diff --git a/src/authz/matrix.csv b/src/authz/matrix.csv index 483fd3eb5..aec5027d2 100644 --- a/src/authz/matrix.csv +++ b/src/authz/matrix.csv @@ -50,6 +50,7 @@ CREDENTIAL ADMIN,,,CredentialIssuer,create,,,,,credential-admin,,,allow, CREDENTIAL ADMIN,,,CredentialIssuer,read,,,,,credential-admin,,,allow,filterByUserNS CREDENTIAL ADMIN,,,CredentialIssuer,update,,namespace,,,credential-admin,,,deny, CREDENTIAL ADMIN,,,CredentialIssuer,,"create,read",namespace,,,credential-admin,,,allow, +CREDENTIAL ADMIN,,sharedIdPs,,,,,,,credential-admin,,,allow, CREDENTIAL ADMIN,,OwnedCredentialIssuer,,,,,,,credential-admin,,,allow, CREDENTIAL ADMIN,,allCredentialIssuersByNamespace,,,,,,,credential-admin,,,allow,filterByUserNS CREDENTIAL ADMIN,,,User,read,,,,,credential-admin,,,allow, diff --git a/src/batch/data-rules.js b/src/batch/data-rules.js index 9bed4406a..253d20b3e 100644 --- a/src/batch/data-rules.js +++ b/src/batch/data-rules.js @@ -464,6 +464,8 @@ const metadata = { 'resourceScopes', 'resourceType', 'resourceAccessScope', + 'isShared', + 'inheritFrom', 'apiKeyName', 'owner', ], @@ -473,9 +475,15 @@ const metadata = { clientRoles: { name: 'toStringDefaultArray' }, clientMappers: { name: 'toStringDefaultArray' }, environmentDetails: { name: 'toString' }, + inheritFrom: { + name: 'connectOne', + list: 'allCredentialIssuers', + refKey: 'name', + }, owner: { name: 'connectOne', list: 'allUsers', refKey: 'username' }, }, validations: { + isShared: { type: 'boolean' }, flow: { type: 'enum', values: ['client-credentials'], diff --git a/src/batch/feed-worker.ts b/src/batch/feed-worker.ts index 181658bad..97bd3c156 100644 --- a/src/batch/feed-worker.ts +++ b/src/batch/feed-worker.ts @@ -469,6 +469,7 @@ export const syncRecords = async function ( } } if (Object.keys(data).length === 0) { + logger.debug('[%s] [%s] no update', entity, localRecord.id); return { status: 200, result: 'no-change', diff --git a/src/controllers/v2/openapi.yaml b/src/controllers/v2/openapi.yaml index 018379ebd..51d2be1ad 100644 --- a/src/controllers/v2/openapi.yaml +++ b/src/controllers/v2/openapi.yaml @@ -277,6 +277,8 @@ components: clientRegistration: managed clientId: a-client-id clientSecret: a-client-secret + CredentialIssuerRefID: + type: string UserRefID: type: string CredentialIssuer: @@ -315,6 +317,8 @@ components: type: string resourceAccessScope: type: string + isShared: + type: boolean apiKeyName: type: string availableScopes: @@ -333,6 +337,8 @@ components: items: type: string type: array + inheritFrom: + $ref: '#/components/schemas/CredentialIssuerRefID' owner: $ref: '#/components/schemas/UserRefID' type: object @@ -508,8 +514,6 @@ components: type: string LegalRefID: type: string - CredentialIssuerRefID: - type: string Environment: properties: appId: diff --git a/src/controllers/v2/routes.ts b/src/controllers/v2/routes.ts index 470142a58..3b132cc4b 100644 --- a/src/controllers/v2/routes.ts +++ b/src/controllers/v2/routes.ts @@ -185,6 +185,11 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CredentialIssuerRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "UserRefID": { "dataType": "refAlias", "type": {"dataType":"string","validators":{}}, @@ -204,11 +209,13 @@ const models: TsoaRoute.Models = { "environmentDetails": {"dataType":"array","array":{"dataType":"refObject","ref":"IssuerEnvironmentConfig"}}, "resourceType": {"dataType":"string"}, "resourceAccessScope": {"dataType":"string"}, + "isShared": {"dataType":"boolean"}, "apiKeyName": {"dataType":"string"}, "availableScopes": {"dataType":"array","array":{"dataType":"string"}}, "resourceScopes": {"dataType":"array","array":{"dataType":"string"}}, "clientRoles": {"dataType":"array","array":{"dataType":"string"}}, "clientMappers": {"dataType":"array","array":{"dataType":"string"}}, + "inheritFrom": {"ref":"CredentialIssuerRefID"}, "owner": {"ref":"UserRefID"}, }, "additionalProperties": false, @@ -326,11 +333,6 @@ const models: TsoaRoute.Models = { "type": {"dataType":"string","validators":{}}, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "CredentialIssuerRefID": { - "dataType": "refAlias", - "type": {"dataType":"string","validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "Environment": { "dataType": "refObject", "properties": { diff --git a/src/controllers/v2/types.ts b/src/controllers/v2/types.ts index 547466673..7e4c9839c 100644 --- a/src/controllers/v2/types.ts +++ b/src/controllers/v2/types.ts @@ -330,11 +330,13 @@ export interface CredentialIssuer { environmentDetails?: IssuerEnvironmentConfig[]; resourceType?: string; resourceAccessScope?: string; + isShared?: boolean; apiKeyName?: string; availableScopes?: string[]; resourceScopes?: string[]; clientRoles?: string[]; clientMappers?: string[]; + inheritFrom?: CredentialIssuerRefID; owner?: UserRefID; } @@ -455,8 +457,13 @@ export interface Activity { refId?: string; namespace?: string; blob?: string; + filterKey1?: string; + filterKey2?: string; + filterKey3?: string; + filterKey4?: string; updatedAt?: DateTime; createdAt?: DateTime; + context?: any; // toString actor?: UserRefID; } diff --git a/src/lists/CredentialIssuer.js b/src/lists/CredentialIssuer.js index 55a874e04..1b51a1103 100644 --- a/src/lists/CredentialIssuer.js +++ b/src/lists/CredentialIssuer.js @@ -21,9 +21,14 @@ const { updateEnvironmentDetails } = require('../services/keystone'); const { DeleteIssuerValidate, StructuredActivityService, + ValidateIssuer, +} = require('../services/workflow'); +const { + syncSharedIdp, + addClientsToSharedIdP, } = require('../services/workflow'); - const { Logger } = require('../logger'); +const { kebabCase } = require('lodash'); const logger = Logger('lists.credentialissuer'); module.exports = { @@ -74,7 +79,7 @@ module.exports = { type: Select, emptyOption: false, dataType: 'string', - defaultValue: 'manual', + defaultValue: 'auto', options: [ { value: 'manual', label: 'Manual' }, { value: 'auto', label: 'Automatic' }, @@ -149,6 +154,21 @@ module.exports = { isRequired: false, defaultValue: 'X-API-KEY', }, + // Introduced to support shared IdP + isShared: { + type: Checkbox, + isRequired: true, + defaultValue: false, + access: { update: false }, + }, + // Introduced to support shared IdP - 'environmentDetails' will be used from the inheritFrom Issuer + inheritFrom: { + type: Relationship, + ref: 'CredentialIssuer', + many: false, + isRequired: false, + access: { update: false }, + }, owner: { type: Relationship, ref: 'User', @@ -173,6 +193,11 @@ module.exports = { if (context['authedItem'] && 'namespace' in context['authedItem']) { resolvedData['namespace'] = context['authedItem']['namespace']; } + if ('inheritFrom' in resolvedData) { + // clientId is used when inheritFrom is set + // and it represents the client that Roles are managed for + resolvedData['clientId'] = kebabCase(resolvedData['name']); + } } if (operation == 'update' || operation == 'create') { // special handling of the environmentDetails @@ -187,6 +212,34 @@ module.exports = { return resolvedData; }, + validateInput: async function ({ + operation, + existingItem, + originalInput, + resolvedData, + context, + addFieldValidationError, // Field hooks only + addValidationError, // List hooks only + listKey, + fieldPath, // Field hooks only + }) { + if (operation === 'update') { + if ('inheritFrom' in originalInput || 'clientId' in originalInput) { + addValidationError( + 'Some fields are only set during creation. Failed to update.' + ); + } + } + await ValidateIssuer( + context.createContext({ skipAccessControl: true }), + operation, + existingItem, + originalInput, + resolvedData, + addValidationError + ); + }, + validateDelete: async function ({ existingItem, context }) { await DeleteIssuerValidate( context, @@ -210,19 +263,48 @@ module.exports = { ); }, - afterChange: async function ({ operation, updatedItem, context }) { + beforeChange: async function ({ + operation, + originalInput, + resolvedData, + context, + }) { + if (operation === 'create' && originalInput.inheritFrom) { + await addClientsToSharedIdP( + context, + resolvedData.namespace, + resolvedData.clientId, + resolvedData.inheritFrom + ); + } + }, + + afterChange: async function ({ + operation, + existingItem, + updatedItem, + context, + }) { + if (updatedItem.inheritFrom) { + await syncSharedIdp(context, updatedItem.id); + } + await new StructuredActivityService( context, - context.authedItem['namespace'] - ).logListActivity( - true, - operation, - 'authorization profile', - { - credentialIssuer: updatedItem, - }, - '{actor} {action} {entity} {credentialIssuer}' - ); + context.authedItem?.namespace + ) + .logListActivity( + true, + operation, + 'authorization profile', + { + credentialIssuer: updatedItem, + }, + '{actor} {action} {entity} {credentialIssuer}' + ) + .catch((e) => { + logger.error('[Activity] Failed to Record %s', e); + }); }, }, }; diff --git a/src/lists/extensions/AliasedQueries.ts b/src/lists/extensions/AliasedQueries.ts index fe8cf7694..472359fb9 100644 --- a/src/lists/extensions/AliasedQueries.ts +++ b/src/lists/extensions/AliasedQueries.ts @@ -1,12 +1,5 @@ -import { CredentialIssuer, Environment } from '@/services/keystone/types'; -import { IssuerEnvironmentConfig } from '@/services/workflow/types'; -import { - ListQuery, - ItemQuery, - SchemaType, - AliasType, - AliasConfig, -} from './resolvers'; +import { ListQuery, ItemQuery, SchemaType, AliasConfig } from './resolvers'; +import CredentialIssuerHook from './hooks/CredentialIssuerHook'; module.exports = { extensions: [ @@ -61,21 +54,7 @@ module.exports = { gqlName: 'allCredentialIssuersByNamespace', list: 'CredentialIssuer', type: SchemaType.ListQuery, - hook: (issuers: CredentialIssuer[]) => { - issuers.forEach((data) => { - const envDetails = JSON.parse(data.environmentDetails); - envDetails.forEach(function (env: IssuerEnvironmentConfig) { - if (env.clientSecret) { - env.clientSecret = '****'; - } else if (env.initialAccessToken) { - env.initialAccessToken = '****'; - } - env.exists = true; - }); - data.environmentDetails = JSON.stringify(envDetails); - }); - return issuers; - }, + hook: CredentialIssuerHook, }, { gqlName: 'allNamespaceServiceAccounts', @@ -96,49 +75,6 @@ module.exports = { gqlName: 'OwnedEnvironment', list: 'Environment', type: SchemaType.ItemQuery, - // hook: (data: Environment) => { - // if ( - // data.credentialIssuer == null || - // data.credentialIssuer.environmentDetails == null - // ) { - // return data; - // } - // const envDetails = JSON.parse( - // data.credentialIssuer.environmentDetails - // ); - // envDetails.forEach(function (env: IssuerEnvironmentConfig) { - // if (env.clientId || env.clientSecret) { - // env.clientId = '****'; - // env.clientSecret = '****'; - // } else if (env.initialAccessToken) { - // env.initialAccessToken = '****'; - // } - // env.exists = true; - // }); - // data.credentialIssuer.environmentDetails = JSON.stringify( - // envDetails - // ); - // return data; - // }, - }, - { - gqlName: 'OwnedCredentialIssuer', - list: 'CredentialIssuer', - type: SchemaType.ItemQuery, - hook: (data: CredentialIssuer) => { - const envDetails = JSON.parse(data.environmentDetails); - envDetails.forEach(function (env: IssuerEnvironmentConfig) { - if (env.clientId || env.clientSecret) { - env.clientId = '****'; - env.clientSecret = '****'; - } else if (env.initialAccessToken) { - env.initialAccessToken = '****'; - } - env.exists = true; - }); - data.environmentDetails = JSON.stringify(envDetails); - return data; - }, }, { gqlName: 'CredentialIssuerSummary', diff --git a/src/lists/extensions/CredentialIssuerExt.ts b/src/lists/extensions/CredentialIssuerExt.ts new file mode 100644 index 000000000..7da967151 --- /dev/null +++ b/src/lists/extensions/CredentialIssuerExt.ts @@ -0,0 +1,48 @@ +const { EnforcementPoint } = require('../../authz/enforcement'); +import { kebabCase } from 'lodash'; +import { + generateEnvDetails, + lookupSharedIssuers, +} from '../../services/keystone'; +import { CredentialIssuer } from '../../services/keystone/types'; + +const typeSharedIssuer = ` + type SharedIssuer { + id: ID! + name: String! + environmentDetails: String! + }`; + +module.exports = { + extensions: [ + (keystone: any) => { + keystone.extendGraphQLSchema({ + types: [{ type: typeSharedIssuer }], + queries: [ + { + schema: 'sharedIdPs(profileName: String): [SharedIssuer]', + resolver: async ( + item: any, + args: { profileName: string }, + context: any, + info: any, + { query, access }: any + ) => { + const issuers = await lookupSharedIssuers(context); + + return issuers.map((issuer: CredentialIssuer) => ({ + id: issuer.id, + name: issuer.name, + environmentDetails: generateEnvDetails( + kebabCase(args.profileName), + JSON.parse(issuer.environmentDetails) + ), + })); + }, + access: EnforcementPoint, + }, + ], + }); + }, + ], +}; diff --git a/src/lists/extensions/Namespace.ts b/src/lists/extensions/Namespace.ts index de996deb5..59dc58ab7 100644 --- a/src/lists/extensions/Namespace.ts +++ b/src/lists/extensions/Namespace.ts @@ -10,7 +10,6 @@ import { lookupProductEnvironmentServicesBySlug, lookupUsersByUsernames, recordActivity, - recordActivityWithBlob, } from '../../services/keystone'; import { getEnvironmentContext, @@ -40,7 +39,7 @@ import { } from '../../services/utils'; import getSubjectToken from '../../auth/auth-token'; import { NamespaceService } from '../../services/org-groups'; -import { IssuerEnvironmentConfig } from '@/services/workflow/types'; +import { IssuerEnvironmentConfig } from '../../services/workflow/types'; const typeUserContact = ` type UserContact { diff --git a/src/lists/extensions/UMAResourceSet.ts b/src/lists/extensions/UMAResourceSet.ts index 3bdadb42d..7d18117b1 100644 --- a/src/lists/extensions/UMAResourceSet.ts +++ b/src/lists/extensions/UMAResourceSet.ts @@ -1,34 +1,7 @@ -const { Text, Checkbox, Relationship } = require('@keystonejs/fields'); -const { Markdown } = require('@keystonejs/fields-markdown'); - -const { EnforcementPoint } = require('../../authz/enforcement'); +import { EnforcementPoint } from '../../authz/enforcement'; import { strict as assert } from 'assert'; -import { - UMAResourceRegistrationService, - ResourceSetQuery, -} from '../../services/uma2'; - -import { - KeycloakTokenService, - getOpenidFromIssuer, -} from '../../services/keycloak'; - -import { - IssuerEnvironmentConfig, - getIssuerEnvironmentConfig, -} from '../../services/workflow/types'; - -import { - isUserBasedResourceOwners, - getSuitableOwnerToken, - getResourceSets, - getEnvironmentContext, -} from './Common'; -import type { TokenExchangeResult } from './Common'; - -import { mergeWhereClause } from '@keystonejs/utils'; - -const keystoneApi = require('../../services/keystone'); +import { UMAResourceRegistrationService } from '../../services/uma2'; +import { getResourceSets, getEnvironmentContext } from './Common'; const typeUMAScope = ` type UMAScope { @@ -44,8 +17,8 @@ type UMAResourceSet { ownerManagedAccess: Boolean, uris: [String] resource_scopes: [UMAScope] -} -`; +}`; + module.exports = { extensions: [ (keystone: any) => { diff --git a/src/lists/extensions/hooks/CredentialIssuerHook.ts b/src/lists/extensions/hooks/CredentialIssuerHook.ts new file mode 100644 index 000000000..3cc2964c8 --- /dev/null +++ b/src/lists/extensions/hooks/CredentialIssuerHook.ts @@ -0,0 +1,31 @@ +import { + lookupCredentialIssuerById, + dynamicallySetEnvironmentDetails, + maskEnvironmentDetails, +} from '../../../services/keystone'; +import { CredentialIssuer } from '../../../services/keystone/types'; + +/** + * Transform the environmentDetails by masking sensitive data + * and backfilling environment details if 'inheritFrom' are provided + */ +export default async (context: any, issuers: CredentialIssuer[]) => { + for (const data of issuers) { + if (data.inheritFrom) { + const inheritedFromIssuer = await lookupCredentialIssuerById( + context.sudo(), + `${data.inheritFrom}` + ); + data.environmentDetails = dynamicallySetEnvironmentDetails({ + ...data, + ...{ inheritFrom: inheritedFromIssuer }, + }); + } else { + data.environmentDetails = maskEnvironmentDetails(data); + } + if (data.inheritFrom) { + delete data.inheritFrom.environmentDetails; + } + } + return issuers; +}; diff --git a/src/lists/extensions/resolvers/itemQuery.ts b/src/lists/extensions/resolvers/itemQuery.ts index 6cc9bb8a8..017151201 100644 --- a/src/lists/extensions/resolvers/itemQuery.ts +++ b/src/lists/extensions/resolvers/itemQuery.ts @@ -36,7 +36,7 @@ export const ItemQuery = (keystone: any, alias: AliasConfig) => { try { // note: only has the root List data, not the relationships logger.debug('Record %j', record); - return alias.hook ? alias.hook(record) : record; + return alias.hook ? await alias.hook(context, record) : record; } catch (e) { logger.error('Failed to process hook - %s', e); throw e; diff --git a/src/lists/extensions/resolvers/listQuery.ts b/src/lists/extensions/resolvers/listQuery.ts index 306d67de1..db310c636 100644 --- a/src/lists/extensions/resolvers/listQuery.ts +++ b/src/lists/extensions/resolvers/listQuery.ts @@ -24,7 +24,7 @@ export const ListQuery = (keystone: any, alias: AliasConfig) => { const vars = mergeWhereClause(args, other.access); const records = await a.listQuery(vars, context, gqlName, info); //logger.debug('Records %j', records); - return alias.hook ? alias.hook(records) : records; + return alias.hook ? await alias.hook(context, records) : records; }, access: EnforcementPoint, }, diff --git a/src/mocks/handlers.js b/src/mocks/handlers.js index 9b8aa1b04..4eb7a8d71 100644 --- a/src/mocks/handlers.js +++ b/src/mocks/handlers.js @@ -44,12 +44,16 @@ import { allProductsHandler, allLegalsHandler, getEnvironmentHandler, + getAllCredentialIssuers, getAllCredentialIssuersByNamespace, + getSharedIdpPreview, allGatewayServicesHandler, + updateAuthzProfile, updateProductHandler, updateEnvironmentHandler, deleteEnvironmentHandler, deleteProductHandler, + createAuthzProfile, } from './resolvers/products'; import { handleAllDatasets } from './resolvers/datasets'; @@ -146,10 +150,11 @@ export const handlers = [ keystone.mutation('AddEnvironment', addEnvironmentHandler), keystone.mutation('DeleteEnvironment', deleteEnvironmentHandler), keystone.query('GetOwnedEnvironment', getEnvironmentHandler), - keystone.query( - 'GetAllCredentialIssuersByNamespace', - getAllCredentialIssuersByNamespace - ), + keystone.query('GetAllCredentialIssuers', getAllCredentialIssuersByNamespace), + keystone.query('GetCredentialIssuers', getAllCredentialIssuers), + keystone.query('SharedIdPPreview', getSharedIdpPreview), + keystone.mutation('CreateAuthzProfile', createAuthzProfile), + keystone.mutation('UpdateAuthzProfile', updateAuthzProfile), keystone.query('GetAllGatewayServices', allGatewayServicesHandler), keystone.query('GetAllLegals', allLegalsHandler), keystone.query( diff --git a/src/mocks/resolvers/products.js b/src/mocks/resolvers/products.js index 565c77574..868682dae 100644 --- a/src/mocks/resolvers/products.js +++ b/src/mocks/resolvers/products.js @@ -270,6 +270,50 @@ export const getEnvironmentHandler = (req, res, ctx) => { ); }; +const _credentialIssuers = [ + { + id: 'c11', + name: 'abc 1234', + flow: 'client-credentials', + mode: 'auto', + owner: { + name: 'Harley Jones', + username: 'harley@idir', + email: 'harley@gov.ca', + }, + environmentDetails: + '[{"exists":true,"environment":"dev","issuerUrl":"https://dev.loginproxy.site/auth/realms/test","clientRegistration":"shared-idp","clientId":"asdkfj0f-9sdf-as90df"}]', + inheritFrom: { + name: 'Silver Dev Shared IdP', + }, + availableScopes: '[]', + clientAuthenticator: 'client-secret', + clientRoles: '[]', + clientMappers: '[{"name":"audience","defaultValue":""}]', + apiKeyName: 'X-API-KEY', + resourceType: '', + resourceScopes: '[]', + resourceAccessScope: '', + }, +]; + +export const getAllCredentialIssuers = (_, res, ctx) => { + return res( + ctx.data({ + allCredentialIssuersByNamespace: _credentialIssuers, + }) + ); +}; + +export const createAuthzProfile = (req, res, ctx) => { + return res(ctx.data({})); +}; + +export const updateAuthzProfile = (req, res, ctx) => { + console.log(req.variables); + return res(ctx.data({})); +}; + export const getAllCredentialIssuersByNamespace = (req, res, ctx) => { const { id } = req.variables; return res( @@ -406,3 +450,27 @@ export const deleteProductHandler = (req, res, ctx) => { products.delete(id); return res(ctx.data({})); }; + +export const getSharedIdpPreview = (req, res, ctx) => { + const { profileName } = req.variables; + + return res( + ctx.data({ + sharedIdPs: [ + { + id: 'sidp-1', + name: 'Silver Dev Shared IdP', + environmentDetails: JSON.stringify([ + { + exists: true, + environment: 'dev', + issuerUrl: 'https://loginproxy.dev/auth/realms/api', + clientRegistration: 'shared-idp', + clientId: `ab-${profileName}-dev`, + }, + ]), + }, + ], + }) + ); +}; diff --git a/src/nextapp/.env.local b/src/nextapp/.env.local index 660a0708d..c4072b69f 100644 --- a/src/nextapp/.env.local +++ b/src/nextapp/.env.local @@ -1,6 +1,6 @@ -NEXT_PUBLIC_APP_VERSION=1.6.3 -NEXT_PUBLIC_APP_REVISION=1d3rur984ioefj844je8fju28wfjjasdf0 -NEXT_PUBLIC_KUBE_CLUSTER=feature-silver +NEXT_PUBLIC_APP_VERSION=0.0.0 +NEXT_PUBLIC_APP_REVISION=000000000000000000000000 +NEXT_PUBLIC_KUBE_CLUSTER=local NEXT_PUBLIC_HELP_DESK_URL=https://dpdd.atlassian.net/servicedesk/customer/portal/1/group/2 NEXT_PUBLIC_HELP_CHAT_URL=https://chat.developer.gov.bc.ca/channel/aps-ops NEXT_PUBLIC_HELP_ISSUE_URL=https://github.com/bcgov/api-services-portal/issues diff --git a/src/nextapp/components/authorization-profile-form/authentication-form.tsx b/src/nextapp/components/authorization-profile-form/authentication-form.tsx index 853cb6145..761c692ca 100644 --- a/src/nextapp/components/authorization-profile-form/authentication-form.tsx +++ b/src/nextapp/components/authorization-profile-form/authentication-form.tsx @@ -14,6 +14,7 @@ import { } from '@chakra-ui/react'; interface AuthenticationFormProps { + hidden: boolean; id?: string; onChange: (value: string) => void; onCancel: () => void; @@ -22,6 +23,7 @@ interface AuthenticationFormProps { } const AuthenticationForm: React.FC = ({ + hidden, id, onChange, onCancel, @@ -45,7 +47,7 @@ const AuthenticationForm: React.FC = ({ return ( <> - +