Skip to content

Commit

Permalink
Topics/k1ch/ introduce get/personas-permissions (#80)
Browse files Browse the repository at this point in the history
* feat: topics/k1ch/ introduce get/personas-permissions

* chore: topics/k1ch/admin-get-persona-permissions/add tests

* chore: topics/k1ch/admin-get-personas-permissions/minor fixes

* chore: topics/k1ch/jsDoc for usherDb knex instance
  • Loading branch information
k1ch authored Dec 28, 2023
1 parent 668da0c commit d42efd6
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 14 deletions.
22 changes: 22 additions & 0 deletions database/layer/admin-persona.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,31 @@ const insertPersonaByTenantKey = async (tenantKey, subClaim, userContext = '') =
}
}

const getPersona = async (personaKey) => {
try {
return await usherDb('personas').select('*').where({ key: personaKey }).first();
} catch (err) {
throw pgErrorHandler(err)
}
}

const getPersonaPermissions = async (personaKey) => {
try {
return await usherDb('permissions')
.select('permissions.key', 'permissions.name', 'permissions.description', 'permissions.clientkey')
.join('personapermissions', 'permissions.key', 'personapermissions.permissionkey')
.join('personas', 'personapermissions.personakey', 'personas.key')
.where('personas.key', personaKey)
} catch (err) {
throw pgErrorHandler(err)
}
}

module.exports = {
insertPersona,
deletePersona,
updatePersona,
insertPersonaByTenantKey,
getPersona,
getPersonaPermissions,
}
11 changes: 11 additions & 0 deletions database/layer/knex.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
const knex = require('knex');
const knexDbConfig = require('../knexfile');

/**
* Usher DB connection instance.
* @name usherDb
* @type {import('knex')}
* @desc This instance provides a connection to the Usher database using Knex.js
* @example // To import usherDb instance
* const { usherDb } = require('./knex');
* @example // To perform a database query
* const persona = await usherDb('personas').where('key', personaKey).first();
*/
const usherDb = knex(knexDbConfig);

module.exports = { usherDb }
56 changes: 42 additions & 14 deletions database/test/db-admin-persona.test.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,83 @@
const { describe, it } = require('mocha')
const assert = require('assert')
const postPersonas = require('../layer/admin-persona.js')
const adminPersonas = require('../layer/admin-persona.js')
const { usherDb } = require('../layer/knex')

