Skip to content

Commit

Permalink
Merge pull request #235 from bcgov/feature/ns-tiered
Browse files Browse the repository at this point in the history
Namespace summary and tiered access
  • Loading branch information
ikethecoder authored Nov 15, 2021
2 parents f8c3454 + 03d5e3b commit 6002d43
Show file tree
Hide file tree
Showing 150 changed files with 7,636 additions and 985 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,7 @@ _tmp

kc.js

.config

# vs code settings
.vscode
1 change: 1 addition & 0 deletions src/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
'../stories/**/*.stories.mdx',
'../stories/**/*.stories.@(js|jsx|ts|tsx|mdx)',
'../nextapp/components/**/*.stories.@(js|jsx|ts|tsx|mdx)',
'../nextapp/components/**/*.stories.mdx',
],
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
webpackFinal: async (config) => {
Expand Down
43 changes: 43 additions & 0 deletions src/api-proxy-ds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const express = require('express');
const pathModule = require('path');

const { createProxyMiddleware } = require('http-proxy-middleware');

class ApiDSProxyApp {
constructor({ url }) {
this._url = url;
}

prepareMiddleware({ keystone }) {
const app = express();

const apiProxy = createProxyMiddleware({
target: this._url,
changeOrigin: true,
pathRewrite: { '^/int/api/': '/ds/api/' },
onProxyReq: (proxyReq, req) => {
proxyReq.removeHeader('cookie');
proxyReq.setHeader('Accept', 'application/json');
proxyReq.setHeader(
'Authorization',
`Bearer ${req.header('x-forwarded-access-token')}`
);
},
onError: (err, req, res, target) => {
console.log('CAUGHT ERROR!');
console.log(err);
res.writeHead(400, {
'Content-Type': 'text/plain',
});
res.end('error reaching api');
},
});
app.all(/^\/int\/api\//, apiProxy);

return app;
}
}

module.exports = {
ApiDSProxyApp,
};
51 changes: 32 additions & 19 deletions src/auth/auth-oauth2-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const querystring = require('querystring');
const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const jwtDecoder = require('jwt-decode');
const { deriveRoleFromUsername } = require('./scope-role-utils');
const { deriveRoleFromUsername, scopesToRoles } = require('./scope-role-utils');

const proxy = process.env.EXTERNAL_URL;
const authLogoutUrl =
Expand Down Expand Up @@ -155,6 +155,33 @@ class Oauth2ProxyAuthStrategy {
}
);

app.put(
'/admin/switch',
[verifyJWT, checkExpired],
async (req, res, next) => {
// Switch to "no namespace" - aka "clear namespace"
try {
const jti = req['oauth_user']['jti']; // JWT ID - Unique Identifier for the token
const username = req['oauth_user']['preferred_username']; // Username included in token
// The oauth2_proxy is handling the refresh token; so there can be a new jti
logger.info(
'[ns-clear] %s -> %s : %s',
req.user.jti,
jti,
req.user.jti === jti ? 'SAME TOKEN' : 'REFRESHED TOKEN!'
);
await this.assign_namespace(req.user.jti, jti, username, {
rsname: null,
scopes: [],
});
res.json({ switch: true });
} catch (err) {
logger.error('Error clearing namespace %s', err);
res.status(400).json({ switch: false, error: 'ns_cleared_fail' });
}
}
);

app.put(
'/admin/switch/:ns',
[verifyJWT, checkExpired],
Expand Down Expand Up @@ -205,27 +232,13 @@ class Oauth2ProxyAuthStrategy {
async assign_namespace(jti, newJti, username, umaAuthDetails) {
const namespace = umaAuthDetails['rsname'];
const scopes = umaAuthDetails['scopes'];
const _roles = [];
if (scopes.includes('Namespace.Manage')) {
_roles.push('api-owner');
}
if (scopes.includes('Namespace.View')) {
_roles.push('provider-user');
}
if (scopes.includes('CredentialIssuer.Admin')) {
_roles.push('credential-admin');
}
if (scopes.includes('Access.Manage')) {
_roles.push('access-manager');
}

_roles.push('portal-user');
_roles.push(deriveRoleFromUsername(username));
const _roles = scopesToRoles(username, scopes);

const roles = JSON.stringify(_roles);

const users = this.keystone.getListByKey(this.listKey);
let results = await users.adapter.find({ jti: jti });
// should be TemporaryIdentity
const idList = this.keystone.getListByKey(this.listKey);
let results = await idList.adapter.find({ jti: jti });
let tempId = results[0]['id'];

const { errors } = await this.keystone.executeGraphQL({
Expand Down
2 changes: 1 addition & 1 deletion src/auth/auth-tsoa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function expressAuthentication(
if (authzerr) {
reject(new Error('Access Denied'));
} else {
resolve(request.oauth_user);
resolve({ ...request.oauth_user, ...{ scope: scopes[0] } });
}
}
);
Expand Down
20 changes: 14 additions & 6 deletions src/auth/scope-role-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@ export function scopes(scopeString: string) {
return scopeString.split(' ');
}

export function scopesToRoles(scopes: string[]) {
export function scopesToRoles(username: string, scopes: string[]): string[] {
const _roles = [];
if (scopes.includes('Namespace.Manage')) {
_roles.push('api-owner');
} else {
// For now, make everyone an api-owner if they have access to a namespace
_roles.push('api-owner');
}
if (scopes.includes('Namespace.Manage')) {
if (scopes.includes('Namespace.View')) {
_roles.push('provider-user');
}
if (scopes.includes('CredentialIssuer.Admin')) {
_roles.push('credential-admin');
}
return JSON.stringify(_roles);
if (scopes.includes('Access.Manage')) {
_roles.push('access-manager');
}

_roles.push('portal-user');
if (username != null) {
_roles.push(deriveRoleFromUsername(username));
}
return _roles;
}

export function deriveRoleFromUsername(username: string) {
Expand Down
1 change: 1 addition & 0 deletions src/authz/enforcement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export function EnforcementPoint(params: any) {
tid: item == null ? null : item.id,
id: item == null ? null : item.userId,
roles: roles,
scopes: item == null ? null : item.scopes,
namespace: item == null ? null : item.namespace,
item: originalInput,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

query GET_APPLICATION_SERVICES($appId: String!) {
myServiceAccesses(where: { application: { appId: $appId } }) {
id
name
active
application {
name
}
productEnvironment {
id
name
product {
name
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

query MyApplications {
myApplications {
id
appId
description
name
owner {
name
}
}
allTemporaryIdentities {
id
userId
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
myApplications {
id
appId
description
name
owner {
name
}
}
allTemporaryIdentities {
id
userId
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

mutation ToggleConsumerRoles(
$prodEnvId: ID!
$consumerUsername: String!
$roleName: String!
$grant: Boolean!
) {
updateConsumerRoleAssignment(
prodEnvId: $prodEnvId
consumerUsername: $consumerUsername
roleName: $roleName
grant: $grant
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

query GetConsumers {
allServiceAccessesByNamespace(
first: 200
orderBy: "updatedAt_DESC"
where: { consumer: { username_not_starts_with: "sa-" } }
) {
namespace
consumer {
id
username
aclGroups
customId
plugins {
name
}
tags
updatedAt
}
application {
name
appId
}
}

allAccessRequestsByNamespace(where: { isComplete_not: true }) {
id
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

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
}
}
}
Loading

0 comments on commit 6002d43

Please sign in to comment.