Skip to content

Commit

Permalink
Topic / k1ch / Introduce GET:/permissions and `GET:/clients/{client…
Browse files Browse the repository at this point in the history
…_id}/permissions` APIs (#135)

* feat: topic/k1ch/introduce API - GET/permissions

* feat: topic/k1ch/ introduce API / GET/clients/{id}/permissions

* chore: db-admin-permissions / add tests for getPermissions

* chore: k1ch / introduce test for GET:/permissions

* chore: k1ch / add test for GET:/clients/{client_id}/permissions

* chore: minor changes

* chore: k1ch / fix test GH workflow
  • Loading branch information
k1ch authored Dec 23, 2024
1 parent d2babb0 commit 34755fc
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/workflows/run-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
PGUSER: postgres
PGPASSWORD: tehsecure
PGSCHEMA: usher
KNEX_POOL_REAP_INTERVAL_MILLIS: 100000
steps:
- run: sudo ethtool -K eth0 tx off rx off
- uses: actions/checkout@v4
Expand Down
34 changes: 34 additions & 0 deletions database/layer/admin-permission.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,39 @@ const getPermissionsByNameClientKey = async (name, clientKey) => {
}
}

/**
* Get permissions by optional filters
*
* @param {Object} filters - The filters to apply
* @param {string} [filters.name] - The name of the permission
* @param {string} [filters.clientId] - The client id
* @param {number} [filters.clientKey] - The client key
* @returns {Promise<Array<Object>>} - A promise that resolves to an array of permissions
*/
const getPermissions = async (filters = {}) => {
try {
const query = usherDb('permissions')
.join('clients', 'permissions.clientkey', '=', 'clients.key')
.select('permissions.*', 'clients.client_id')

const { clientId, name, clientKey } = filters
if (clientId) {
query.where('clients.client_id', 'ilike', `%${clientId}%`)
}
if (name) {
query.where('permissions.name', 'ilike', `%${name}%`)
}
if (clientKey) {
query.where('permissions.clientkey', clientKey)
}

const permissions = await query
return permissions
} catch (err) {
throw pgErrorHandler(err)
}
}

module.exports = {
insertPermissionByClientId,
updatePermissionByPermissionname,
Expand All @@ -122,4 +155,5 @@ module.exports = {
getPermissionsByRoleKey,
insertPermission,
getPermissionsByNameClientKey,
getPermissions,
}
43 changes: 43 additions & 0 deletions database/test/db-admin-permissions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,47 @@ describe('Admin permissions view', () => {
assert.deepEqual(permissions, [])
})
})

describe('Test GetPermissions by optional filter', () => {
it('Should return all permissions when no filters are applied', async () => {
const { count: permissionCount } = await usherDb('permissions').count('*').first()
const permissions = await adminPermissions.getPermissions()
assert.equal(permissions.length, Number(permissionCount))
assert.ok(permissionTableColumns.every((col) => col in permissions[0]))
})

it('Should return permissions for a specific clientId', async () => {
const { clientkey: clientKey } = await usherDb('permissions').select('clientkey').first()
const { client_id: clientId } = await usherDb('clients').select('client_id').where({ key: clientKey }).first()
const permissions = await adminPermissions.getPermissions({ clientId })
assert.ok(permissions.length > 0)
assert.ok(permissions.every(permission => permission.client_id === clientId))
})

it('Should return permissions for a specific name', async () => {
const { name } = await usherDb('permissions').select('name').first()
const permissions = await adminPermissions.getPermissions({ name })
assert.ok(permissions.length > 0)
assert.ok(permissions.every(permission => permission.name === name))
})

it('Should return permissions for a specific clientKey', async () => {
const { clientkey } = await usherDb('permissions').select('clientkey').first()
const permissions = await adminPermissions.getPermissions({ clientKey: clientkey })
assert.ok(permissions.length > 0)
assert.ok(permissions.every(permission => permission.clientkey === clientkey))
})

it('Should return permissions for multiple filters', async () => {
const { name, clientkey: clientKey } = await usherDb('permissions').select('*').first()
const permissions = await adminPermissions.getPermissions({ name, clientKey })
assert.ok(permissions.length > 0)
assert.ok(permissions.every(permission => permission.clientkey === clientKey && permission.name === name))
})

it('Should return an empty array if no permissions match the criteria', async () => {
const permissions = await adminPermissions.getPermissions({ name: 'Nonexistent Name', clientId: 'Nonexistent ClientId' })
assert.ok(permissions.length === 0)
})
})
})
21 changes: 21 additions & 0 deletions server/src/api_endpoints/clients/permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,27 @@ const createPermission = async (req, res, next) => {
}
}

