From 5b35ede6aaf732d6486340a5892cfc7f0e515429 Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Wed, 13 Apr 2022 14:05:36 -0700 Subject: [PATCH 1/9] fix org access check on v2 api --- src/controllers/v1/openapi.yaml | 2 +- src/controllers/v2/OrganizationController.ts | 3 ++- src/controllers/v2/openapi.yaml | 2 +- .../org-groups-list/org-groups-list.tsx | 17 +++++++++++++++-- src/services/keycloak/user-service.ts | 2 +- .../org-groups/group-converter-utils.ts | 16 ++++++++++++++-- .../org-groups/group-converter-utils.test.js | 15 +++++++++++++++ 7 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/controllers/v1/openapi.yaml b/src/controllers/v1/openapi.yaml index 114a692e8..601c318b1 100644 --- a/src/controllers/v1/openapi.yaml +++ b/src/controllers/v1/openapi.yaml @@ -46,7 +46,7 @@ components: scopes: {} info: title: bcgov-aps-portal - version: 0.1.0 + version: 1.1.0 description: 'API Services Portal by BC Gov API Programme Services' license: name: MIT diff --git a/src/controllers/v2/OrganizationController.ts b/src/controllers/v2/OrganizationController.ts index 9edfbcd8b..ddb89417a 100644 --- a/src/controllers/v2/OrganizationController.ts +++ b/src/controllers/v2/OrganizationController.ts @@ -37,6 +37,7 @@ import { import { getOrganizations, getOrganizationUnit } from '../../services/keystone'; import { getActivity } from '../../services/keystone/activity'; import { Activity } from './types'; +import { isParent } from '../../services/org-groups/group-converter-utils'; @injectable() @Route('/organizations') @@ -123,7 +124,7 @@ export class OrganizationController extends Controller { ): Promise { // must match either the 'name' or the leaf of the 'parent' assert.strictEqual( - org === body.name || org === leaf(body.parent), + org === body.name || isParent(body.parent, org), true, 'Organization mismatch' ); diff --git a/src/controllers/v2/openapi.yaml b/src/controllers/v2/openapi.yaml index feba4fe80..a6aa53b8c 100644 --- a/src/controllers/v2/openapi.yaml +++ b/src/controllers/v2/openapi.yaml @@ -608,7 +608,7 @@ components: bearerFormat: JWT info: title: 'APS Directory API' - version: 0.1.0 + version: 1.1.0 description: 'API Services Portal by BC Gov API Programme Services' license: name: MIT diff --git a/src/nextapp/components/org-groups-list/org-groups-list.tsx b/src/nextapp/components/org-groups-list/org-groups-list.tsx index b0552664a..ee19b76d7 100644 --- a/src/nextapp/components/org-groups-list/org-groups-list.tsx +++ b/src/nextapp/components/org-groups-list/org-groups-list.tsx @@ -35,6 +35,17 @@ const OrgGroupsList: React.FC = ({ const client = useQueryClient(); const list = data?.sort((a, b) => a.name.localeCompare(b.name)); + function camelize(str) { + return str + .replace(/(?:^\w|[A-Z]|\b\w)/g, function (word: string) { + return word.toUpperCase(); + }) + .replace(/\s+/g, ' '); + } + + const format = (s: string, i: number) => + camelize(s.split('/')[i + 1].replace(/-/g, ' ')); + return ( <> @@ -52,8 +63,10 @@ const OrgGroupsList: React.FC = ({ .map((item) => (
- {item.groups.map((g) => ( -

{g}

+ {item.groups.map((g, i) => ( + + {i > 0 && ' > '} {format(g, i)} + ))}
diff --git a/src/services/keycloak/user-service.ts b/src/services/keycloak/user-service.ts index a6ec6fbe6..84371ae61 100644 --- a/src/services/keycloak/user-service.ts +++ b/src/services/keycloak/user-service.ts @@ -42,7 +42,7 @@ export class KeycloakUserService { clientId: string, clientSecret: string ): Promise { - logger.debug('[login] %s:%s', clientId, clientSecret); + logger.debug('[login] %s', clientId); await this.kcAdminClient .auth({ diff --git a/src/services/org-groups/group-converter-utils.ts b/src/services/org-groups/group-converter-utils.ts index 324bd22a3..de52cf0a4 100644 --- a/src/services/org-groups/group-converter-utils.ts +++ b/src/services/org-groups/group-converter-utils.ts @@ -13,9 +13,16 @@ export function root(str: string) { return parts.length > 1 ? parts[1] : ''; } -export function leaf(str: string) { +/** + * + * @param str + * @param real_leaf when there is just one node and real_leaf is false, then it returns "" + * @returns + */ +export function leaf(str: string, real_leaf: boolean = false) { + const matchLength = real_leaf ? 1 : 2; const parts = str.split('/'); - return parts.length <= 2 ? '' : parts[parts.length - 1]; + return parts.length <= matchLength ? '' : parts[parts.length - 1]; } export function parent(str: string) { @@ -32,3 +39,8 @@ export function convertToOrgGroup(str: string): OrganizationGroup { parent: _leaf === '' ? '' : `/${root(str)}${parent(str)}`, }; } + +export function isParent(str: string, parent: string) { + const parts = str.split('/'); + return parts.filter((p) => p === parent).length > 0; +} diff --git a/src/test/services/org-groups/group-converter-utils.test.js b/src/test/services/org-groups/group-converter-utils.test.js index f4326afce..a458d30ac 100644 --- a/src/test/services/org-groups/group-converter-utils.test.js +++ b/src/test/services/org-groups/group-converter-utils.test.js @@ -4,6 +4,7 @@ import { root, convertToOrgGroup, } from '../../../services/org-groups'; +import { isParent } from '../../../services/org-groups/group-converter-utils'; describe('Group Access', function () { it('should get correct leaf', async function () { @@ -12,6 +13,12 @@ describe('Group Access', function () { expect(leaf('/role')).toBe(''); }); + it('should get correct true leaf', async function () { + expect(leaf('/role/parent/child', true)).toBe('child'); + expect(leaf('/role/parent', true)).toBe('parent'); + expect(leaf('/role', true)).toBe('role'); + }); + it('should get correct root', async function () { expect(root('/role/parent/child')).toBe('role'); expect(root('/role/parent')).toBe('role'); @@ -54,4 +61,12 @@ describe('Group Access', function () { expect(og.name).toBe('role'); expect(og.parent).toBe(''); }); + + it('should get correct parent', async function () { + expect(isParent('/role/parent/child', 'role')).toBe(true); + expect(isParent('/role/parent/child', 'parent')).toBe(true); + expect(isParent('/role/parent/child', 'child')).toBe(true); + expect(isParent('/role/parent/child', 'other')).toBe(false); + expect(isParent('/role', 'role')).toBe(true); + }); }); From 73c87d1190f8ff95e1db676c4a96860f8d8cf9b6 Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Fri, 15 Apr 2022 12:04:05 -0700 Subject: [PATCH 2/9] fix activity query error and gw config deletion --- src/controllers/v2/NamespaceController.ts | 9 ++- src/controllers/v2/openapi.yaml | 6 +- src/controllers/v2/routes.ts | 2 +- src/lists/extensions/Namespace.ts | 54 ++++++++----- src/nextapp/shared/types/query.types.ts | 4 +- src/services/gwaapi/gwa-service.ts | 38 ++++++++++ src/services/gwaapi/index.ts | 1 + src/services/keystone/types.ts | 4 +- src/services/utils.ts | 35 +++++++++ src/services/workflow/delete-namespace.ts | 13 ++-- src/test/integrated/gwaapi/gwaapi.ts | 88 ++++++++++++++++++++++ src/test/integrated/keystonejs/activity.ts | 2 +- 12 files changed, 225 insertions(+), 31 deletions(-) create mode 100644 src/services/gwaapi/gwa-service.ts create mode 100644 src/services/gwaapi/index.ts create mode 100644 src/test/integrated/gwaapi/gwaapi.ts diff --git a/src/controllers/v2/NamespaceController.ts b/src/controllers/v2/NamespaceController.ts index 5f4fa95bb..0fe05ba59 100644 --- a/src/controllers/v2/NamespaceController.ts +++ b/src/controllers/v2/NamespaceController.ts @@ -19,6 +19,7 @@ import { Namespace } from '../../services/keystone/types'; import { Readable } from 'stream'; import { + parseBlobString, parseJsonString, removeEmpty, transformAllRefID, @@ -183,8 +184,8 @@ export class NamespaceController extends Controller { const records = await getActivity(ctx, [ns], first > 50 ? 50 : first, skip); return records .map((o) => removeEmpty(o)) - .map((o) => transformAllRefID(o, ['blob'])) - .map((o) => parseJsonString(o, ['context', 'blob'])); + .map((o) => parseJsonString(o, ['context'])) + .map((o) => parseBlobString(o)); } } @@ -205,7 +206,9 @@ const item = gql` } permDomains permDataPlane - permProtected + permProtectedNs + org + orgUnit } } `; diff --git a/src/controllers/v2/openapi.yaml b/src/controllers/v2/openapi.yaml index a6aa53b8c..97a4e812d 100644 --- a/src/controllers/v2/openapi.yaml +++ b/src/controllers/v2/openapi.yaml @@ -350,7 +350,11 @@ components: nullable: true Namespace: properties: - permProtected: + orgUnit: + $ref: '#/components/schemas/Maybe_Scalars-at-String_' + org: + $ref: '#/components/schemas/Maybe_Scalars-at-String_' + permProtectedNs: $ref: '#/components/schemas/Maybe_Scalars-at-String_' permDataPlane: $ref: '#/components/schemas/Maybe_Scalars-at-String_' diff --git a/src/controllers/v2/routes.ts b/src/controllers/v2/routes.ts index 4812994d9..e94f48243 100644 --- a/src/controllers/v2/routes.ts +++ b/src/controllers/v2/routes.ts @@ -234,7 +234,7 @@ const models: TsoaRoute.Models = { // 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 "Namespace": { "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"permProtected":{"ref":"Maybe_Scalars-at-String_"},"permDataPlane":{"ref":"Maybe_Scalars-at-String_"},"permDomains":{"ref":"Maybe_Array_Maybe_Scalars-at-String___"},"prodEnvId":{"ref":"Maybe_Scalars-at-String_"},"scopes":{"dataType":"array","array":{"dataType":"refAlias","ref":"Maybe_UmaScope_"},"required":true},"name":{"dataType":"string","required":true},"id":{"dataType":"string","required":true},"__typename":{"dataType":"enum","enums":["Namespace"]}},"validators":{}}, + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"orgUnit":{"ref":"Maybe_Scalars-at-String_"},"org":{"ref":"Maybe_Scalars-at-String_"},"permProtectedNs":{"ref":"Maybe_Scalars-at-String_"},"permDataPlane":{"ref":"Maybe_Scalars-at-String_"},"permDomains":{"ref":"Maybe_Array_Maybe_Scalars-at-String___"},"prodEnvId":{"ref":"Maybe_Scalars-at-String_"},"scopes":{"dataType":"array","array":{"dataType":"refAlias","ref":"Maybe_UmaScope_"},"required":true},"name":{"dataType":"string","required":true},"id":{"dataType":"string","required":true},"__typename":{"dataType":"enum","enums":["Namespace"]}},"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 "DateTime": { diff --git a/src/lists/extensions/Namespace.ts b/src/lists/extensions/Namespace.ts index fd0919789..8ba352d76 100644 --- a/src/lists/extensions/Namespace.ts +++ b/src/lists/extensions/Namespace.ts @@ -31,6 +31,11 @@ import { DeleteNamespace, DeleteNamespaceValidate, } from '../../services/workflow/delete-namespace'; +import { GWAService } from '../../services/gwaapi'; +import { + camelCaseAttributes, + transformSingleValueAttributes, +} from '../../services/utils'; const typeUserContact = ` type UserContact { @@ -48,7 +53,9 @@ type Namespace { prodEnvId: String, permDomains: [String], permDataPlane: String, - permProtected: String + permProtectedNs: String, + org: String, + orgUnit: String } `; @@ -213,23 +220,34 @@ module.exports = { args.ns ); - // perm-protected-ns - // perm-domains - // perm-data-plane - - (detail as any).permProtected = - 'perm-protected-ns' in nsPermissions.attributes - ? nsPermissions.attributes['perm-protected-ns'][0] - : 'deny'; - (detail as any).permDomains = - 'perm-domains' in nsPermissions.attributes - ? nsPermissions.attributes['perm-domains'] - : []; - (detail as any).permDataPlane = - 'perm-data-plane' in nsPermissions.attributes - ? nsPermissions.attributes['perm-data-plane'][0] - : ''; - return detail; + transformSingleValueAttributes(nsPermissions.attributes, [ + 'perm-data-plane', + 'perm-protected-ns', + 'org', + 'org-unit', + ]); + + logger.debug('[namespace] %j', nsPermissions.attributes); + + const client = new GWAService(process.env.GWA_API_URL); + const defaultSettings = await client.getDefaultNamespaceSettings(); + + logger.debug('[namespace] Default Settings %j', defaultSettings); + + const merged = { + ...detail, + ...defaultSettings, + ...nsPermissions.attributes, + }; + camelCaseAttributes(merged, [ + 'perm-domains', + 'perm-data-plane', + 'perm-protected-ns', + 'org', + 'org-unit', + ]); + logger.debug('[namespace] Result %j', merged); + return merged; }, access: EnforcementPoint, }, diff --git a/src/nextapp/shared/types/query.types.ts b/src/nextapp/shared/types/query.types.ts index e40b24d70..6a746b2d3 100644 --- a/src/nextapp/shared/types/query.types.ts +++ b/src/nextapp/shared/types/query.types.ts @@ -6065,7 +6065,9 @@ export type Namespace = { prodEnvId?: Maybe; permDomains?: Maybe>>; permDataPlane?: Maybe; - permProtected?: Maybe; + permProtectedNs?: Maybe; + org?: Maybe; + orgUnit?: Maybe; }; export type NamespaceInput = { diff --git a/src/services/gwaapi/gwa-service.ts b/src/services/gwaapi/gwa-service.ts new file mode 100644 index 000000000..8a8ff5496 --- /dev/null +++ b/src/services/gwaapi/gwa-service.ts @@ -0,0 +1,38 @@ +import { checkStatus } from '../checkStatus'; +import fetch from 'node-fetch'; +import { logger } from '../../logger'; + +export class GWAService { + private gwaUrl: string; + + constructor(gwaUrl: string) { + this.gwaUrl = gwaUrl; + } + + public async getDefaultNamespaceSettings() { + const url = `${this.gwaUrl}/v2/namespaces/defaults`; + logger.debug('[getDefaultNamespaceSettings]'); + return await fetch(url, { + method: 'get', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(checkStatus) + .then((res) => res.json()); + } + + public async deleteAllGatewayConfiguration(subjectToken: string, ns: string) { + const url = `${this.gwaUrl}/v2/namespaces/${ns}`; + logger.debug('[deleteAllGatewayConfiguration] ns=%s', ns); + return await fetch(url, { + method: 'post', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer $subjectToken`, + }, + }) + .then(checkStatus) + .then((res) => res.json()); + } +} diff --git a/src/services/gwaapi/index.ts b/src/services/gwaapi/index.ts new file mode 100644 index 000000000..c1249c9d3 --- /dev/null +++ b/src/services/gwaapi/index.ts @@ -0,0 +1 @@ +export { GWAService } from './gwa-service'; diff --git a/src/services/keystone/types.ts b/src/services/keystone/types.ts index e40b24d70..6a746b2d3 100644 --- a/src/services/keystone/types.ts +++ b/src/services/keystone/types.ts @@ -6065,7 +6065,9 @@ export type Namespace = { prodEnvId?: Maybe; permDomains?: Maybe>>; permDataPlane?: Maybe; - permProtected?: Maybe; + permProtectedNs?: Maybe; + org?: Maybe; + orgUnit?: Maybe; }; export type NamespaceInput = { diff --git a/src/services/utils.ts b/src/services/utils.ts index 3ab08f35a..21155bf3a 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -14,3 +14,38 @@ export function dateRange(days = 5): string[] { return result; } + +export function transformSingleValueAttributes( + object: any, + keys: string[] +): void { + keys.forEach((k) => { + const data = object[k]; + if (data && data.length > 0) { + object[k] = data[0]; + } else { + delete object[k]; + } + }); +} + +function camelize(str: string) { + return str + .replace(/-/g, ' ') + .replace(/(?:^\w|[A-Z]|\b\w)/g, function (word: string, index: number) { + return index === 0 ? word.toLowerCase() : word.toUpperCase(); + }) + .replace(/\s+/g, ''); +} + +export function camelCaseAttributes(object: any, keys: string[]): void { + keys.forEach((k) => { + if (object[k]) { + const newKey = camelize(k); + if (newKey != k) { + object[newKey] = object[k]; + delete object[k]; + } + } + }); +} diff --git a/src/services/workflow/delete-namespace.ts b/src/services/workflow/delete-namespace.ts index 183dc71bb..7410d94a8 100644 --- a/src/services/workflow/delete-namespace.ts +++ b/src/services/workflow/delete-namespace.ts @@ -26,6 +26,8 @@ import { lookupEnvironmentsByNS } from '../keystone/product-environment'; import { FieldErrors } from 'tsoa'; import { updateActivity } from '../keystone/activity'; import { CascadeDeleteEnvironment } from './delete-environment'; +import { GWAService } from '../gwaapi'; +import getSubjectToken from '../../auth/auth-token'; const logger = Logger('wf.DeleteNamespace'); @@ -117,16 +119,17 @@ export const DeleteNamespace = async (context: any, ns: string) => { 'Invalid namespace' ); - const gwServices = await lookupServicesByNamespace(context, ns); + //const gwServices = await lookupServicesByNamespace(context, ns); const envs = await lookupEnvironmentsByNS(context, ns); const ids = envs.map((e: Environment) => e.id); - assert.strictEqual( - gwServices.length == 0, - true, - `Gateway Services still exist for this namespace.` + const gwaService = new GWAService(process.env.GWA_API_URL); + + await gwaService.deleteAllGatewayConfiguration( + getSubjectToken(context.req), + ns ); const activity = await DeleteNamespaceRecordActivity(context, ns); diff --git a/src/test/integrated/gwaapi/gwaapi.ts b/src/test/integrated/gwaapi/gwaapi.ts new file mode 100644 index 000000000..ea4413a39 --- /dev/null +++ b/src/test/integrated/gwaapi/gwaapi.ts @@ -0,0 +1,88 @@ +/* + +Wire up directly with Keycloak and use the Services + +To run: + + +npm run ts-build +export CID="" +export CSC="" +export ISSUER="" +npm run ts-watch +node dist/test/integrated/gwaapi/gwaapi.js + +*/ + +import { o } from '../util'; + +import { + camelCaseAttributes, + transformSingleValueAttributes, +} from '../../../services/utils'; + +import { GWAService } from '../../../services/gwaapi'; + +(async () => { + const client = new GWAService(process.env.GWA_API_URL); + + const result = await client.getDefaultNamespaceSettings(); + o(result); + + const detail = { + name: 'mynamespace', + }; + + if (true) { + const group: any = { + name: 'mynamespace', + attributes: { + 'perm-domains': ['.local'], + 'perm-data-plane': ['kong-dp'], + 'perm-protected-ns': ['allow'], + org: ['the-org'], + 'org-unit': ['the-org-unit'], + }, + }; + transformSingleValueAttributes(group.attributes, [ + 'perm-data-plane', + 'perm-protected-ns', + 'org', + 'org-unit', + ]); + + const merged = { + ...detail, + ...result, + ...group.attributes, + }; + o(merged); + } + if (true) { + const group: any = { + name: 'mynamespace', + attributes: {}, + }; + transformSingleValueAttributes(group.attributes, [ + 'perm-data-plane', + 'perm-protected-ns', + 'org', + 'org-unit', + ]); + + const merged = { + ...detail, + ...result, + ...group.attributes, + }; + camelCaseAttributes(merged, [ + 'perm-domains', + 'perm-data-plane', + 'perm-protected-ns', + 'org', + 'org-unit', + ]); + o(merged); + } + // console.log(await kc.listMembers('660cadef-9233-4532-ba45-5393beaddea4')); +})(); diff --git a/src/test/integrated/keystonejs/activity.ts b/src/test/integrated/keystonejs/activity.ts index 4d6a9db03..50e1cd16d 100644 --- a/src/test/integrated/keystonejs/activity.ts +++ b/src/test/integrated/keystonejs/activity.ts @@ -59,7 +59,7 @@ import { ); } - const records = await getActivity(ctx, ['orgcontrol'], 1); + const records = await getActivity(ctx, ['simple'], 5); o( records From b2046498a201912fc85b6a9d7f73ca1090526fe2 Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Fri, 15 Apr 2022 12:40:47 -0700 Subject: [PATCH 3/9] gather up request subject token for namespace deletion --- src/lists/extensions/Namespace.ts | 2 ++ src/services/workflow/delete-namespace.ts | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lists/extensions/Namespace.ts b/src/lists/extensions/Namespace.ts index 8ba352d76..2fd20f395 100644 --- a/src/lists/extensions/Namespace.ts +++ b/src/lists/extensions/Namespace.ts @@ -36,6 +36,7 @@ import { camelCaseAttributes, transformSingleValueAttributes, } from '../../services/utils'; +import getSubjectToken from '../../auth/auth-token'; const typeUserContact = ` type UserContact { @@ -498,6 +499,7 @@ module.exports = { } await DeleteNamespace( context.createContext({ skipAccessControl: true }), + getSubjectToken(context.req), args.namespace ); resourcesApi.deleteResourceSet(nsResource[0].id); diff --git a/src/services/workflow/delete-namespace.ts b/src/services/workflow/delete-namespace.ts index 7410d94a8..73241b02a 100644 --- a/src/services/workflow/delete-namespace.ts +++ b/src/services/workflow/delete-namespace.ts @@ -111,7 +111,11 @@ export const DeleteNamespaceRecordActivity = async ( return r; }; -export const DeleteNamespace = async (context: any, ns: string) => { +export const DeleteNamespace = async ( + context: any, + subjectToken: string, + ns: string +) => { logger.debug('Deleting Namespace ns=%s', ns); assert.strictEqual( typeof ns === 'string' && ns.length > 0, @@ -127,10 +131,7 @@ export const DeleteNamespace = async (context: any, ns: string) => { const gwaService = new GWAService(process.env.GWA_API_URL); - await gwaService.deleteAllGatewayConfiguration( - getSubjectToken(context.req), - ns - ); + await gwaService.deleteAllGatewayConfiguration(subjectToken, ns); const activity = await DeleteNamespaceRecordActivity(context, ns); From a3fd8629ae50abbe711e900877f62a2a7dea6884 Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Fri, 15 Apr 2022 12:55:24 -0700 Subject: [PATCH 4/9] delete gw config upd --- src/services/gwaapi/gwa-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/gwaapi/gwa-service.ts b/src/services/gwaapi/gwa-service.ts index 8a8ff5496..7b27506a7 100644 --- a/src/services/gwaapi/gwa-service.ts +++ b/src/services/gwaapi/gwa-service.ts @@ -26,7 +26,7 @@ export class GWAService { const url = `${this.gwaUrl}/v2/namespaces/${ns}`; logger.debug('[deleteAllGatewayConfiguration] ns=%s', ns); return await fetch(url, { - method: 'post', + method: 'delete', headers: { 'Content-Type': 'application/json', Authorization: `Bearer $subjectToken`, From 6ad3fbac91f17e2687aeabe953b7cfd1765ed5a3 Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Fri, 15 Apr 2022 13:14:03 -0700 Subject: [PATCH 5/9] fix gw delete for ns --- src/services/gwaapi/gwa-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/gwaapi/gwa-service.ts b/src/services/gwaapi/gwa-service.ts index 7b27506a7..bbcd48143 100644 --- a/src/services/gwaapi/gwa-service.ts +++ b/src/services/gwaapi/gwa-service.ts @@ -29,7 +29,7 @@ export class GWAService { method: 'delete', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer $subjectToken`, + Authorization: `Bearer ${subjectToken}`, }, }) .then(checkStatus) From ae85418652170909133ea10ee88fd36b418df4d1 Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Fri, 15 Apr 2022 13:35:41 -0700 Subject: [PATCH 6/9] upd deletiong response --- src/services/gwaapi/gwa-service.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/services/gwaapi/gwa-service.ts b/src/services/gwaapi/gwa-service.ts index bbcd48143..86078bdf5 100644 --- a/src/services/gwaapi/gwa-service.ts +++ b/src/services/gwaapi/gwa-service.ts @@ -31,8 +31,6 @@ export class GWAService { 'Content-Type': 'application/json', Authorization: `Bearer ${subjectToken}`, }, - }) - .then(checkStatus) - .then((res) => res.json()); + }).then(checkStatus); } } From 41177a188145c522b0aac03739be987074c2ee50 Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Fri, 15 Apr 2022 13:55:33 -0700 Subject: [PATCH 7/9] make sure kong is synced when deleting ns config --- src/lists/Activity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lists/Activity.js b/src/lists/Activity.js index e5e44bdee..cf4d0d30b 100644 --- a/src/lists/Activity.js +++ b/src/lists/Activity.js @@ -60,7 +60,7 @@ module.exports = { fieldPath, // exists only for field hooks }) { if ( - updatedItem.action === 'publish' && + (updatedItem.action === 'publish' || updatedItem.action === 'delete') && updatedItem.type === 'GatewayConfig' && updatedItem.result === 'completed' ) { From 526668e1a00f6a2eab9ea5a87800ab075d636c8c Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Tue, 19 Apr 2022 12:46:56 -0700 Subject: [PATCH 8/9] do ns check and mark decommissioned --- src/lists/extensions/Namespace.ts | 23 ++++++++++++++++++++- src/services/org-groups/namespace.ts | 23 +++++++++++++++++++++ src/services/workflow/delete-namespace.ts | 10 +++------ src/test/integrated/keycloak/groups.ts | 2 +- src/test/integrated/keystonejs/activity.ts | 3 ++- src/test/integrated/org-groups/namespace.ts | 15 +++++++++----- 6 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/lists/extensions/Namespace.ts b/src/lists/extensions/Namespace.ts index 2fd20f395..eea895dcd 100644 --- a/src/lists/extensions/Namespace.ts +++ b/src/lists/extensions/Namespace.ts @@ -37,6 +37,7 @@ import { transformSingleValueAttributes, } from '../../services/utils'; import getSubjectToken from '../../auth/auth-token'; +import { NamespaceService } from '../../services/org-groups'; const typeUserContact = ` type UserContact { @@ -357,9 +358,18 @@ module.exports = { access ); + const nsService = new NamespaceService( + envCtx.issuerEnvConfig.issuerUrl + ); + await nsService.login( + envCtx.issuerEnvConfig.clientId, + envCtx.issuerEnvConfig.clientSecret + ); + await nsService.checkNamespaceAvailable(args.namespace); + // This function gets all resources but also sets the accessToken in envCtx // which we need to create the resource set - const resourceIds = await getResourceSets(envCtx); + await getResourceSets(envCtx); const resourceApi = new UMAResourceRegistrationService( envCtx.uma2.resource_registration_endpoint, @@ -503,6 +513,17 @@ module.exports = { args.namespace ); resourcesApi.deleteResourceSet(nsResource[0].id); + + // Last thing to do is mark the Namespace group 'decommissioned' + const nsService = new NamespaceService( + envCtx.issuerEnvConfig.issuerUrl + ); + await nsService.login( + envCtx.issuerEnvConfig.clientId, + envCtx.issuerEnvConfig.clientSecret + ); + await nsService.markNamespaceAsDecommissioned(args.namespace); + return true; }, access: EnforcementPoint, diff --git a/src/services/org-groups/namespace.ts b/src/services/org-groups/namespace.ts index 952d98ef0..7dbcbf403 100644 --- a/src/services/org-groups/namespace.ts +++ b/src/services/org-groups/namespace.ts @@ -105,6 +105,29 @@ export class NamespaceService { return false; } + async markNamespaceAsDecommissioned(ns: string): Promise { + const group = await this.groupService.getGroup('ns', ns); + + logger.debug('[markNamespaceAsDecommissioned] %s - Group = %j', ns, group); + + assert.strictEqual(group === null, false, 'Namespace not found'); + + assert.strictEqual( + 'decommissioned' in group.attributes, + false, + `[${ns}] Namespace already decommissioned` + ); + + group.attributes['decommissioned'] = ['true']; + await this.groupService.updateGroup(group); + return true; + } + + async checkNamespaceAvailable(ns: string): Promise { + const group = await this.groupService.getGroup('ns', ns); + assert.strictEqual(group === null, true, 'Namespace already exists'); + } + async listAssignedNamespacesByOrg(org: string): Promise { const groups = await this.groupService.getGroups('ns', false); assert.strictEqual( diff --git a/src/services/workflow/delete-namespace.ts b/src/services/workflow/delete-namespace.ts index 73241b02a..d4417c1ce 100644 --- a/src/services/workflow/delete-namespace.ts +++ b/src/services/workflow/delete-namespace.ts @@ -123,17 +123,13 @@ export const DeleteNamespace = async ( 'Invalid namespace' ); - //const gwServices = await lookupServicesByNamespace(context, ns); - - const envs = await lookupEnvironmentsByNS(context, ns); - - const ids = envs.map((e: Environment) => e.id); + const activity = await DeleteNamespaceRecordActivity(context, ns); const gwaService = new GWAService(process.env.GWA_API_URL); - await gwaService.deleteAllGatewayConfiguration(subjectToken, ns); - const activity = await DeleteNamespaceRecordActivity(context, ns); + const envs = await lookupEnvironmentsByNS(context, ns); + const ids = envs.map((e: Environment) => e.id); for (const envId of ids) { await CascadeDeleteEnvironment(context, ns, envId); diff --git a/src/test/integrated/keycloak/groups.ts b/src/test/integrated/keycloak/groups.ts index 9c42d61f5..dc6f37719 100644 --- a/src/test/integrated/keycloak/groups.ts +++ b/src/test/integrated/keycloak/groups.ts @@ -28,7 +28,7 @@ import { KeycloakGroupService } from '../../../services/keycloak'; //const groups = await kc.search('orgcontrol'); //o(groups); - const groupByName = await kc.findByName('ns', 'orgcontrol', false); + const groupByName = await kc.findByName('ns', 'simple', false); o(groupByName); // console.log(await kc.listMembers('660cadef-9233-4532-ba45-5393beaddea4')); diff --git a/src/test/integrated/keystonejs/activity.ts b/src/test/integrated/keystonejs/activity.ts index 50e1cd16d..57c2dc866 100644 --- a/src/test/integrated/keystonejs/activity.ts +++ b/src/test/integrated/keystonejs/activity.ts @@ -59,12 +59,13 @@ import { ); } - const records = await getActivity(ctx, ['simple'], 5); + const records = await getActivity(ctx, ['simple'], 20); o( records .map((o) => removeEmpty(o)) // .map((o) => transformAllRefID(o, ['blob'])) + .map((o) => parseJsonString(o, ['context'])) .map((o) => parseBlobString(o)) ); diff --git a/src/test/integrated/org-groups/namespace.ts b/src/test/integrated/org-groups/namespace.ts index ff8b400a2..b81bf18e2 100644 --- a/src/test/integrated/org-groups/namespace.ts +++ b/src/test/integrated/org-groups/namespace.ts @@ -22,11 +22,13 @@ import { KeycloakGroupService } from '../../../services/keycloak'; await kc.login(process.env.CID, process.env.CSC); - await kc.assignNamespaceToOrganization( - 'feature-myacc', - 'ministry-citizens-services', - 'databc' - ); + if (false) { + await kc.assignNamespaceToOrganization( + 'feature-myacc', + 'ministry-citizens-services', + 'databc' + ); + } // await kc.unassignNamespaceFromOrganization( // 'refactortime', @@ -37,4 +39,7 @@ import { KeycloakGroupService } from '../../../services/keycloak'; console.log( await kc.listAssignedNamespacesByOrg('ministry-citizens-services') ); + + console.log(await kc.checkNamespaceAvailable('simple')); + // await kc.markNamespaceAsDecommissioned('simple'); })(); From 0956bdadcd3f69d139210fc8e298d05e89904080 Mon Sep 17 00:00:00 2001 From: ikethecoder Date: Tue, 19 Apr 2022 13:46:00 -0700 Subject: [PATCH 9/9] minor comment upd --- src/controllers/v2/OrganizationController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/v2/OrganizationController.ts b/src/controllers/v2/OrganizationController.ts index ddb89417a..d05dae115 100644 --- a/src/controllers/v2/OrganizationController.ts +++ b/src/controllers/v2/OrganizationController.ts @@ -122,7 +122,7 @@ export class OrganizationController extends Controller { @Path() org: string, @Body() body: GroupMembership ): Promise { - // must match either the 'name' or the leaf of the 'parent' + // must match either the 'name' or one of the parent nodes assert.strictEqual( org === body.name || isParent(body.parent, org), true,