Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Topic / k1ch / Introduce GET:/permissions and GET:/clients/{client_id}/permissions APIs #135

Merged
merged 7 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
mikelax marked this conversation as resolved.
Show resolved Hide resolved
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
Loading