describe('Admin persona view', () => {
describe('Test INSERT personas', () => {
it('Should insert persona without an exception', async () => {
const insertResult = await postPersonas.insertPersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
const insertResult = await adminPersonas.insertPersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
assert.strictEqual(insertResult, 'Insert successful')
await postPersonas.deletePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
await adminPersonas.deletePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
})
it('Should fail to insert for a nonexistent tenant', async () => {
const insertResult = await postPersonas.insertPersona('test-tenant1 Non-existent', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
const insertResult = await adminPersonas.insertPersona('test-tenant1 Non-existent', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
assert.strictEqual(insertResult, 'Insert failed: Tenant does not exist matching tenantname test-tenant1 Non-existent iss_claim http://idp.dmgt.com.mock.localhost:3002/')
})
it('Should fail to insert duplicate tenant/persona combination - check tenantname', async () => {
await postPersonas.insertPersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
const result = await postPersonas.insertPersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
await adminPersonas.insertPersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
const result = await adminPersonas.insertPersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
assert.strictEqual(result, 'Insert failed: A persona (sub_claim = [email protected]; user_context = ) already exists on tenantname test-tenant1 iss_claim http://idp.dmgt.com.mock.localhost:3002/')
await postPersonas.deletePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
await adminPersonas.deletePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
})
it('Should insert persona by tenant key without an exception', async () => {
const subClaim = '[email protected]'
const [tenant] = await usherDb('tenants').select('*').limit(1)
const persona = await postPersonas.insertPersonaByTenantKey(tenant.key, subClaim)
const persona = await adminPersonas.insertPersonaByTenantKey(tenant.key, subClaim)
assert.strictEqual(persona.sub_claim, subClaim)
await usherDb('personas').where({ key: persona.key }).del()
})
})

describe('Test UPDATE personas', () => {
it('Should update persona without an exception by tenantname', async () => {
await postPersonas.insertPersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
const resultTenantname = await postPersonas.updatePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '[email protected]', '', '')
await adminPersonas.insertPersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
const resultTenantname = await adminPersonas.updatePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '[email protected]', '', '')
assert.strictEqual(resultTenantname, 'Update successful')
await postPersonas.deletePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
await adminPersonas.deletePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
})
it('Should fail to update for a nonexistent tenant', async () => {
const resultTenantname = await postPersonas.updatePersona('test-tenant1 Non-existent', 'http://idp.dmgt.com.mock.localhost:3002/', 'auth0|test-persona2-REPLACE', 'should_not_replace_sub_claim', '', '')
const resultTenantname = await adminPersonas.updatePersona('test-tenant1 Non-existent', 'http://idp.dmgt.com.mock.localhost:3002/', 'auth0|test-persona2-REPLACE', 'should_not_replace_sub_claim', '', '')
assert.strictEqual(resultTenantname, 'Update failed: A persona (sub_claim = auth0|test-persona2-REPLACE; user_context = ) does not exist on tenantname test-tenant1 Non-existent iss_claim http://idp.dmgt.com.mock.localhost:3002/')
})
it('Should fail to update for a nonexistent persona', async () => {
const resultTenantname = await postPersonas.updatePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', 'should_not_replace_sub_claim', '', '')
const resultTenantname = await adminPersonas.updatePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', 'should_not_replace_sub_claim', '', '')
assert.strictEqual(resultTenantname, 'Update failed: A persona (sub_claim = [email protected]; user_context = ) does not exist on tenantname test-tenant1 iss_claim http://idp.dmgt.com.mock.localhost:3002/')
})
})

describe('Test DELETE personas', () => {
it('Should fail to delete a persona not linked to a tenant', async () => {
const resultDelete = await postPersonas.deletePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
const resultDelete = await adminPersonas.deletePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '')
assert.strictEqual(resultDelete, 'Delete failed: A persona (sub_claim = [email protected]; user_context = ) does not exist on tenantname test-tenant1 iss_claim http://idp.dmgt.com.mock.localhost:3002/')
})
})

describe('Test GET personas', () => {
const invalidPersonaKey = 0;
it('Should return a valid persona', async () => {
const persona = await adminPersonas.getPersona(1)
assert.strictEqual(persona.key, 1)
})
it('Should return undefined for invalid persona key', async () => {
const persona = await adminPersonas.getPersona(invalidPersonaKey)
assert.strictEqual(persona, undefined)
})
})

describe('Test GET personas permissions', () => {
const invalidPersonaKey = 0;
it('Should return an array of permissions for the persona', async function () {
const { personakey } = await usherDb('personapermissions').select('*').first() || {}
if (!personakey) {
this.skip()
}
const personaPermissions = await adminPersonas.getPersonaPermissions(personakey)
assert.equal(!!personaPermissions.length, true)
})
it('Should return an empty array', async () => {
const personaPermissions = await adminPersonas.getPersonaPermissions(invalidPersonaKey)
assert.equal(personaPermissions.length, 0)
})
})
})
18 changes: 18 additions & 0 deletions server/src/api_endpoints/personas/permissions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const createError = require('http-errors')
const dbAdminPersona = require('database/layer/admin-persona')
const { checkPersonaExists } = require('./utils')

const getPersonaPermissions = async (req, res, next) => {
try {
const { persona_key: personaKey } = req.params
await checkPersonaExists(personaKey)
const permissions = await dbAdminPersona.getPersonaPermissions(personaKey)
res.status(200).send(permissions)
} catch ({ httpStatusCode = 500, message }) {
return next(createError(httpStatusCode, { message }))
}
}

module.exports = {
getPersonaPermissions,
}
15 changes: 15 additions & 0 deletions server/src/api_endpoints/personas/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const dbAdminPersona = require('database/layer/admin-persona')

