Skip to content

Commit

Permalink
feat(api): enhance multitenancy support
Browse files Browse the repository at this point in the history
  • Loading branch information
rjbrache committed Feb 9, 2023
1 parent 1952a7c commit f45e998
Show file tree
Hide file tree
Showing 22 changed files with 131 additions and 63 deletions.
4 changes: 3 additions & 1 deletion src/amt/ConnectedDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ export class ConnectedDevice {
ciraSocket: CIRASocket
limiter: Bottleneck
kvmConnect: boolean
tenantId: string

constructor (ciraSocket: CIRASocket, readonly username: string, readonly password: string) {
constructor (ciraSocket: CIRASocket, readonly username: string, readonly password: string, tenantId: string) {
this.ciraSocket = ciraSocket
this.httpHandler = new HttpHandler()
this.kvmConnect = false
this.tenantId = tenantId
this.limiter = new Bottleneck({
maxConcurrent: 3,
minTime: 250
Expand Down
2 changes: 1 addition & 1 deletion src/amt/connectedDevice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const socket: CIRASocket = null

describe('Connected Device', () => {
it('should initialize', () => {
const device = new ConnectedDevice(socket, 'admin', 'P@ssw0rd')
const device = new ConnectedDevice(socket, 'admin', 'P@ssw0rd', '')
expect(device.ciraSocket).toBeNull()
expect(device.httpHandler).toBeDefined()
})
Expand Down
1 change: 1 addition & 0 deletions src/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ declare module 'express' {
certs: certificatesType
db: IDB
deviceAction: DeviceAction
tenantId: string
}
}
28 changes: 13 additions & 15 deletions src/data/postgres/tables/device.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ describe('device tests', () => {
const device: Device = await deviceTable.getById('4c4c4544-004b-4210-8033-b6c04f504633')
expect(device).toBe(null)
expect(querySpy).toBeCalledTimes(1)
expect(querySpy).toBeCalledWith(`
SELECT
expect(querySpy).toBeCalledWith(`SELECT
guid as "guid",
hostname as "hostname",
tags as "tags",
Expand All @@ -113,8 +112,8 @@ describe('device tests', () => {
tenantid as "tenantId",
friendlyname as "friendlyName",
dnssuffix as "dnsSuffix"
FROM devices
WHERE guid = $1 and tenantid = $2`, ['4c4c4544-004b-4210-8033-b6c04f504633', ''])
FROM devices
WHERE guid = $1`, ['4c4c4544-004b-4210-8033-b6c04f504633'])
})

test('should get count of connected devices when exists', async () => {
Expand Down Expand Up @@ -153,17 +152,16 @@ describe('device tests', () => {
const result: Device = await deviceTable.getById('4c4c4544-004b-4210-8033-b6c04f504633', 'tenantId')
expect(result).toBe(device)
expect(querySpy).toBeCalledTimes(1)
expect(querySpy).toBeCalledWith(`
SELECT
guid as "guid",
hostname as "hostname",
tags as "tags",
mpsinstance as "mpsInstance",
connectionstatus as "connectionStatus",
mpsusername as "mpsusername",
tenantid as "tenantId",
friendlyname as "friendlyName",
dnssuffix as "dnsSuffix"
expect(querySpy).toBeCalledWith(`SELECT
guid as "guid",
hostname as "hostname",
tags as "tags",
mpsinstance as "mpsInstance",
connectionstatus as "connectionStatus",
mpsusername as "mpsusername",
tenantid as "tenantId",
friendlyname as "friendlyName",
dnssuffix as "dnsSuffix"
FROM devices
WHERE guid = $1 and tenantid = $2`, ['4c4c4544-004b-4210-8033-b6c04f504633', 'tenantId'])
})
Expand Down
27 changes: 21 additions & 6 deletions src/data/postgres/tables/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,22 @@ export class DeviceTable implements IDeviceTable {
* @param {string} guid
* @returns {Device} Device object
*/
async getById (id: string, tenantId: string = ''): Promise<Device> {
const results = await this.db.query<Device>(`
SELECT
async getById (id: string, tenantId?: string): Promise<Device> {
let query = `SELECT
guid as "guid",
hostname as "hostname",
tags as "tags",
mpsinstance as "mpsInstance",
connectionstatus as "connectionStatus",
mpsusername as "mpsusername",
tenantid as "tenantId",
friendlyname as "friendlyName",
dnssuffix as "dnsSuffix"
FROM devices
WHERE guid = $1 and tenantid = $2`
let params = [id, tenantId]
if (tenantId == null) {
query = `SELECT
guid as "guid",
hostname as "hostname",
tags as "tags",
Expand All @@ -84,9 +97,11 @@ export class DeviceTable implements IDeviceTable {
tenantid as "tenantId",
friendlyname as "friendlyName",
dnssuffix as "dnsSuffix"
FROM devices
WHERE guid = $1 and tenantid = $2`, [id, tenantId])

FROM devices
WHERE guid = $1`
params = [id]
}
const results = await this.db.query<Device>(query, params)
return results.rowCount > 0 ? results.rows[0] : null
}

Expand Down
6 changes: 6 additions & 0 deletions src/middleware/custom/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ exports = module.exports = function (req, res, next) {

// For setting the tenantId use req.tenantId
// req.tenantId = req.headers['x-tenant-id-token']
// Be sure to reject requests that do not have access to the tenant
// if (req.tenantId === <resource's>.tenantId) {
// next()
// } else {
// res.send(401).end()
// }

// ensure next is called when appropriate, or return an error code using res
next()
Expand Down
2 changes: 2 additions & 0 deletions src/models/Config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export interface configType {
instance_name: string
redirection_expiration_time: number
web_auth_enabled: boolean
jwt_token_header: string
jwt_tenant_property: string
}

export interface certificatesType {
Expand Down
8 changes: 4 additions & 4 deletions src/routes/devices/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('create', () => {
}
} as any
await insertDevice(req, res as any)
expect(req.db.devices.getById).toHaveBeenCalledWith(guidFromRequest)
expect(req.db.devices.getById).toHaveBeenCalledWith(guidFromRequest, tenantIdFromRequest)
expect(statusSpy).toHaveBeenCalledWith(200)
expect(jsonSpy).toHaveBeenCalledWith(expectedUpdateResultFromDb)
expect(req.db.devices.update).toHaveBeenCalledWith(expectedUpdateResultFromDb)
Expand Down Expand Up @@ -110,7 +110,7 @@ describe('create', () => {
}
} as any
await insertDevice(req, res as any)
expect(req.db.devices.getById).toHaveBeenCalledWith(guidFromRequest)
expect(req.db.devices.getById).toHaveBeenCalledWith(guidFromRequest, null)
expect(statusSpy).toHaveBeenCalledWith(200)
expect(jsonSpy).toHaveBeenCalledWith(expectedUpdateResultFromDb)
expect(req.db.devices.update).toHaveBeenCalledWith(expectedUpdateResultFromDb)
Expand Down Expand Up @@ -154,7 +154,7 @@ describe('create', () => {
}
} as any
await insertDevice(req, res as any)
expect(req.db.devices.getById).toHaveBeenCalledWith(guidFromRequest)
expect(req.db.devices.getById).toHaveBeenCalledWith(guidFromRequest, tenantIdFromRequest)
expect(statusSpy).toHaveBeenCalledWith(201)
expect(jsonSpy).toHaveBeenCalledWith(expectedInsertResultFromDb)
expect(req.db.devices.insert).toHaveBeenCalledWith(expectedInsertResultFromDb)
Expand Down Expand Up @@ -198,7 +198,7 @@ describe('create', () => {
}
} as any
await insertDevice(req, res as any)
expect(req.db.devices.getById).toHaveBeenCalledWith(guidFromRequest)
expect(req.db.devices.getById).toHaveBeenCalledWith(guidFromRequest, null)
expect(statusSpy).toHaveBeenCalledWith(201)
expect(jsonSpy).toHaveBeenCalledWith(expectedInsertResultFromDb)
expect(req.db.devices.insert).toHaveBeenCalledWith(expectedInsertResultFromDb)
Expand Down
2 changes: 1 addition & 1 deletion src/routes/devices/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { type Request, type Response } from 'express'
export async function insertDevice (req: Request, res: Response): Promise<void> {
let device: Device
try {
device = await req.db.devices.getById(req.body.guid)
device = await req.db.devices.getById(req.body.guid, req.body.tenantId)
if (device != null) {
device.hostname = req.body.hostname ?? device.hostname
device.tags = req.body.tags ?? device.tags
Expand Down
4 changes: 2 additions & 2 deletions src/routes/devices/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { logger, messages } from '../../logging'

export async function deleteDevice (req: Request, res: Response): Promise<void> {
try {
const device = await req.db.devices.getById(req.params.guid)
const device = await req.db.devices.getById(req.params.guid, req.tenantId)
if (device == null) {
res.status(404).json({ error: 'NOT FOUND', message: `Device ID ${req.params.guid} not found` }).end()
} else {
const results = await req.db.devices.delete(req.params.guid)
const results = await req.db.devices.delete(req.params.guid, req.tenantId)
if (results) {
res.status(204).end()
}
Expand Down
42 changes: 40 additions & 2 deletions src/routes/devices/get.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ beforeEach(() => {

describe('guid get', () => {
const req = {
tenantId: 'tenantxyz',
params: {
guid: '00000000-0000-0000-0000-000000000000'
},
query: {
tenantId: ''
},
db: {
devices: {
getById: () => {}
Expand All @@ -35,15 +39,30 @@ describe('guid get', () => {
} as any
const logSpy = jest.spyOn(logger, 'error')

it('should set status to 200 and get result if device exists in DB', async () => {
req.db.devices.getById = jest.fn().mockReturnValue({})
it('should set status to 200 and get result if device exists in DB with tenant', async () => {
req.db.devices.getById = jest.fn().mockReturnValue({
guid: '00000000-0000-0000-0000-000000000000',
hostname: 'hostname',
tags: [],
mpsusername: 'admin',
tenantId: 'tenantxyz'
})
await getDevice(req, res as any)
expect(req.db.devices.getById).toHaveBeenCalledWith(req.params.guid)
expect(statusSpy).toHaveBeenCalledWith(200)
expect(jsonSpy).not.toHaveBeenCalledWith(null)
expect(endSpy).toHaveBeenCalled()
})

it('should set status to 204 and get result if device exists in DB', async () => {
req.db.devices.getById = jest.fn().mockReturnValue({})
await getDevice(req, res as any)
expect(req.db.devices.getById).toHaveBeenCalledWith(req.params.guid)
expect(statusSpy).toHaveBeenCalledWith(204)
expect(jsonSpy).not.toHaveBeenCalledWith(null)
expect(endSpy).toHaveBeenCalled()
})

it('should set status to 404 if device does not exist in DB', async () => {
req.db.devices.getById = jest.fn().mockReturnValue(null)
await getDevice(req, res as any)
Expand All @@ -64,4 +83,23 @@ describe('guid get', () => {
expect(endSpy).toHaveBeenCalled()
expect(logSpy).toHaveBeenCalled()
})

it('should set status to 204 and get tenantId from req.query', async () => {
req.tenantId = ''
req.query.tenantId = 'test'

req.db.devices.getById = jest.fn().mockReturnValue({
guid: '00000000-0000-0000-0000-000000000000',
hostname: 'hostname',
tags: [],
mpsusername: 'admin',
tenantId: 'tenantxyz'
})
await getDevice(req, res as any)
expect(req.db.devices.getById).toHaveBeenCalledWith(req.params.guid)
expect(statusSpy).toHaveBeenCalledWith(204)
expect(jsonSpy).not.toHaveBeenCalledWith(null)
expect(endSpy).toHaveBeenCalled()
})

})
15 changes: 12 additions & 3 deletions src/routes/devices/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,18 @@ import { type Request, type Response } from 'express'

export async function getDevice (req: Request, res: Response): Promise<void> {
try {
const results = await req.db.devices.getById(req.params.guid)
if (results != null) {
res.status(200).json(results).end()
let tenantId = req.tenantId
const tentantIdInQuery = req.query?.tenantId
if ((tenantId == null || tenantId === '') && (tentantIdInQuery != null || tentantIdInQuery !== '')) {
tenantId = tentantIdInQuery as string
}
const result = await req.db.devices.getById(req.params.guid)
if (result != null) {
if (result.tenantId === tenantId) {
res.status(200).json(result).end()
} else {
res.status(204).end()
}
} else {
res.status(404).end()
}
Expand Down
13 changes: 8 additions & 5 deletions src/routes/devices/getAll.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('getAll', () => {
$skip: 0,
status: false
},
tenantId: '',
db: {
devices:
{
Expand All @@ -52,7 +53,7 @@ describe('getAll', () => {
}
await getAllDevices(req as any, res as any)
const tags = req.query.tags.split(',')
expect(req.db.devices.getByTags).toHaveBeenCalledWith(tags, req.query.method, req.query.$top, req.query.$skip)
expect(req.db.devices.getByTags).toHaveBeenCalledWith(tags, req.query.method, req.query.$top, req.query.$skip, req.tenantId)
expect(statusSpy).toHaveBeenCalledWith(200)
})

Expand All @@ -67,6 +68,7 @@ describe('getAll', () => {
$skip: 0,
status: null
},
tenantId: '',
db: {
devices:
{
Expand All @@ -75,7 +77,7 @@ describe('getAll', () => {
}
}
await getAllDevices(req as any, res as any)
expect(req.db.devices.get).toHaveBeenCalledWith(req.query.$top, req.query.$skip)
expect(req.db.devices.get).toHaveBeenCalledWith(req.query.$top, req.query.$skip, req.tenantId)
expect(statusSpy).toHaveBeenCalledWith(200)
expect(jsonSpy).toHaveBeenCalledWith(deviceList)
})
Expand Down Expand Up @@ -110,6 +112,7 @@ describe('getAll', () => {
query: {
hostname: 'test'
},
tenantId: '',
db: {
devices: {
getById: () => {}
Expand All @@ -121,7 +124,7 @@ describe('getAll', () => {
it('should set status to 200 and get result if device exists in DB', async () => {
req.db.devices.getByHostname = jest.fn().mockReturnValue([{}])
await getAllDevices(req, res as any)
expect(req.db.devices.getByHostname).toHaveBeenCalledWith(req.query.hostname)
expect(req.db.devices.getByHostname).toHaveBeenCalledWith(req.query.hostname, req.tenantId)
expect(statusSpy).toHaveBeenCalledWith(200)
expect(jsonSpy).toHaveBeenCalledWith([{}])
expect(endSpy).toHaveBeenCalled()
Expand All @@ -130,7 +133,7 @@ describe('getAll', () => {
it('should set status to 404 if device does not exist in DB', async () => {
req.db.devices.getByHostname = jest.fn().mockReturnValue([])
await getAllDevices(req, res as any)
expect(req.db.devices.getByHostname).toHaveBeenCalledWith(req.query.hostname)
expect(req.db.devices.getByHostname).toHaveBeenCalledWith(req.query.hostname, req.tenantId)
expect(statusSpy).toHaveBeenCalledWith(200)
expect(jsonSpy).toHaveBeenCalledWith([])
expect(endSpy).toHaveBeenCalled()
Expand All @@ -141,7 +144,7 @@ describe('getAll', () => {
throw new TypeError('fake error')
})
await getAllDevices(req, res as any)
expect(req.db.devices.getByHostname).toHaveBeenCalledWith(req.query.hostname)
expect(req.db.devices.getByHostname).toHaveBeenCalledWith(req.query.hostname, req.tenantId)
expect(statusSpy).toHaveBeenCalledWith(500)
expect(jsonSpy).not.toHaveBeenCalled()
expect(endSpy).toHaveBeenCalled()
Expand Down
8 changes: 4 additions & 4 deletions src/routes/devices/getAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ export async function getAllDevices (req: Request, res: Response): Promise<void>
let list: Device[] = []

if (req.query.hostname != null) {
list = await req.db.devices.getByHostname(req.query.hostname as string)
list = await req.db.devices.getByHostname(req.query.hostname as string, req.tenantId)
} else if (req.query.tags != null) {
const tags = (req.query.tags as string).split(',')
list = await req.db.devices.getByTags(tags, req.query.method as string, req.query.$top as any, req.query.$skip as any)
list = await req.db.devices.getByTags(tags, req.query.method as string, req.query.$top as any, req.query.$skip as any, req.tenantId)
} else {
list = await req.db.devices.get(req.query.$top as any, req.query.$skip as any)
list = await req.db.devices.get(req.query.$top as any, req.query.$skip as any, req.tenantId)
}
if (req.query.status != null) {
list = list.filter(x => {
Expand All @@ -28,7 +28,7 @@ export async function getAllDevices (req: Request, res: Response): Promise<void>
})
}
if (count != null && count) {
const count: number = await req.db.devices.getCount()
const count: number = await req.db.devices.getCount(req.tenantId)
const dataWithCount: DataWithCount = {
data: list,
totalCount: count
Expand Down
4 changes: 2 additions & 2 deletions src/routes/devices/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { messages } from '../../logging/messages'

export async function stats (req: Request, res: Response): Promise<void> {
try {
const connectedCount = await req.db.devices.getConnectedDevices()
const totalCount = await req.db.devices.getCount()
const connectedCount = await req.db.devices.getConnectedDevices(req.tenantId)
const totalCount = await req.db.devices.getCount(req.tenantId)
res.json({
totalCount,
connectedCount,
Expand Down
Loading

0 comments on commit f45e998

Please sign in to comment.