From 3ae1d2b0fd97bfa673bd4613dbe81c59b1d4f1fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 13:34:49 -0700 Subject: [PATCH] Create Latest Release - sec fixes (#491) * First pass at new consumers table. * Stub out filters. * Finish base filter behavior. * Finish basic POC of filters. * Fix up consumers list page. * Add proper id to search logic * First pass at approval dialog * First pass at laying out access request dialog. * Add working forms. * Fix possible empty object * Proxy access requests. * Add missing allowed query. * Touch up some files. * Wire up conditional select options. * Wire up auth and controls. * Bubble controls up to the top level. * Finish forms and add data-testids * First pass at new consumers table. * Stub out filters. * Finish base filter behavior. * Finish basic POC of filters. * Fix up consumers list page. * Add proper id to search logic * First pass at consumers unit tests. * Fix type error. * Unit tests for consumers index page functionality. * Add mocks for access request * Fix some tests, start grant access dialog * Add tests for request details and controls * Add more unit tests for access requests so consumers page tests can be lighter * Dialog and controls tests. * Add grant access dialog tests. * Clean up consumers tests. * First pass at detail page layout. * Add restrictions. * Handle restrictions * UI layout tweaks and add edit form. * First pass at editing controls. * Finish first pass at refactor of consumers. * Update tests. * Finish IP restrictions test. * Fix up tests. * Add some coverage and fix broken tests. * Add some edit dialog tests * initial backend changes for getFilteredNamespaceConsumers * add backend for getNamespaceConsumerAccess * put back application owner in detail page * upd backend support for getConsumerProdEnvAccess * Update tests, organize MSW directory and add new consumers page query * backend getConsumerProdEnvAccess query * upd tests for backend * upd backend ConsumerProdEnvAccess object * Wire up auth, wire up UI for new API changes. * upd services for consumer access detail * Wire up edit dialog with new API. * Fix type errors * Fix some props access on the consumers detail page. * Remove debugger * upd backend updateConsumerAccess and saveConsumerLabels * adj request details on consumer * Update edit dialog and mock data. * Add app owner. * upd app owner for consumer detail and graphql whitelist * upd whitelist for access review * add allConsumerGroupLabels * Add new manage labels dialog. * tweak grant access dialog * upd permissions and whitelist for saving labels * Added Scenarios for Refresh Credentials, API Test and update existing client credential scenarios * Wire up manage labels on consumers index page. * Update grant access dialog * add filter for consumer list * add filter for consumer list * add revokeAccessFromConsumer backend call * for grant access scopes and roles are optional * add revokeAccessFromConsumer backend call * request details optional on edit dialog * 1) Change config of oauth2 proxy and keycloak 2) Update cypress tests * Refactor filters. * filters structure for labels changed a bit * Use names in filters instead of ids. Prevent duplicates * wire up filtering for consumers * wire up consumer scope and role backend for filter selection * Wire up labels to the UI * fix failing build * add backend scope search logic * Add loading state to filters. * Add new add label group interface. * new whitelist query for manage labels * save labels remove blank label * Update labels functionality, rework filters. * add whitelist for consumer list query * consumer read for api owner * fix a ssr invalid query * Cache fixes, proper naming in request dialog * Add caching to filters, minor UI improvements. * updates for consumer plugins * fix edit consumer * disable filtering * handle services not linked to products * inc whitelist for reject request * fix plugin matching for consumer detail * filter bug * Fix the filters when no session storage. * upd plugin service and route lisst * add role update for consumer * Add labels to access request * Add test-ids * resource tune for proto-generic-api and -mongodb * wire up labels on access request * remove dev,test,prod on push from ci-build-deploy * Add revoke and hide application row if null * fix for revoking access * missing whitelist for revoke access * 1)updated scenarios as per new Consumer UI 2)Added data-testid in filrwes.tsx page 3)Update keystone db schema as per new changes * upd query types * upd types * remove svc accts from consumer list and fix plugin error * stop using service access id in consumer list * fix consumer product edit dialog * improve plugin update comparison * upd plugins when grant new product to consumer * remove from whitelist the deleteGatewayConsumer * delete consumer upds * fix my access and access lists * dedup list of scopes * Add some custom error messages * Remove bcsc user menu option * Use unified error in toasts * fix 427 api key not getting deleted * resolve 443 creds for jwt key pair * fix org dataset operations * Standardize toasts to all be closable with only a startcase capitalized * add test branch back in to ci-build-deploy * fix revoke access whitelist * Comment out flaky verification step * fix ci-build-deploy indent * Fix yaml formatting * add in resources * run as production when starting keystone * fix allProductsByNamespace showing too much * fix unable to load form for access request * fix gateway services showing too many services * Added Test to verify directory details for the namespace that has no directory * Added test to verify namespace having no directory * improve log security * fix wrong role for identity provider * fix ds api error response dataset * fix business profile not displaying * remove old pages for requests * Update expected status code when call the API with non exist directory ID Co-authored-by: Joshua Jones Co-authored-by: ikethecoder Co-authored-by: Niraj Patel Co-authored-by: nirajCITZ <94716060+nirajCITZ@users.noreply.github.com> Co-authored-by: Justin Tendeck Co-authored-by: ikethecoder Co-authored-by: jTendeck <34200068+jTendeck@users.noreply.github.com> --- .../tests/08-aps-api/06-api-directory.ts | 27 +- src/admin-hooks.js | 84 ++-- src/api-openapi.js | 27 +- src/auth/auth-oauth2-proxy.js | 4 +- ...tplocalhost4180managerconsumers-3a56c3.gql | 18 + ...tplocalhost4180managerconsumers-4dbd7b.gql | 18 + ...tplocalhost4180managerconsumers-5d889f.gql | 29 ++ ...tplocalhost4180managerconsumers-f5a769.gql | 16 + src/authz/matrix.csv | 10 +- .../v2/NamespaceDirectoryController.ts | 9 +- src/lists/extensions/BusinessProfile.ts | 30 +- .../access-request/access-requests-list.tsx | 5 + .../access-request/business-details.tsx | 6 +- .../access-request/request-details.tsx | 2 +- .../business-profile/business-profile.tsx | 12 +- src/nextapp/pages/manager/consumers/[id].tsx | 9 +- src/nextapp/pages/manager/requests/[id].tsx | 387 ------------------ src/nextapp/pages/manager/requests/index.tsx | 29 -- src/nextapp/shared/types/query.types.ts | 10 +- src/package.json | 2 +- src/server.ts | 48 +-- src/services/keystone/credential-issuer.ts | 12 +- src/services/keystone/product-environment.ts | 10 +- src/services/keystone/service-access.ts | 2 + src/services/keystone/types.ts | 10 +- .../keystone_overrides/formatError.js | 157 +++++++ src/services/keystone_overrides/logger.js | 9 + src/services/workflow/consumer-management.ts | 3 +- src/services/workflow/get-consumer-authz.ts | 6 +- .../keystonejs/consumerManagement.ts | 35 +- src/tools/copyKeystoneAdminAssets.js | 8 +- 31 files changed, 448 insertions(+), 586 deletions(-) create mode 100644 src/authz/graphql-whitelist/httplocalhost4180managerconsumers-3a56c3.gql create mode 100644 src/authz/graphql-whitelist/httplocalhost4180managerconsumers-4dbd7b.gql create mode 100644 src/authz/graphql-whitelist/httplocalhost4180managerconsumers-5d889f.gql create mode 100644 src/authz/graphql-whitelist/httplocalhost4180managerconsumers-f5a769.gql delete mode 100644 src/nextapp/pages/manager/requests/[id].tsx delete mode 100644 src/nextapp/pages/manager/requests/index.tsx create mode 100644 src/services/keystone_overrides/formatError.js create mode 100644 src/services/keystone_overrides/logger.js diff --git a/e2e/cypress/tests/08-aps-api/06-api-directory.ts b/e2e/cypress/tests/08-aps-api/06-api-directory.ts index 8cfbf998d..fe455c867 100644 --- a/e2e/cypress/tests/08-aps-api/06-api-directory.ts +++ b/e2e/cypress/tests/08-aps-api/06-api-directory.ts @@ -160,10 +160,21 @@ describe('API Tests for Updating dataset', () => { }) }) - it('Get the namespace directory details (/namespaces/{ns}/directory) and verify the success code in the response', () => { + it('Get the namespace directory details (/namespaces/{ns}/directory) and verify the success code and empty response for the namespace with no directory', () => { cy.get('@apiowner').then(({ apiTest }: any) => { cy.get('@api').then(({ apiDirectory }: any) => { cy.makeAPIRequest(apiDirectory.endPoint + '/' + apiTest.namespace + '/directory', 'GET').then((res) => { + expect(res.status).to.be.equal(200) + expect(res.body).to.be.empty + }) + }) + }) + }) + + it('Get the namespace directory details (/namespaces/{ns}/directory) and verify the success code in the response', () => { + cy.get('@apiowner').then(({ namespace }: any) => { + cy.get('@api').then(({ apiDirectory }: any) => { + cy.makeAPIRequest(apiDirectory.endPoint + '/' + namespace + '/directory', 'GET').then((res) => { expect(res.status).to.be.equal(200) response = res.body[0] directoryID = res.body[0].id @@ -180,9 +191,9 @@ describe('API Tests for Updating dataset', () => { // }) it('Get the namespace directory details by its ID (/namespaces/{ns}/directory/{id}) and verify the success code in the response', () => { - cy.get('@apiowner').then(({ apiTest }: any) => { + cy.get('@apiowner').then(({ namespace }: any) => { cy.get('@api').then(({ apiDirectory }: any) => { - cy.makeAPIRequest(apiDirectory.endPoint + '/' + apiTest.namespace + '/directory' + '/' + directoryID, 'GET').then((res) => { + cy.makeAPIRequest(apiDirectory.endPoint + '/' + namespace + '/directory' + '/' + directoryID, 'GET').then((res) => { expect(res.status).to.be.equal(200) expect(res.body.name).to.be.equal(directoryName) }) @@ -190,6 +201,16 @@ describe('API Tests for Updating dataset', () => { }) }) + it('Get the namespace directory details (/namespaces/{ns}/directory/{id}) for non exist directory ID and verify the response code', () => { + cy.get('@apiowner').then(({ namespace }: any) => { + cy.get('@api').then(({ apiDirectory }: any) => { + cy.makeAPIRequest(apiDirectory.endPoint + '/' + namespace + '/directory' + '/99' , 'GET').then((res) => { + expect(res.status).to.be.oneOf([404,422]) + }) + }) + }) + }) + it('Delete the dataset (/organizations/{org}/datasets/{name}) and verify the success code in the response', () => { cy.get('@apiowner').then(({ apiTest }: any) => { cy.get('@api').then(({ apiDirectory, organization }: any) => { diff --git a/src/admin-hooks.js b/src/admin-hooks.js index c3a68a753..d5d0f8622 100644 --- a/src/admin-hooks.js +++ b/src/admin-hooks.js @@ -1,42 +1,44 @@ - - -const HelloPage = require.resolve('./pages/hello') -const Placeholder = require.resolve('./pages/placeholder') - module.exports = { - pages: [ - { - label: 'Dashboard', - path: '', - component: Placeholder, - }, - { - label: 'Workflow', - children: ['AccessRequest', 'Application', 'ServiceAccess', 'Activity'], - }, - { - label: 'Products', - children: ['Product', 'Environment', 'CredentialIssuer', 'Content', 'Legal'], - }, - { - label: 'Monitoring', - children: ['Alert', 'Metric'], - }, - { - label: 'Session', - children: ['TemporaryIdentity'], - }, - { - label: 'Keycloak', - children: ['User'], - }, - { - label: 'Kong', - children: ['GatewayService', 'GatewayRoute', 'GatewayConsumer', 'GatewayGroup', 'GatewayPlugin'], - }, - { - label: 'BCDC', - children: ['Organization', 'OrganizationUnit', 'Dataset'], - } - ] -} + pages: [ + { + label: 'Workflow', + children: ['AccessRequest', 'Application', 'ServiceAccess', 'Activity'], + }, + { + label: 'Products', + children: [ + 'Product', + 'Environment', + 'CredentialIssuer', + 'Content', + 'Legal', + ], + }, + { + label: 'Monitoring', + children: ['Alert', 'Metric'], + }, + { + label: 'Session', + children: ['TemporaryIdentity'], + }, + { + label: 'Keycloak', + children: ['User'], + }, + { + label: 'Kong', + children: [ + 'GatewayService', + 'GatewayRoute', + 'GatewayConsumer', + 'GatewayGroup', + 'GatewayPlugin', + ], + }, + { + label: 'BCDC', + children: ['Organization', 'OrganizationUnit', 'Dataset'], + }, + ], +}; diff --git a/src/api-openapi.js b/src/api-openapi.js index 93d51b536..bcf052c44 100644 --- a/src/api-openapi.js +++ b/src/api-openapi.js @@ -92,23 +92,26 @@ class ApiOpenapiApp { code: err.code, message: err.message, }); - } - if (err instanceof ValidateError) { + } else if (err instanceof ValidateError) { console.warn(`Caught Validation Error for ${req.path}:`, err.fields); return res.status(422).json({ message: 'Validation Failed', details: err?.fields, }); - } - if (err instanceof AssertionError) { - logger.error(err); - logger.error('AssertionError PATH: %s', req.path); - return res.status(422).json({ - message: err.message, - }); - } - - if (err instanceof Error) { + } else if (err instanceof AssertionError) { + // For some reason `message` is what the `stack` is normally + // so just grab the first line and return that + const response = { + message: 'Failed an assertion', + }; + try { + logger.warn('Error message %s', err.message); + response.message = err.message.split('\n')[0]; + } catch (e) { + logger.warn('Failed to parse error message %s', e); + } + return res.status(422).json(response); + } else if (err instanceof Error) { logger.error(err); logger.error('Error PATH: %s', req.path); return res.status(500).json({ diff --git a/src/auth/auth-oauth2-proxy.js b/src/auth/auth-oauth2-proxy.js index 844c1ffb9..24c9b4580 100644 --- a/src/auth/auth-oauth2-proxy.js +++ b/src/auth/auth-oauth2-proxy.js @@ -228,7 +228,7 @@ class Oauth2ProxyAuthStrategy { try { const rpt = jwtDecoder(accessToken); const jti = req['oauth_user']['jti']; // JWT ID - Unique Identifier for the token - const username = req['oauth_user']['preferred_username']; // Username included in token + const identityProvider = req['oauth_user']['identity_provider']; // Identity Provider included in token // The oauth2_proxy is handling the refresh token; so there can be a new jti logger.info( '[ns-switch] %s -> %s : %s', @@ -239,7 +239,7 @@ class Oauth2ProxyAuthStrategy { await this.assign_namespace( req.user.jti, jti, - username, + identityProvider, rpt['authorization']['permissions'][0] ); res.json({ switch: true }); diff --git a/src/authz/graphql-whitelist/httplocalhost4180managerconsumers-3a56c3.gql b/src/authz/graphql-whitelist/httplocalhost4180managerconsumers-3a56c3.gql new file mode 100644 index 000000000..d899b95c5 --- /dev/null +++ b/src/authz/graphql-whitelist/httplocalhost4180managerconsumers-3a56c3.gql @@ -0,0 +1,18 @@ + + query GetBusinessProfile($consumerId: ID!) { + BusinessProfile(serviceAccessId: $consumerId) { + institution { + legalName + address { + addressLine1 + addressLine2 + city + postal + province + country + } + isSuspended + businessTypeOther + } + } + } diff --git a/src/authz/graphql-whitelist/httplocalhost4180managerconsumers-4dbd7b.gql b/src/authz/graphql-whitelist/httplocalhost4180managerconsumers-4dbd7b.gql new file mode 100644 index 000000000..34ad7beb3 --- /dev/null +++ b/src/authz/graphql-whitelist/httplocalhost4180managerconsumers-4dbd7b.gql @@ -0,0 +1,18 @@ + + query GetBusinessProfile($consumerId: ID!) { + BusinessProfile(consumerId: $consumerId) { + institution { + legalName + address { + addressLine1 + addressLine2 + city + postal + province + country + } + isSuspended + businessTypeOther + } + } + } diff --git a/src/authz/graphql-whitelist/httplocalhost4180managerconsumers-5d889f.gql b/src/authz/graphql-whitelist/httplocalhost4180managerconsumers-5d889f.gql new file mode 100644 index 000000000..0686f8b1c --- /dev/null +++ b/src/authz/graphql-whitelist/httplocalhost4180managerconsumers-5d889f.gql @@ -0,0 +1,29 @@ + + query GetAccessRequests { + allAccessRequestsByNamespace(where: { isComplete_not: true }) { + id + name + additionalDetails + communication + createdAt + requestor { + name + } + application { + name + } + productEnvironment { + id + name + additionalDetailsToRequest + product { + name + } + } + serviceAccess { + consumer { + id + } + } + } + } diff --git a/src/authz/graphql-whitelist/httplocalhost4180managerconsumers-f5a769.gql b/src/authz/graphql-whitelist/httplocalhost4180managerconsumers-f5a769.gql new file mode 100644 index 000000000..b647552e4 --- /dev/null +++ b/src/authz/graphql-whitelist/httplocalhost4180managerconsumers-f5a769.gql @@ -0,0 +1,16 @@ + + query RequestDetailsBusinessProfile($consumerId: ID!) { + BusinessProfile(consumerId: $consumerId) { + institution { + legalName + address { + addressLine1 + addressLine2 + city + postal + province + country + } + } + } + } diff --git a/src/authz/matrix.csv b/src/authz/matrix.csv index 0d89c4233..86c618eee 100644 --- a/src/authz/matrix.csv +++ b/src/authz/matrix.csv @@ -67,9 +67,9 @@ NS MANAGER,,getNamespaceConsumerAccess,,,,,,,api-owner,,,allow, NS MANAGER,,allConsumerGroupLabels,,,,,,,api-owner,,,allow, NS MANAGER,,allConsumerScopesAndRoles,,,,,,,api-owner,,,allow, NS MANAGER,,getConsumerProdEnvAccess,,,,,,,api-owner,,,allow, -NS MANAGER,,allProductsByNamespace,,,,,,,api-owner,,,allow, -NS MANAGER,,allGatewayServicesByNamespace,,,,,,,api-owner,,,allow, -NS MANAGER,,getFilteredNamespaceConsumers,,,,,,,api-owner,,,allow, +NS MANAGER,,allProductsByNamespace,,,,,,,api-owner,,,allow,filterByUserNS +NS MANAGER,,allGatewayServicesByNamespace,,,,,,,api-owner,,,allow,filterByUserNS +NS MANAGER,,getFilteredNamespaceConsumers,,,,,,,api-owner,,,allow,filterByUserNS API Owner Role Rules,,,AccessRequest,read,,,,,,,"api-owner,provider-user",allow, API Owner Role Rules,,,Activity,read,,,,,,,"api-owner,provider-user",allow,filterByUserNS API Owner Role Rules,,,Alert,,read,,,,,,"api-owner,provider-user",allow, @@ -87,7 +87,6 @@ API Owner Role Rules,,forceDeleteEnvironment,,,,,,,api-owner,,,allow, API Owner Role Rules,,,Environment,read,,,,,,,"api-owner,provider-user",allow, API Owner Role Rules,,,Environment,create,,,,,api-owner,,,allow, API Owner Role Rules,,,Environment,update,,,,,api-owner,,,allow, -,,,,,,,,,,,,, API Owner Role Rules,,,Label,read,,,,,api-owner,,,allow, API Owner Role Rules,,,GatewayConsumer,,"read,update,delete",,,,api-owner,,,allow, API Owner Role Rules,,,GatewayConsumer,create,,,,,api-owner,,,allow, @@ -199,8 +198,7 @@ API Owner Role Rules,,allGatewayRoutesByNamespace,,,,,,,,,"api-owner,provider-us API Owner Role Rules,,allProductsByNamespace,,,,,,,,,"api-owner,provider-user",allow,filterByUserNS API Owner Role Rules,,allNamespaceServiceAccounts,,,,,,,,,"api-owner,provider-user",allow,filterByUserNS API Owner Role Rules,,allCredentialIssuersByNamespace,,,,,,,,,"api-owner,provider-user",allow,filterByUserNS -,,allProductsByNamespace,,,,,,,,,portal-user,allow, -,,,,,,,,,,,,, +Portal User or Guest,,allProductsByNamespace,,,,,,,,,portal-user,allow,filterByUserNS Portal User or Guest,,allDiscoverableContents,,,,,,,,,"portal-user,guest",allow,filterByNamespaceOrPublic Portal User or Guest,,allDiscoverableProducts,,,,,,,,,"portal-user,guest",allow,filterByActiveEnvironment Portal User or Guest,,allProducts,,,,,,,,,"portal-user,guest",allow, diff --git a/src/controllers/v2/NamespaceDirectoryController.ts b/src/controllers/v2/NamespaceDirectoryController.ts index 0519946d7..108286631 100644 --- a/src/controllers/v2/NamespaceDirectoryController.ts +++ b/src/controllers/v2/NamespaceDirectoryController.ts @@ -12,6 +12,7 @@ import { KeystoneService } from '../ioc/keystoneInjector'; import { inject, injectable } from 'tsyringe'; import { transform, transformSetAnonymous } from './DirectoryController'; import { gql } from 'graphql-request'; +import { strict as assert } from 'assert'; @injectable() @Route('/namespaces/{ns}/directory') @@ -47,9 +48,11 @@ export class NamespaceDirectoryController extends Controller { variables: { id }, }); - if (result.data.allProductsByNamespace.length == 0) { - return null; - } + assert.strictEqual( + result.data.allProductsByNamespace?.length === 1, + true, + 'Dataset not found' + ); return transform( transformSetAnonymous(result.data.allProductsByNamespace) diff --git a/src/lists/extensions/BusinessProfile.ts b/src/lists/extensions/BusinessProfile.ts index a8c024b3e..610fb0de0 100644 --- a/src/lists/extensions/BusinessProfile.ts +++ b/src/lists/extensions/BusinessProfile.ts @@ -1,9 +1,10 @@ import { EnforcementPoint } from '../../authz/enforcement'; - -import { lookupCredentialReferenceByServiceAccess } from '../../services/keystone'; - import { ConfigService } from '../../services/config.service'; import { BCeIDService } from '../../services/bceid/bceid.service'; +import { getNamespaceConsumerAccess } from '../../services/workflow'; +import { Logger } from '../../logger'; + +const logger = Logger('List.Ext.BusProfile'); const typeBusinessProfile = ` type BusinessProfile { @@ -58,29 +59,24 @@ module.exports = { ], queries: [ { - schema: 'BusinessProfile(serviceAccessId: ID!): BusinessProfile', + schema: 'BusinessProfile(consumerId: ID!): BusinessProfile', resolver: async ( item: any, - args: any, + { consumerId }: any, context: any, info: any, { query, access }: any ) => { - const serviceAccess = await lookupCredentialReferenceByServiceAccess( + const namespace = context.req.user.namespace; + const consumer = await getNamespaceConsumerAccess( context, - args.serviceAccessId + namespace, + consumerId ); + logger.debug('%s -> %j', consumerId, consumer.owner); - const fullUsername = serviceAccess.application?.owner.username; - - if ( - fullUsername != null && - fullUsername.endsWith('@bceid-business') - ) { - const username = fullUsername.substring( - 0, - fullUsername.lastIndexOf('@') - ); + if (consumer.owner?.provider === 'bceid-business') { + const username = consumer.owner?.providerUsername; const bc = new BCeIDService(new ConfigService()); return await bc.getAccountDetails(username); diff --git a/src/nextapp/components/access-request/access-requests-list.tsx b/src/nextapp/components/access-request/access-requests-list.tsx index 927f2d17a..fb6fbf4c0 100644 --- a/src/nextapp/components/access-request/access-requests-list.tsx +++ b/src/nextapp/components/access-request/access-requests-list.tsx @@ -60,6 +60,11 @@ const query = gql` name } } + serviceAccess { + consumer { + id + } + } } } `; diff --git a/src/nextapp/components/access-request/business-details.tsx b/src/nextapp/components/access-request/business-details.tsx index 168f13151..8c64e838e 100644 --- a/src/nextapp/components/access-request/business-details.tsx +++ b/src/nextapp/components/access-request/business-details.tsx @@ -11,7 +11,7 @@ interface BusinessDetailsProps { const BusinessDetails: React.FC = ({ id }) => { const { data } = useApi(['businessAddress', id], { query, - variables: { serviceAccessId: id }, + variables: { consumerId: id }, }); const { institution } = data?.BusinessProfile; let legalName = 'N/A'; @@ -37,8 +37,8 @@ const BusinessDetails: React.FC = ({ id }) => { export default BusinessDetails; const query = gql` - query RequestDetailsBusinessProfile($serviceAccessId: ID!) { - BusinessProfile(serviceAccessId: $serviceAccessId) { + query RequestDetailsBusinessProfile($consumerId: ID!) { + BusinessProfile(consumerId: $consumerId) { institution { legalName address { diff --git a/src/nextapp/components/access-request/request-details.tsx b/src/nextapp/components/access-request/request-details.tsx index c1941c8c6..1cd616bf3 100644 --- a/src/nextapp/components/access-request/request-details.tsx +++ b/src/nextapp/components/access-request/request-details.tsx @@ -105,7 +105,7 @@ const RequestDetails: React.FC = ({ data, labels }) => { } > - + diff --git a/src/nextapp/components/business-profile/business-profile.tsx b/src/nextapp/components/business-profile/business-profile.tsx index c6df4906f..6d9d25d21 100644 --- a/src/nextapp/components/business-profile/business-profile.tsx +++ b/src/nextapp/components/business-profile/business-profile.tsx @@ -6,18 +6,18 @@ import { BoxProps } from '@chakra-ui/layout'; import BusinessProfileContent from './business-profile-content'; interface BusinessProfileProps extends BoxProps { - serviceAccessId: string; + consumerId: string; } const BusinessProfileComponent: React.FC = ({ - serviceAccessId, + consumerId, ...props }) => { const { data, isLoading } = useApi( - ['BusinessProfile', serviceAccessId], + ['BusinessProfile', consumerId], { query, - variables: { serviceAccessId }, + variables: { consumerId }, }, { suspense: false } ); @@ -34,8 +34,8 @@ const BusinessProfileComponent: React.FC = ({ export default BusinessProfileComponent; const query = gql` - query GetBusinessProfile($serviceAccessId: ID!) { - BusinessProfile(serviceAccessId: $serviceAccessId) { + query GetBusinessProfile($consumerId: ID!) { + BusinessProfile(consumerId: $consumerId) { institution { legalName address { diff --git a/src/nextapp/pages/manager/consumers/[id].tsx b/src/nextapp/pages/manager/consumers/[id].tsx index f2c841218..191e0a678 100644 --- a/src/nextapp/pages/manager/consumers/[id].tsx +++ b/src/nextapp/pages/manager/consumers/[id].tsx @@ -131,6 +131,7 @@ const ConsumerPage: React.FC< return <>; } + return ( <> @@ -144,7 +145,11 @@ const ConsumerPage: React.FC< /> Grant Access} + actions={ + + } breadcrumb={breadcrumbs([ { href: '/manager/consumers', text: 'Consumers' }, { @@ -200,7 +205,7 @@ const ConsumerPage: React.FC< - + diff --git a/src/nextapp/pages/manager/requests/[id].tsx b/src/nextapp/pages/manager/requests/[id].tsx deleted file mode 100644 index 0a9403bf8..000000000 --- a/src/nextapp/pages/manager/requests/[id].tsx +++ /dev/null @@ -1,387 +0,0 @@ -import * as React from 'react'; -import api, { useApi } from '@/shared/services/api'; -import { - Box, - Button, - Checkbox, - CheckboxGroup, - Container, - Flex, - FormControl, - FormLabel, - Heading, - Tabs, - TabList, - TabPanels, - Tab, - TabPanel, - Badge, - Text, - Avatar, - Grid, - GridItem, - HStack, - ButtonGroup, - Icon, - Wrap, - WrapItem, -} from '@chakra-ui/react'; -import PageHeader from '@/components/page-header'; -import format from 'date-fns/format'; -import { dehydrate } from 'react-query/hydration'; -import { QueryClient } from 'react-query'; -import { GetServerSideProps, InferGetServerSidePropsType } from 'next'; -import Head from 'next/head'; -import { Query } from '@/shared/types/query.types'; -import { RequestControls } from '@/shared/types/app.types'; -import { gql } from 'graphql-request'; -import ControlsList from '@/components/controls-list'; -import IpRestriction from '@/components/controls/ip-restriction'; -import RateLimiting from '@/components/controls/rate-limiting'; -import ModelIcon from '@/components/model-icon/model-icon'; -import RequestActions from '@/components/request-actions'; -import BusinessProfile from '@/components/business-profile'; -import ActivityList from '@/components/activity-list'; -import breadcrumbs from '@/components/ns-breadcrumb'; -import isNotBlank from '@/shared/isNotBlank'; - -export const getServerSideProps: GetServerSideProps = async (context) => { - const { id } = context.params; - const queryClient = new QueryClient(); - - await queryClient.prefetchQuery( - ['accessRequest', id], - async () => - await api( - query, - { id, rid: id }, - { - headers: context.req.headers as HeadersInit, - } - ) - ); - - return { - props: { - id, - dehydratedState: dehydrate(queryClient), - }, - }; -}; - -const AccessRequestPage: React.FC< - InferGetServerSidePropsType -> = ({ id }) => { - const queryKey = ['accessRequest', id]; - const { data } = useApi( - queryKey, - { - query, - variables: { - id, - rid: id, - }, - }, - { suspense: false } - ); - const isComplete = - data.AccessRequest?.isIssued && data.AccessRequest?.isApproved; - - const breadcrumb = breadcrumbs([ - { href: '/manager/consumers', text: 'Consumer Requests' }, - ]); - const { plugins } = data?.AccessRequest?.controls - ? JSON.parse(data.AccessRequest.controls) - : []; - - const controls: RequestControls = { - ...{ defaultClientScopes: [] }, - ...(data?.AccessRequest?.controls - ? JSON.parse(data.AccessRequest.controls) - : {}), - }; - - const availableScopes = data?.AccessRequest?.productEnvironment - .credentialIssuer?.availableScopes - ? JSON.parse( - data?.AccessRequest.productEnvironment.credentialIssuer?.availableScopes - ) - : []; - - const clientRoles = data?.AccessRequest.productEnvironment.credentialIssuer - ?.clientRoles - ? JSON.parse( - data?.AccessRequest.productEnvironment.credentialIssuer?.clientRoles - ) - : []; - - const setScopes = (scopes: string[]) => { - controls['defaultClientScopes'] = scopes; - }; - const setRoles = (roles: string[]) => { - controls['roles'] = roles; - }; - - const requestor = data?.AccessRequest?.requestor; - - return data.AccessRequest ? ( - <> - - {`Access Request | ${data.AccessRequest.name}`} - - - - ) - } - title={ - - - {data.AccessRequest.name} - - } - > - - - {isComplete ? 'Complete' : 'Pending'} - - - - {'Requesting access to '} - - {data?.AccessRequest.productEnvironment?.product.name} - - {' on '}{' '} - - {format( - new Date(data?.AccessRequest.createdAt), - 'LLL do, yyyy' - )} - - - - - - - - Controls - Permissions - Comments - Activity - - - - - - - - - - - - - - Scopes - setScopes(d)}> - - {availableScopes.map((r) => ( - - - {r} - - - ))} - - - - - Roles - setRoles(d)}> - - {clientRoles.map((r) => ( - - - {r} - - - ))} - - - - - - {isNotBlank( - data?.AccessRequest.productEnvironment - ?.additionalDetailsToRequest - ) ? ( - - - Instructions for Requester: - - { - data?.AccessRequest.productEnvironment - ?.additionalDetailsToRequest - } - - - - ) : ( - - No Comments Requested. - - )} - - - Requestor Comments: - {data?.AccessRequest.additionalDetails} - - - - - - - - - - - - Requestor - - - - - - {requestor.name} - - {requestor.username} - - {requestor.email} - - - - - - Environment - - - {data?.AccessRequest.productEnvironment?.name} - - - Application - - {data?.AccessRequest.application?.name} - - - - - - - - ) : ( - <> - ); -}; - -export default AccessRequestPage; - -const query = gql` - query GetAccessRequest($id: ID!, $rid: String!) { - AccessRequest(where: { id: $id }) { - id - name - isApproved - isIssued - controls - additionalDetails - createdAt - requestor { - name - username - email - } - application { - name - } - serviceAccess { - id - consumer { - id - username - plugins { - id - name - extForeignKey - config - service { - id - name - extForeignKey - } - route { - id - name - extForeignKey - } - } - } - } - productEnvironment { - name - additionalDetailsToRequest - product { - name - } - credentialIssuer { - availableScopes - clientRoles - } - } - } - - allActivities(sortBy: createdAt_DESC, where: { refId: $rid }) { - id - type - name - action - result - message - context - refId - namespace - extRefId - createdAt - actor { - name - username - } - } - } -`; diff --git a/src/nextapp/pages/manager/requests/index.tsx b/src/nextapp/pages/manager/requests/index.tsx deleted file mode 100644 index b6ffbfc7d..000000000 --- a/src/nextapp/pages/manager/requests/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from 'react'; -import { Box, Container, Divider, Text } from '@chakra-ui/react'; -import ClientRequest from '@/components/client-request'; -import PageHeader from '@/components/page-header'; -import ServicesList from '@/components/services-list'; -import { useAuth /*, withAuth*/ } from '@/shared/services/auth'; -import SearchInput from '@/components/search-input'; -import { FaCaretSquareUp, FaFilter } from 'react-icons/fa'; -import ServicesFilters from '@/components/services-list/services-filters'; - -const AccessRequestsPage: React.FC = () => { - return ( - - - - List of pending access requests to services that you provide. Access - requests can be initiated by an API Owner, or they can be requested by - a Developer. - - - - - Access - - - ); -}; - -export default AccessRequestsPage; diff --git a/src/nextapp/shared/types/query.types.ts b/src/nextapp/shared/types/query.types.ts index 2ebcd0e56..ad7d441d9 100644 --- a/src/nextapp/shared/types/query.types.ts +++ b/src/nextapp/shared/types/query.types.ts @@ -7364,7 +7364,7 @@ export type QueryAllDiscoverableContentsArgs = { export type QueryBusinessProfileArgs = { - serviceAccessId: Scalars['ID']; + consumerId: Scalars['ID']; }; @@ -7726,7 +7726,6 @@ export type Mutation = { updateConsumerScopeAssignment?: Maybe; regenerateCredentials?: Maybe; createNamespace?: Maybe; - deleteNamespace?: Maybe; forceDeleteNamespace?: Maybe; createServiceAccount?: Maybe; createUmaPolicy?: Maybe; @@ -8433,6 +8432,7 @@ export type MutationCreateUsersArgs = { }; + export type MutationUpdateUserArgs = { id: Scalars['ID']; data?: Maybe; @@ -8514,6 +8514,7 @@ export type MutationUpdateConsumerRoleAssignmentArgs = { prodEnvId: Scalars['ID']; consumerUsername: Scalars['String']; roleName: Scalars['String']; + grant: Scalars['Boolean']; }; @@ -8536,11 +8537,6 @@ export type MutationCreateNamespaceArgs = { }; -export type MutationDeleteNamespaceArgs = { - namespace: Scalars['String']; -}; - - export type MutationForceDeleteNamespaceArgs = { namespace: Scalars['String']; force: Scalars['Boolean']; diff --git a/src/package.json b/src/package.json index 4914d4c2d..c16b521e2 100644 --- a/src/package.json +++ b/src/package.json @@ -37,7 +37,7 @@ "dev2": "cross-env NODE_ENV=development DISABLE_LOGGING=true keystone --entry=dist/index.js", "mock-server": "nodemon ./test/mock-server/server.js", "ks-build": "cross-env NODE_ENV=production keystone build --entry=dist/server.js --out=dist2", - "start": "cross-env NODE_ENV=development keystone start --entry=dist/server.js", + "start": "cross-env NODE_ENV=production keystone start --entry=dist/server.js", "build": "npm-run-all copy-assets tsoa-build-v1 tsoa-build-v2 ts-build ks-build copy-keystone-admin-assets", "create-tables": "cross-env CREATE_TABLES=true keystone create-tables --entry=dist/server.js", "lint": "eslint ./nextapp --ext .ts,.tsx --quiet", diff --git a/src/server.ts b/src/server.ts index 79b18ab86..0f96c49a2 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,17 +1,12 @@ /// /// import 'reflect-metadata'; -import express from 'express'; -import request from 'graphql-request'; +const { formatError } = require('./services/keystone_overrides/formatError'); const { Keystone } = require('@keystonejs/keystone'); -const { Checkbox, Password, Select } = require('@keystonejs/fields'); -//import Oauth2ProxyAuthStrategy from './auth/auth-oauth2-proxy' const { Oauth2ProxyAuthStrategy } = require('./auth/auth-oauth2-proxy'); const { PasswordAuthStrategy } = require('@keystonejs/auth-password'); const { AdminUIApp } = require('@keystonejs/app-admin-ui'); const { generate } = require('@graphql-codegen/cli'); -//const { AdminUIApp } = require('@keystone-next/admin-ui'); -const { StaticApp } = require('@keystonejs/app-static'); const { NextApp } = require('@keystonejs/app-next'); const { ApiProxyApp } = require('./api-proxy'); const { ApiGraphqlWhitelistApp } = require('./api-graphql-whitelist'); @@ -20,20 +15,12 @@ const { MaintenanceApp } = require('./api-maintpage'); const { ApiOpenapiApp } = require('./api-openapi'); const { ApiDSProxyApp } = require('./api-proxy-ds'); -var Keycloak = require('keycloak-connect'); - const initialiseData = require('./initial-data'); -const { startAuthedSession } = require('@keystonejs/session'); const session = require('express-session'); -const MongoStore = require('connect-mongo')(session); const redis = require('redis'); let RedisStore = require('connect-redis')(session); -const { Strategy, Issuer, Client } = require('openid-client'); - -const { staticRoute, staticPath, distDir } = require('./config'); - const { putFeedWorker, deleteFeedWorker, @@ -41,10 +28,7 @@ const { } = require('./batch/feed-worker'); const { Retry } = require('./services/tasked'); -const { - FieldEnforcementPoint, - EnforcementPoint, -} = require('./authz/enforcement'); +const { EnforcementPoint } = require('./authz/enforcement'); const { loadRulesAndWatch } = require('./authz/enforcement'); @@ -248,13 +232,6 @@ const authStrategy = }); const { pages } = require('./admin-hooks.js'); -//const tasked = require('./services/tasked'); - -const { - checkWhitelist, - loadWhitelistAndWatch, - addToWhitelist, -} = require('./authz/whitelist'); const apps = [ new ApiHealthApp(state), @@ -262,6 +239,22 @@ const apps = [ new MaintenanceApp(), new ApiGraphqlWhitelistApp({ apiPath, + apollo: { + formatError: (err: any) => { + logger.error('GraphQL Error: %s', err); + const error = formatError(err); + + const data = error.extensions?.exception?.response?.data; + if (error.extensions?.exception) { + logger.warn('Removing exception details from error response'); + delete error.extensions['exception']; + } + if (data) { + logger.error(' %s', data); + } + return error; + }, + }, }), new AdminUIApp({ name: PROJECT_NAME, @@ -271,11 +264,6 @@ const apps = [ authStrategy, pages: pages, enableDefaultRoute: false, - isAccessAllowed: (user: any) => { - // console.log('isAllowed?'); - // console.log(JSON.stringify(user)); - return true; - }, }), new ApiDSProxyApp({ url: process.env.SSR_API_ROOT }), new ApiProxyApp({ gwaApiUrl: process.env.GWA_API_URL }), diff --git a/src/services/keystone/credential-issuer.ts b/src/services/keystone/credential-issuer.ts index 4f28db247..efb19f5cd 100644 --- a/src/services/keystone/credential-issuer.ts +++ b/src/services/keystone/credential-issuer.ts @@ -28,7 +28,7 @@ export async function lookupCredentialIssuerById( variables: { id: id }, }); if (result.errors) { - logger.error('[lookupCredentialIssuerById] %j', result); + logger.error('[lookupCredentialIssuerById] %j', result.errors); } return result.data.CredentialIssuer; } @@ -51,11 +51,11 @@ export function updateEnvironmentDetails( const existingIndex = existing.findIndex((env) => { return env.environment === upd.environment; }); - if (existingIndex === -1) { - logger.debug('Adding %s', upd.environment); - } else { - logger.debug('Replacing %s', upd.environment); - } + // if (existingIndex === -1) { + // logger.debug('Adding %s', upd.environment); + // } else { + // logger.debug('Replacing %s', upd.environment); + // } newList.push(upd); } }); diff --git a/src/services/keystone/product-environment.ts b/src/services/keystone/product-environment.ts index dc7b1d485..375bf4617 100644 --- a/src/services/keystone/product-environment.ts +++ b/src/services/keystone/product-environment.ts @@ -147,10 +147,10 @@ export async function lookupEnvironmentAndIssuerUsingWhereClause( }`, variables: { where }, }); - logger.debug( - '[lookupEnvironmentAndIssuerUsingWhereClause] result %j', - result - ); + // logger.debug( + // '[lookupEnvironmentAndIssuerUsingWhereClause] result %j', + // result + // ); assert.strictEqual( result.data.allEnvironments.length, 1, @@ -230,7 +230,7 @@ export async function lookupEnvironmentAndIssuerById(context: any, id: string) { }`, variables: { id: id }, }); - logger.debug('[lookupEnvironmentAndIssuerById] result %j', result); + // logger.debug('[lookupEnvironmentAndIssuerById] result %j', result); assert.strictEqual( result.data.Environment == null, false, diff --git a/src/services/keystone/service-access.ts b/src/services/keystone/service-access.ts index 92523d6b5..62492a8d1 100644 --- a/src/services/keystone/service-access.ts +++ b/src/services/keystone/service-access.ts @@ -300,6 +300,8 @@ export async function lookupLabeledServiceAccessesForNamespace( name owner { name + provider + providerUsername username email } diff --git a/src/services/keystone/types.ts b/src/services/keystone/types.ts index 2ebcd0e56..ad7d441d9 100644 --- a/src/services/keystone/types.ts +++ b/src/services/keystone/types.ts @@ -7364,7 +7364,7 @@ export type QueryAllDiscoverableContentsArgs = { export type QueryBusinessProfileArgs = { - serviceAccessId: Scalars['ID']; + consumerId: Scalars['ID']; }; @@ -7726,7 +7726,6 @@ export type Mutation = { updateConsumerScopeAssignment?: Maybe; regenerateCredentials?: Maybe; createNamespace?: Maybe; - deleteNamespace?: Maybe; forceDeleteNamespace?: Maybe; createServiceAccount?: Maybe; createUmaPolicy?: Maybe; @@ -8433,6 +8432,7 @@ export type MutationCreateUsersArgs = { }; + export type MutationUpdateUserArgs = { id: Scalars['ID']; data?: Maybe; @@ -8514,6 +8514,7 @@ export type MutationUpdateConsumerRoleAssignmentArgs = { prodEnvId: Scalars['ID']; consumerUsername: Scalars['String']; roleName: Scalars['String']; + grant: Scalars['Boolean']; }; @@ -8536,11 +8537,6 @@ export type MutationCreateNamespaceArgs = { }; -export type MutationDeleteNamespaceArgs = { - namespace: Scalars['String']; -}; - - export type MutationForceDeleteNamespaceArgs = { namespace: Scalars['String']; force: Scalars['Boolean']; diff --git a/src/services/keystone_overrides/formatError.js b/src/services/keystone_overrides/formatError.js new file mode 100644 index 000000000..a382f5fbc --- /dev/null +++ b/src/services/keystone_overrides/formatError.js @@ -0,0 +1,157 @@ +const { + createError, + formatError: _formatError, + isInstance: isApolloErrorInstance, +} = require('apollo-errors'); +const ensureError = require('ensure-error'); +const { serializeError } = require('serialize-error'); +const StackUtils = require('stack-utils'); +const cuid = require('cuid'); +const { omit } = require('@keystonejs/utils'); +const { graphqlLogger } = require('./logger'); + +const stackUtil = new StackUtils({ + cwd: process.cwd(), + internals: StackUtils.nodeInternals(), +}); + +const cleanError = (maybeError) => { + if (!maybeError.stack) { + return maybeError; + } + maybeError.stack = stackUtil.clean(maybeError.stack); + return maybeError; +}; + +const NestedError = createError('NestedError', { + message: 'Nested errors occurred', + options: { + showPath: true, + }, +}); + +const safeFormatError = (error) => { + const formattedError = _formatError(error, true); + if (formattedError) { + return cleanError(formattedError); + } + return serializeError(cleanError(error)); +}; + +const duplicateError = (error, ignoreKeys = []) => { + const newError = new error.constructor(error.message); + if (error.stack) { + if (isApolloErrorInstance(error)) { + newError._stack = error.stack; + } else { + newError.stack = error.stack; + } + } + if (error.code) { + newError.code = error.code; + } + return Object.assign(newError, omit(error, ignoreKeys)); +}; + +const flattenNestedErrors = (error) => + (error.errors || []).reduce( + (errors, nestedError) => [ + ...errors, + ...[ + duplicateError(nestedError, ['errors']), + ...flattenNestedErrors(nestedError), + ].map((flattenedError) => { + // Ensure the path is complete + if (Array.isArray(error.path) && Array.isArray(flattenedError.path)) { + flattenedError.path = [...error.path, ...flattenedError.path]; + } + return flattenedError; + }), + ], + [] + ); + +const formatError = (error) => { + const { originalError } = error; + if (originalError && !originalError.path) { + originalError.path = error.path; + } + + try { + // For correlating user error reports with logs + error.uid = cuid(); + + if (isApolloErrorInstance(originalError)) { + // log internalData to stdout but not include it in the formattedError + // TODO: User pino for logging + graphqlLogger.info({ + type: 'error', + data: originalError.data, + internalData: originalError.internalData, + }); + } else { + const exception = + originalError || (error.extensions && error.extensions.exception); + if (exception) { + const pinoError = { + ...omit(serializeError(error), ['extensions']), + ...omit(exception, ['name', 'model', 'stacktrace']), + stack: stackUtil.clean(exception.stacktrace || exception.stack), + }; + + if (pinoError.errors) { + pinoError.errors = flattenNestedErrors(exception).map( + safeFormatError + ); + } + + graphqlLogger.error(pinoError); + } else { + const errorOutput = serializeError(ensureError(error)); + errorOutput.stack = stackUtil.clean(errorOutput.stack); + graphqlLogger.error(errorOutput); + } + } + } catch (formatErrorError) { + // Something went wrong with formatting above, so we log the errors + graphqlLogger.error(serializeError(ensureError(error))); + graphqlLogger.error(serializeError(ensureError(formatErrorError))); + + return safeFormatError(error); + } + + try { + let formattedError; + + // Support throwing multiple errors + if (originalError && originalError.errors) { + const multipleErrorContainer = new NestedError({ + data: { + // Format (aka; serialize) the error + errors: flattenNestedErrors(originalError).map(safeFormatError), + }, + }); + + formattedError = safeFormatError(multipleErrorContainer); + } else { + formattedError = safeFormatError(error); + } + + if (error.uid) { + formattedError.uid = error.uid; + } + + return formattedError; + } catch (formatErrorError) { + // NOTE: We don't log again here as we assume the earlier try/catch + // correctly logged + + // Return the original error as a fallback so the client gets at + // least some useful info + return safeFormatError(error); + } +}; + +module.exports = { + formatError, +}; diff --git a/src/services/keystone_overrides/logger.js b/src/services/keystone_overrides/logger.js new file mode 100644 index 000000000..78253fb5e --- /dev/null +++ b/src/services/keystone_overrides/logger.js @@ -0,0 +1,9 @@ +const falsey = require('falsey'); +const pino = require('pino'); + +module.exports = { + graphqlLogger: pino({ + name: 'graphql', + enabled: falsey(process.env.DISABLE_LOGGING), + }), +}; diff --git a/src/services/workflow/consumer-management.ts b/src/services/workflow/consumer-management.ts index 1caf59b6f..e8342d245 100644 --- a/src/services/workflow/consumer-management.ts +++ b/src/services/workflow/consumer-management.ts @@ -228,7 +228,8 @@ export async function getNamespaceConsumerAccess( owner: { id: serviceAccess.application?.owner.id, name: serviceAccess.application?.owner.name, - username: serviceAccess.application?.owner.username, + provider: serviceAccess.application?.owner.provider, + providerUsername: serviceAccess.application?.owner.providerUsername, email: serviceAccess.application?.owner.email, }, labels: labels.map( diff --git a/src/services/workflow/get-consumer-authz.ts b/src/services/workflow/get-consumer-authz.ts index a096e4cb2..93b9c0ea2 100644 --- a/src/services/workflow/get-consumer-authz.ts +++ b/src/services/workflow/get-consumer-authz.ts @@ -69,7 +69,11 @@ export async function getConsumerAuthz( } as any; } } catch (err: any) { - logger.error('[getConsumerAuthz] (%j) Error %s', consumerUsername, err); + logger.error( + '[getConsumerAuthz] (%j) Error %s', + consumerUsername, + err.message + ); throw err; } } diff --git a/src/test/integrated/keystonejs/consumerManagement.ts b/src/test/integrated/keystonejs/consumerManagement.ts index c919b0db3..a7b30baf1 100644 --- a/src/test/integrated/keystonejs/consumerManagement.ts +++ b/src/test/integrated/keystonejs/consumerManagement.ts @@ -30,6 +30,9 @@ import { import { doFiltering } from '../../../services/workflow/consumer-filters'; import { syncPlugins } from '../../../services/workflow/consumer-plugins'; import { lookupConsumerPlugins } from '../../../services/keystone'; +import { Logger } from '../../../logger'; + +const logger = Logger('test.intg'); (async () => { const keystone = await InitKeystone(); @@ -93,24 +96,32 @@ import { lookupConsumerPlugins } from '../../../services/keystone'; o(consumer); } + if (true) { + const cid = '62f55c9cc56563de1c514e1b'; + const id = '629fccaf76e9e65444ca6a43'; + const res = await getConsumerProdEnvAccess(ctx, ns, cid, id).catch((e) => { + logger.error('Caught error: %s', e.message); + }); + o(res); + } if (false) { const id = '62a18b772da3cdea467b10fd'; const consumerAccess = await getNamespaceConsumerAccess(ctx, ns, id); o(consumerAccess); // only enriches with Authorization and Access Request details - // const envPromises = consumerAccess.prodEnvAccess - // .filter((a) => a.plugins.length > 0) - // .map(async (p: any) => { - // const res = await getConsumerProdEnvAccess( - // ctx, - // ns, - // consumerAccess.consumer.id, - // p.environment.id - // ); - // o(res); - // }); - // await Promise.all(envPromises); + const envPromises = consumerAccess.prodEnvAccess + .filter((a) => a.plugins.length > 0) + .map(async (p: any) => { + const res = await getConsumerProdEnvAccess( + ctx, + ns, + consumerAccess.consumer.id, + p.environment.id + ); + o(res); + }); + await Promise.all(envPromises); } if (false) { const id = '62a18b772da3cdea467b10fd'; diff --git a/src/tools/copyKeystoneAdminAssets.js b/src/tools/copyKeystoneAdminAssets.js index 8cfbf5afa..e41423e5e 100644 --- a/src/tools/copyKeystoneAdminAssets.js +++ b/src/tools/copyKeystoneAdminAssets.js @@ -1,6 +1,6 @@ const shell = require('shelljs'); -shell.cp( "-R", ["dist2"], "dist/" ) -shell.cp( "-R", ["dist2/"], "dist/" ) -shell.cp( "-R", ["dist2/*"], "dist/" ) -shell.rm('-rf', 'dist2') +shell.cp('-R', ['dist2'], 'dist/'); +shell.cp('-R', ['dist2/'], 'dist/'); +shell.cp('-R', ['dist2/*'], 'dist/'); +shell.rm('-rf', 'dist2');