Skip to content

Commit

Permalink
Merge pull request #373 from bcgov/dev
Browse files Browse the repository at this point in the history
Release fixes for namespace deletion
  • Loading branch information
ikethecoder authored Apr 20, 2022
2 parents 71f8b25 + 14eea02 commit e8e8a59
Show file tree
Hide file tree
Showing 22 changed files with 336 additions and 54 deletions.
2 changes: 1 addition & 1 deletion src/controllers/v1/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions src/controllers/v2/NamespaceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Namespace } from '../../services/keystone/types';

import { Readable } from 'stream';
import {
parseBlobString,
parseJsonString,
removeEmpty,
transformAllRefID,
Expand Down Expand Up @@ -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));
}
}

Expand All @@ -205,7 +206,9 @@ const item = gql`
}
permDomains
permDataPlane
permProtected
permProtectedNs
org
orgUnit
}
}
`;
Expand Down
5 changes: 3 additions & 2 deletions src/controllers/v2/OrganizationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -121,9 +122,9 @@ export class OrganizationController extends Controller {
@Path() org: string,
@Body() body: GroupMembership
): Promise<void> {
// 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 || org === leaf(body.parent),
org === body.name || isParent(body.parent, org),
true,
'Organization mismatch'
);
Expand Down
8 changes: 6 additions & 2 deletions src/controllers/v2/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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_'
Expand Down Expand Up @@ -608,7 +612,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
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/v2/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion src/lists/Activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
) {
Expand Down
79 changes: 60 additions & 19 deletions src/lists/extensions/Namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ import {
DeleteNamespace,
DeleteNamespaceValidate,
} from '../../services/workflow/delete-namespace';
import { GWAService } from '../../services/gwaapi';
import {
camelCaseAttributes,
transformSingleValueAttributes,
} from '../../services/utils';
import getSubjectToken from '../../auth/auth-token';
import { NamespaceService } from '../../services/org-groups';

const typeUserContact = `
type UserContact {
Expand All @@ -48,7 +55,9 @@ type Namespace {
prodEnvId: String,
permDomains: [String],
permDataPlane: String,
permProtected: String
permProtectedNs: String,
org: String,
orgUnit: String
}
`;

Expand Down Expand Up @@ -213,23 +222,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,
},
Expand Down Expand Up @@ -338,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,
Expand Down Expand Up @@ -480,9 +509,21 @@ module.exports = {
}
await DeleteNamespace(
context.createContext({ skipAccessControl: true }),
getSubjectToken(context.req),
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,
Expand Down
17 changes: 15 additions & 2 deletions src/nextapp/components/org-groups-list/org-groups-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ const OrgGroupsList: React.FC<OrgGroupsListProps> = ({
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 (
<>
<Table variant="simple">
Expand All @@ -52,8 +63,10 @@ const OrgGroupsList: React.FC<OrgGroupsListProps> = ({
.map((item) => (
<Tr key={item.id}>
<Td>
{item.groups.map((g) => (
<p>{g}</p>
{item.groups.map((g, i) => (
<span>
{i > 0 && ' > '} {format(g, i)}
</span>
))}
</Td>
<Td>
Expand Down
4 changes: 3 additions & 1 deletion src/nextapp/shared/types/query.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6065,7 +6065,9 @@ export type Namespace = {
prodEnvId?: Maybe<Scalars['String']>;
permDomains?: Maybe<Array<Maybe<Scalars['String']>>>;
permDataPlane?: Maybe<Scalars['String']>;
permProtected?: Maybe<Scalars['String']>;
permProtectedNs?: Maybe<Scalars['String']>;
org?: Maybe<Scalars['String']>;
orgUnit?: Maybe<Scalars['String']>;
};

export type NamespaceInput = {
Expand Down
36 changes: 36 additions & 0 deletions src/services/gwaapi/gwa-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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: 'delete',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${subjectToken}`,
},
}).then(checkStatus);
}
}
1 change: 1 addition & 0 deletions src/services/gwaapi/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GWAService } from './gwa-service';
2 changes: 1 addition & 1 deletion src/services/keycloak/user-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class KeycloakUserService {
clientId: string,
clientSecret: string
): Promise<KeycloakUserService> {
logger.debug('[login] %s:%s', clientId, clientSecret);
logger.debug('[login] %s', clientId);

await this.kcAdminClient
.auth({
Expand Down
4 changes: 3 additions & 1 deletion src/services/keystone/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6065,7 +6065,9 @@ export type Namespace = {
prodEnvId?: Maybe<Scalars['String']>;
permDomains?: Maybe<Array<Maybe<Scalars['String']>>>;
permDataPlane?: Maybe<Scalars['String']>;
permProtected?: Maybe<Scalars['String']>;
permProtectedNs?: Maybe<Scalars['String']>;
org?: Maybe<Scalars['String']>;
orgUnit?: Maybe<Scalars['String']>;
};

export type NamespaceInput = {
Expand Down
16 changes: 14 additions & 2 deletions src/services/org-groups/group-converter-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
}
23 changes: 23 additions & 0 deletions src/services/org-groups/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,29 @@ export class NamespaceService {
return false;
}

async markNamespaceAsDecommissioned(ns: string): Promise<boolean> {
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<void> {
const group = await this.groupService.getGroup('ns', ns);
assert.strictEqual(group === null, true, 'Namespace already exists');
}

async listAssignedNamespacesByOrg(org: string): Promise<OrgNamespace[]> {
const groups = await this.groupService.getGroups('ns', false);
assert.strictEqual(
Expand Down
Loading

0 comments on commit e8e8a59

Please sign in to comment.