/**
* HTTP Request handler
* Get a list of permissions for a client
*
* @param {Object} req - The request object
* @param {Object} res - The response object to send a 200 status code and the list of permissions
* @param {Function} next - The next middleware function
* @returns {Promise<void>} - A Promise that resolves to void when permissions are retrieved
*/
const getClientPermissions = async (req, res, next) => {
try {
const { client_id: clientId } = req.params
await checkClientExists(clientId)
const permissions = await dbAdminPermission.getPermissions({ clientId })
res.status(200).send(permissions)
} catch ({ httpStatusCode = 500, message }) {
return next(createError(httpStatusCode, { message }))
}
}

module.exports = {
createPermission,
getClientPermissions,
}
23 changes: 23 additions & 0 deletions server/src/api_endpoints/endpoint_permissions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const createError = require('http-errors')
const dbAdminPermission = require('database/layer/admin-permission')

/**
* HTTP Request handler
* Returns a list of permissions
*
* @param {Object} req - The request object
* @param {Object} res - The response object to send 200 statusCode and a list of permissions
* @param {Function} next - The next middleware function
* @returns {Promise<void>} - A promise that resolves to void when permissions are retrieved
*/
const getPermissions = async (req, res, next) => {
try {
const { name, client_id: clientId, client_key: clientKey } = req.query
const permissions = await dbAdminPermission.getPermissions({ name, clientId, clientKey })
res.status(200).send(permissions)
} catch ({ httpStatusCode = 500, message }) {
return next(createError(httpStatusCode, { message }))
}
}

module.exports = { getPermissions }
39 changes: 39 additions & 0 deletions server/test/endpoint_clients.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,43 @@ describe('Admin Clients Endpoint Test', () => {
assert.equal(response.status, 409)
})
})

describe('Get Client Permissions', () => {
let validClientId
const getClientPermissions = async (clientId, header = requestHeaders) => {
return await fetch(`${url}/clients/${clientId}/permissions`, {
method: 'GET',
headers: header,
})
}

before(async () => {
const client = await usherDb('clients').select('*').first()
validClientId = client.client_id
})

it('should return 200 and list of all permissions', async () => {
const response = await getClientPermissions(validClientId)
assert.equal(response.status, 200)
const permissions = await response.json()
const { count: permissionCount } = await usherDb('permissions')
.join('clients', 'permissions.clientkey', '=', 'clients.key')
.where('clients.client_id', validClientId).count('*').first()
assert.equal(permissions?.length, Number(permissionCount))
})

it('should return 401 due to lack of proper token', async () => {
const userAccessToken = await getTestUser1IdPToken()
const response = await getClientPermissions(validClientId, {
...requestHeaders,
Authorization: `Bearer ${userAccessToken}`
})
assert.equal(response.status, 401)
})

it('should return 404 for non-existent client id', async () => {
const response = await getClientPermissions('invalid_client_id')
assert.equal(response.status, 404)
})
})
})
85 changes: 85 additions & 0 deletions server/test/endpoint_permissions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const { describe, it } = require('mocha')
const fetch = require('node-fetch')
const assert = require('node:assert')
const { usherDb } = require('database/layer/knex')
const { getAdmin1IdPToken, getTestUser1IdPToken } = require('./lib/tokens')
const { getServerUrl } = require('./lib/urls')