const checkPersonaExists = async (personaKey) => {
const persona = await dbAdminPersona.getPersona(personaKey)
if (!persona) {
throw {
httpStatusCode: 404,
message: 'Persona does not exist!'
}
}
}

module.exports = {
checkPersonaExists,
}
70 changes: 70 additions & 0 deletions server/test/endpoint_admin_personas_permissions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const { describe, it, before } = require('mocha')
const fetch = require('node-fetch')
const assert = require('assert')

const { getAdmin1IdPToken, getTestUser1IdPToken } = require('./lib/tokens')
const { getServerUrl } = require('./lib/urls')
const { usherDb } = require('../../database/layer/knex')


describe('Admin Personas Permissions', () => {
let requestHeaders
const url = `${getServerUrl()}`

before(async () => {
const adminAccessToken = await getAdmin1IdPToken()
requestHeaders = {
'Content-Type': 'application/json',
Authorization: `Bearer ${adminAccessToken}`,
}
})

describe('GET:/personas/{persona_key}/permissions', () => {
const invalidPersona = 0;
const validPersonaWithNoPermissions = 1

it('should return 200 and a list of permissions for the persona', async function () {
const { personakey } = await usherDb('personapermissions').select('*').first() || {}
if (!personakey) {
this.skip()
}
const response = await fetch(`${url}/personas/${personakey}/permissions`, {
method: 'GET',
headers: requestHeaders,
})
assert.equal(response.status, 200)
const personaPermissions = await response.json()
assert.equal(personaPermissions.length > 0, true)
})

it('should return 200 and an empty array', async () => {
const response = await fetch(`${url}/personas/${validPersonaWithNoPermissions}/permissions`, {
method: 'GET',
headers: requestHeaders,
})
assert.equal(response.status, 200)
const personaPermissions = await response.json()
assert.equal(personaPermissions.length, 0)
})

it('should return 404 and fail to get permissions for an invalid persona', async () => {
const response = await fetch(`${url}/personas/${invalidPersona}/permissions`, {
method: 'GET',
headers: requestHeaders,
})
assert.equal(response.status, 404)
})

it('should return 401 due to lack of proper token', async () => {
const userAccessToken = await getTestUser1IdPToken()
const response = await fetch(`${url}/personas/${validPersonaWithNoPermissions}/permissions`, {
method: 'GET',
headers: {
...requestHeaders,
Authorization: `Bearer ${userAccessToken}`
},
})
assert.equal(response.status, 401)
})
})
})
50 changes: 50 additions & 0 deletions server/the-usher-openapi-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,29 @@ paths:
500:
$ref: '#/components/responses/InternalError'

/personas/{persona_key}/permissions:
get:
'x-swagger-router-controller': 'personas/permissions'
operationId: getPersonaPermissions
parameters:
- $ref: '#/components/parameters/personaKeyPathParam'
tags:
- Admin APIs
security:
- bearerAdminAuth: []
responses:
200:
description: Returns a list of permissions for the subject persona
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/PermissionObject"
404:
$ref: '#/components/responses/NotFound'
500:
$ref: '#/components/responses/InternalError'

/clients/{client_id}:
get:
Expand Down Expand Up @@ -635,6 +658,13 @@ components:
required: true
schema:
type: integer
personaKeyPathParam:
name: persona_key
description: The unique persona identifier
in: path
required: true
schema:
type: integer
# user_context
userContextParam:
name: user_context
Expand Down Expand Up @@ -739,6 +769,26 @@ components:
$ref: '#/components/schemas/EntityDescriptionDef'
required:
- permission

PermissionObject:
type: object
properties:
key:
type: integer
minimum: 1
format: int32
clientkey:
type: integer
minimum: 1
format: int32
name:
$ref: '#/components/schemas/EntityNameDef'
description:
$ref: '#/components/schemas/EntityDescriptionDef'
required:
- key
- name
- clientkey
#---------------------
ArrayOfPermissions:
type: array
Expand Down

0 comments on commit d42efd6

Please sign in to comment.