describe('Admin Permissions API Tests', () => {
const url = getServerUrl()
let requestHeaders
before(async () => {
const adminAccessToken = await getAdmin1IdPToken()
requestHeaders = {
'Content-Type': 'application/json',
Authorization: `Bearer ${adminAccessToken}`,
}
})

describe('GET:/permissions', () => {
/**
* GET /permissions
* HTTP request to retrieve a list of permissions
*
* @param {string} query - The query params to be added to the URL (e.g., ?name=value1&client_id=value2&client_key=value3)
* @param {Object} header - The request headers
* @returns {Promise<fetch.Response>} - A Promise which resolves to fetch.Response
*/
const getPermissions = async (query = '', header = requestHeaders) => {
return await fetch(`${url}/permissions${query}`, {
method: 'GET',
headers: header,
})
}

it('should return 200, return all the permissions', async () => {
const { count: permissionCount } = await usherDb('permissions').count('*').first()
const response = await getPermissions()
assert.equal(response.status, 200)
const permissions = await response.json()
assert.equal(permissions.length, Number(permissionCount))
})

it('should return 200, return all the permissions for a client', async () => {
const { client_id: validClientId, key: validClientKey } = await usherDb('clients').select('*').first()
const { count: permissionCount } = await usherDb('permissions').where({ clientkey: validClientKey }).count('*').first()
const response = await getPermissions(`?client_id=${validClientId}`)
assert.equal(response.status, 200)
const permissions = await response.json()
assert.equal(permissions.length, Number(permissionCount))
assert.equal(permissions[0]['client_id'], validClientId)
})

it('should return 200, return a permission with two filter parameters', async () => {
const validPermission = await usherDb('permissions').select('*').first()
const { clientkey, name } = validPermission
const response = await getPermissions(`?client_key=${clientkey}&name=${name}`)
assert.equal(response.status, 200)
const permissions = await response.json()
assert.ok(permissions.every(permission => permission.clientkey === clientkey))
assert.ok(permissions.every(permission => permission.name === name))
})

it('should return 200, return an empty array for an invalid client_id', async () => {
const response = await getPermissions('?client_id=invalid')
assert.equal(response.status, 200)
const permissions = await response.json()
assert.equal(permissions.length, 0)
})

it('should return 400, due to an invalid query param', async () => {
const response = await getPermissions('?client_key=string,')
assert.equal(response.status, 400)
})

it('should return 401, unauthorized token', async () => {
const userAccessToken = await getTestUser1IdPToken()
const response = await getPermissions('',
{
...requestHeaders,
Authorization: `Bearer ${userAccessToken}`
})
assert.equal(response.status, 401)
})
})
})
85 changes: 84 additions & 1 deletion server/the-usher-openapi-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,42 @@ paths:
503:
$ref: '#/components/responses/ServiceUnavailableError'

/permissions:
get:
'x-swagger-router-controller': 'endpoint_permissions'
operationId: getPermissions
summary: Get a List of permissions, optionally filtered by name, client_id and client_key
tags:
- Admin APIs
security:
- bearerAdminAuth: []
parameters:
- $ref: '#/components/parameters/nameQueryParam'
- $ref: '#/components/parameters/clientIdQueryParam'
- $ref: '#/components/parameters/clientKeyQueryParam'
responses:
200:
description: The List of Permissions
content:
application/json:
schema:
type: array
items:
allOf:
- $ref: '#/components/schemas/PermissionObject'
- type: object
properties:
client_id:
type: string
400:
$ref: '#/components/responses/BadRequest'
401:
$ref: '#/components/responses/Unauthorized'
500:
$ref: '#/components/responses/InternalError'
503:
$ref: '#/components/responses/ServiceUnavailableError'

/personas:
get:
'x-swagger-router-controller': 'personas/persona'
Expand Down Expand Up @@ -1061,6 +1097,39 @@ paths:
/clients/{client_id}/permissions:
parameters:
- $ref: '#/components/parameters/clientIdPathParam'
get:
'x-swagger-router-controller': 'clients/permissions'
operationId: getClientPermissions
summary: Get a list of permissions for a client
tags:
- Client Admin APIs
security:
- bearerAdminAuth: []
- bearerClientAdminAuth: []
responses:
200:
description: List of permissions for a client
content:
application/json:
schema:
type: array
items:
allOf:
- $ref: '#/components/schemas/PermissionObject'
- type: object
properties:
client_id:
type: string
400:
$ref: '#/components/responses/BadRequest'
401:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
500:
$ref: '#/components/responses/InternalError'
503:
$ref: '#/components/responses/ServiceUnavailableError'
post:
'x-swagger-router-controller': 'clients/permissions'
summary: Create a new permission for the given client
Expand Down Expand Up @@ -1183,7 +1252,21 @@ components:
value: "*"
clientIdQueryParam:
name: client_id
description: Unique identifier for the client.
description: Filter by client_id
in: query
required: false
schema:
$ref: '#/components/schemas/EntityNameDef'
clientKeyQueryParam:
name: client_key
description: Filter by client_key
in: query
required: false
schema:
type: integer
nameQueryParam:
name: name
description: Filter by name
in: query
required: false
schema:
Expand Down

0 comments on commit 34755fc

Please sign in to comment.