Skip to content

Commit

Permalink
feat(api): adds multitenancy support
Browse files Browse the repository at this point in the history
  • Loading branch information
madhavilosetty-intel committed Feb 8, 2023
1 parent dd8a32b commit 0ce2be7
Show file tree
Hide file tree
Showing 74 changed files with 649 additions and 449 deletions.
573 changes: 326 additions & 247 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,4 @@
"optionalDependencies": {
"fsevents": "^2.3.2"
}
}
}
4 changes: 2 additions & 2 deletions src/DataProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ export class DataProcessor {
this.setConnectionParams(clientId)
activation.service.start()
if (devices[clientId].activationStatus) {
activation.service.send({ type: 'ACTIVATED', clientId, isActivated: true })
activation.service.send({ type: 'ACTIVATED', tenantId: clientMsg.tenantId, clientId, isActivated: true })
} else {
activation.service.send({ type: 'ACTIVATION', clientId })
activation.service.send({ type: 'ACTIVATION', tenantId: clientMsg.tenantId, clientId })
}
}

Expand Down
10 changes: 6 additions & 4 deletions src/DomainCredentialManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ export class DomainCredentialManager implements IDomainCredentialManager {
/**
* @description get the provisioning cert for a given domain
* @param {string} domainSuffix
* @param {string} tenantId
* @returns {AMTDomain} returns domain object
*/
async getProvisioningCert (domainSuffix: string): Promise<AMTDomain> {
const domain = await this.amtDomains.getDomainByDomainSuffix(domainSuffix)
async getProvisioningCert (domainSuffix: string, tenantId: string): Promise<AMTDomain> {
const domain = await this.amtDomains.getDomainByDomainSuffix(domainSuffix, tenantId)
this.logger.debug(`domain : ${JSON.stringify(domain)}`)

if (domain?.provisioningCert) {
Expand All @@ -46,10 +47,11 @@ export class DomainCredentialManager implements IDomainCredentialManager {
/**
* @description Checks if the AMT domain exists or not
* @param {string} domainSuffix
* @param {string} tenantId
* @returns {boolean} returns true if domain exists otherwise false.
*/
public async doesDomainExist (domainSuffix: string): Promise<boolean> {
if (await this.amtDomains.getDomainByDomainSuffix(domainSuffix)) {
public async doesDomainExist (domainSuffix: string, tenantId: string): Promise<boolean> {
if (await this.amtDomains.getDomainByDomainSuffix(domainSuffix, tenantId)) {
return true
} else {
return false
Expand Down
14 changes: 8 additions & 6 deletions src/Index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,20 @@ mqtt.connectBroker()
const dbFactory = new DbCreatorFactory()

export const loadCustomMiddleware = async function (): Promise<express.RequestHandler[]> {
const pathToCustomMiddleware = './src/middleware/custom'
const pathToCustomMiddleware = path.join(__dirname, './middleware/custom/')
const middleware: express.RequestHandler[] = []
const doesExist = existsSync(pathToCustomMiddleware)
const isDirectory = lstatSync(pathToCustomMiddleware).isDirectory()
if (doesExist && isDirectory) {
const files = readdirSync(pathToCustomMiddleware)
for (const file of files) {
const pathToMiddleware = path.join('../src/middleware/custom/', file)
console.log(pathToMiddleware)
const customMiddleware = await import(pathToMiddleware)
if (customMiddleware?.default != null) {
middleware.push(customMiddleware.default)
if (path.extname(file) === '.js') {
const pathToMiddleware = path.join(pathToCustomMiddleware, file.substring(0, file.lastIndexOf('.')))
log.info('Loading custom middleware: ' + file)
const customMiddleware = await import(pathToMiddleware)
if (customMiddleware?.default != null) {
middleware.push(customMiddleware.default)
}
}
}
}
Expand Down
30 changes: 15 additions & 15 deletions src/ProfileManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export class ProfileManager implements IProfileManager {
* @param {string} profileName profile to look up
* @returns {string} returns the activation to be performed
*/
public async getActivationMode (profileName: string): Promise<string> {
const profile = await this.getAmtProfile(profileName)
public async getActivationMode (profileName: string, tenantId: string): Promise<string> {
const profile = await this.getAmtProfile(profileName, tenantId)
let activation: string

if (profile?.activation) {
Expand All @@ -50,8 +50,8 @@ export class ProfileManager implements IProfileManager {
* @param {string} profile of cira config
* @returns {string} returns the config for CIRA for a given profile
*/
public async getCiraConfiguration (profileName: string): Promise<CIRAConfig> {
const profile = await this.getAmtProfile(profileName)
public async getCiraConfiguration (profileName: string, tenantId: string): Promise<CIRAConfig> {
const profile = await this.getAmtProfile(profileName, tenantId)
let ciraConfig: CIRAConfig

if (profile?.ciraConfigName && profile.ciraConfigObject) {
Expand All @@ -69,8 +69,8 @@ export class ProfileManager implements IProfileManager {
* @param {string} profileName profile name of amt password
* @returns {string} returns the amt password for a given profile
*/
public async getAmtPassword (profileName: string): Promise<string> {
const profile: AMTConfiguration = await this.getAmtProfile(profileName)
public async getAmtPassword (profileName: string, tenantId: string): Promise<string> {
const profile: AMTConfiguration = await this.getAmtProfile(profileName, tenantId)
let amtPassword: string
if (profile) {
if (profile.generateRandomPassword) {
Expand Down Expand Up @@ -102,8 +102,8 @@ export class ProfileManager implements IProfileManager {
* @param {string} profileName profile name of amt password
* @returns {string} returns the amt password for a given profile
*/
public async getMEBxPassword (profileName: string): Promise<string> {
const profile: AMTConfiguration = await this.getAmtProfile(profileName)
public async getMEBxPassword (profileName: string, tenantId: string): Promise<string> {
const profile: AMTConfiguration = await this.getAmtProfile(profileName, tenantId)
let mebxPassword: string
if (profile) {
if (profile.generateRandomMEBxPassword) {
Expand Down Expand Up @@ -137,8 +137,8 @@ export class ProfileManager implements IProfileManager {
* @param {string} profileName profile name of MPS password
* @returns {string} returns the MPS password for a given profile
*/
public async getMPSPassword (profileName: string): Promise<string> {
const profile: AMTConfiguration = await this.getAmtProfile(profileName)
public async getMPSPassword (profileName: string, tenantId: string): Promise<string> {
const profile: AMTConfiguration = await this.getAmtProfile(profileName, tenantId)
let mpsPassword: string

if (profile?.ciraConfigObject) {
Expand Down Expand Up @@ -166,15 +166,15 @@ export class ProfileManager implements IProfileManager {
* @param {string} profile
* @returns {AMTConfiguration} returns AMTConfig object if profile exists otherwise null.
*/
public async getAmtProfile (profile: string): Promise<AMTConfiguration> {
public async getAmtProfile (profile: string, tenantId: string): Promise<AMTConfiguration> {
try {
if (!profile) {
return null
}
const amtProfile: AMTConfiguration = await this.amtConfigurations.getByName(profile)
const amtProfile: AMTConfiguration = await this.amtConfigurations.getByName(profile, tenantId)
// If the CIRA Config associated with profile, retrieves from DB
if (amtProfile?.ciraConfigName != null) {
amtProfile.ciraConfigObject = await this.amtConfigurations.getCiraConfigForProfile(amtProfile.ciraConfigName)
amtProfile.ciraConfigObject = await this.amtConfigurations.getCiraConfigForProfile(amtProfile.ciraConfigName, tenantId)
}
// If the TLS Config associated with profile, retrieves from DB
if (amtProfile.tlsMode != null) {
Expand All @@ -196,8 +196,8 @@ export class ProfileManager implements IProfileManager {
* @param {string} profile
* @returns {boolean} returns true if profile exists otherwise false.
*/
public async doesProfileExist (profileName: string): Promise<boolean> {
const profile = await this.getAmtProfile(profileName)
public async doesProfileExist (profileName: string, tenantId: string): Promise<boolean> {
const profile = await this.getAmtProfile(profileName, tenantId)
if (profile) {
// this.logger.debug(`found profile ${profileName}`);
return true
Expand Down
20 changes: 11 additions & 9 deletions src/Validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class Validator implements IValidator {
throw new RPSError(`Device ${payload.uuid} activation failed. Missing password.`)
}
// Check for client requested action and profile activation
const profile: AMTConfiguration = await this.configurator.profileManager.getAmtProfile(payload.profile)
const profile: AMTConfiguration = await this.configurator.profileManager.getAmtProfile(payload.profile, msg.tenantId)
if (!profile) {
throw new RPSError(`Device ${payload.uuid} activation failed. ${payload.profile} does not match list of available AMT profiles.`)
}
Expand All @@ -97,7 +97,7 @@ export class Validator implements IValidator {
}
// Validate client message to configure ACM message
if (clientObj.action === ClientAction.ADMINCTLMODE) {
await this.verifyActivationMsgForACM(payload)
await this.verifyActivationMsgForACM(msg)
}
// }
}
Expand Down Expand Up @@ -236,15 +236,17 @@ export class Validator implements IValidator {
return msg.payload
}

async verifyActivationMsgForACM (payload: Payload): Promise<void> {
if (!payload.certHashes) {
throw new RPSError(`Device ${payload.uuid} activation failed. Missing certificate hashes from the device.`)
async verifyActivationMsgForACM (msg: ClientMsg): Promise<void> {
if (!msg.payload.certHashes) {
throw new RPSError(`Device ${msg.payload.uuid} activation failed. Missing certificate hashes from the device.`)
}
if (!payload.fqdn && (payload.currentMode !== 2)) {
throw new RPSError(`Device ${payload.uuid} activation failed. Missing DNS Suffix.`)

if (!msg.payload.fqdn && (msg.payload.currentMode !== 2)) {
throw new RPSError(`Device ${msg.payload.uuid} activation failed. Missing DNS Suffix.`)
}
if (!(await this.configurator.domainCredentialManager.doesDomainExist(payload.fqdn)) && (payload.currentMode !== 2)) {
throw new RPSError(`Device ${payload.uuid} activation failed. Specified AMT domain suffix: ${payload.fqdn} does not match list of available AMT domain suffixes.`)

if (!(await this.configurator.domainCredentialManager.doesDomainExist(msg.payload.fqdn, msg.tenantId)) && (msg.payload.currentMode !== 2)) {
throw new RPSError(`Device ${msg.payload.uuid} activation failed. Specified AMT domain suffix: ${msg.payload.fqdn} does not match list of available AMT domain suffixes.`)
}
}

Expand Down
1 change: 1 addition & 0 deletions src/commandParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function getBasicMessage (): ClientMsg {
protocolVersion: '4.0.0',
status: 'ok',
message: "all's good!",
tenantId: '',
payload: {
ver: '11.8.50',
build: '3425',
Expand Down
4 changes: 2 additions & 2 deletions src/data/postgres/tables/ciraConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class CiraConfigTable implements ICiraConfigTable {
ciraConfig.tenantId
])
if (results.rowCount > 0) {
return await this.getByName(ciraConfig.configName)
return await this.getByName(ciraConfig.configName, ciraConfig.tenantId)
}
return null
} catch (error) {
Expand Down Expand Up @@ -168,7 +168,7 @@ export class CiraConfigTable implements ICiraConfigTable {
ciraConfig.tenantId,
ciraConfig.version
])
latestItem = await this.getByName(ciraConfig.configName)
latestItem = await this.getByName(ciraConfig.configName, ciraConfig.tenantId)
if (results.rowCount > 0) {
return latestItem
}
Expand Down
4 changes: 2 additions & 2 deletions src/data/postgres/tables/domains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export class DomainsTable implements IDomainsTable {
amtDomain.tenantId
])
if (results.rowCount > 0) {
const domain = await this.getByName(amtDomain.profileName)
const domain = await this.getByName(amtDomain.profileName, amtDomain.tenantId)
return domain
}
return null
Expand Down Expand Up @@ -168,7 +168,7 @@ export class DomainsTable implements IDomainsTable {
amtDomain.tenantId,
amtDomain.version
])
latestItem = await this.getByName(amtDomain.profileName)
latestItem = await this.getByName(amtDomain.profileName, amtDomain.tenantId)
if (results.rowCount > 0) {
return latestItem
}
Expand Down
8 changes: 4 additions & 4 deletions src/data/postgres/tables/profiles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ describe('profiles tests', () => {
test('should get ciraconfig for profile', async () => {
const ciraConfigSpy = jest.spyOn(db.ciraConfigs, 'getByName')
ciraConfigSpy.mockResolvedValue({} as any)
const result = await profilesTable.getCiraConfigForProfile(profileName)
const result = await profilesTable.getCiraConfigForProfile(profileName, '')
expect(result).toStrictEqual({})
expect(ciraConfigSpy).toHaveBeenCalledWith(profileName)
expect(ciraConfigSpy).toHaveBeenCalledWith(profileName, '')
})
})
describe('Delete', () => {
Expand Down Expand Up @@ -188,7 +188,7 @@ describe('profiles tests', () => {

expect(result).toBe(amtConfig)
expect(profileWirelessConfigsSpy).toHaveBeenCalledWith(amtConfig.wifiConfigs, amtConfig.profileName, amtConfig.tenantId)
expect(getByNameSpy).toHaveBeenCalledWith(amtConfig.profileName)
expect(getByNameSpy).toHaveBeenCalledWith(amtConfig.profileName, amtConfig.tenantId)
expect(querySpy).toBeCalledTimes(1)
expect(querySpy).toBeCalledWith(`
INSERT INTO profiles(
Expand Down Expand Up @@ -227,7 +227,7 @@ describe('profiles tests', () => {

expect(result).toBe(amtConfig)
expect(profileWirelessConfigsSpy).not.toHaveBeenCalledWith(amtConfig.wifiConfigs, amtConfig.profileName, amtConfig.tenantId)
expect(getByNameSpy).toHaveBeenCalledWith(amtConfig.profileName)
expect(getByNameSpy).toHaveBeenCalledWith(amtConfig.profileName, amtConfig.tenantId)
expect(querySpy).toBeCalledTimes(1)
expect(querySpy).toBeCalledWith(`
INSERT INTO profiles(
Expand Down
15 changes: 10 additions & 5 deletions src/data/postgres/tables/profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class ProfilesTable implements IProfilesTable {

/**
* @description Get count of all profiles from DB
* @param {string} tenantId
* @returns {number}
*/
async getCount (tenantId: string = ''): Promise<number> {
Expand All @@ -39,6 +40,7 @@ export class ProfilesTable implements IProfilesTable {
* @description Get all AMT Profiles from DB
* @param {number} top
* @param {number} skip
* @param {string} tenantId
* @returns {Pagination} returns an array of AMT profiles from DB
*/
async get (top: number = DEFAULT_TOP, skip: number = DEFAULT_SKIP, tenantId: string = ''): Promise<AMTConfiguration[]> {
Expand Down Expand Up @@ -85,6 +87,7 @@ export class ProfilesTable implements IProfilesTable {
/**
* @description Get AMT Profile from DB by name
* @param {string} profileName
* @param {string} tenantId
* @returns {AMTConfiguration} AMT Profile object
*/
async getByName (profileName: string, tenantId: string = ''): Promise<AMTConfiguration> {
Expand Down Expand Up @@ -130,15 +133,17 @@ export class ProfilesTable implements IProfilesTable {
/**
* @description Get CIRA config from DB by name
* @param {string} configName
* @param {string} tenantId
* @returns {CIRAConfig} CIRA config object
*/
async getCiraConfigForProfile (configName: string): Promise<CIRAConfig> {
return await this.db.ciraConfigs.getByName(configName)
async getCiraConfigForProfile (configName: string, tenantId: string): Promise<CIRAConfig> {
return await this.db.ciraConfigs.getByName(configName, tenantId)
}

/**
* @description Delete AMT Profile from DB by name
* @param {string} profileName
* @param {string} tenantId
* @returns {boolean} Return true on successful deletion
*/
async delete (profileName: string, tenantId: string = ''): Promise<boolean> {
Expand Down Expand Up @@ -198,7 +203,7 @@ export class ProfilesTable implements IProfilesTable {
await this.db.profileWirelessConfigs.createProfileWifiConfigs(amtConfig.wifiConfigs, amtConfig.profileName, amtConfig.tenantId)
}

return await this.getByName(amtConfig.profileName)
return await this.getByName(amtConfig.profileName, amtConfig.tenantId)
} catch (error) {
this.log.error(`Failed to insert AMT profile: ${amtConfig.profileName}`, error)
if (error instanceof RPSError) {
Expand Down Expand Up @@ -249,11 +254,11 @@ export class ProfilesTable implements IProfilesTable {
if (amtConfig.wifiConfigs?.length > 0) {
await this.db.profileWirelessConfigs.createProfileWifiConfigs(amtConfig.wifiConfigs, amtConfig.profileName)
}
latestItem = await this.getByName(amtConfig.profileName)
latestItem = await this.getByName(amtConfig.profileName, amtConfig.tenantId)
return latestItem
}
// if rowcount is 0, we assume update failed and grab the current reflection of the record in the DB to be returned in the Concurrency Error
latestItem = await this.getByName(amtConfig.profileName)
latestItem = await this.getByName(amtConfig.profileName, amtConfig.tenantId)
} catch (error) {
this.log.error(`Failed to update AMT profile: ${amtConfig.profileName}`, error)
if (error.code === '23503') { // Foreign key constraint violation
Expand Down
6 changes: 3 additions & 3 deletions src/data/postgres/tables/wirelessProfiles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe('wireless profiles tests', () => {
const result = await wirelessProfilesTable.insert(wirelessConfig)

expect(result).toBe(wirelessConfig)
expect(getByNameSpy).toHaveBeenCalledWith(wirelessConfig.profileName)
expect(getByNameSpy).toHaveBeenCalledWith(wirelessConfig.profileName, wirelessConfig.tenantId)
expect(querySpy).toBeCalledTimes(1)
expect(querySpy).toBeCalledWith(`
INSERT INTO wirelessconfigs
Expand Down Expand Up @@ -191,7 +191,7 @@ describe('wireless profiles tests', () => {
getByNameSpy.mockResolvedValue(wirelessConfig)
const result = await wirelessProfilesTable.update(wirelessConfig)
expect(result).toBe(wirelessConfig)
expect(getByNameSpy).toHaveBeenCalledWith(wirelessConfig.profileName)
expect(getByNameSpy).toHaveBeenCalledWith(wirelessConfig.profileName, wirelessConfig.tenantId)
expect(querySpy).toBeCalledTimes(1)
expect(querySpy).toBeCalledWith(`
UPDATE wirelessconfigs
Expand All @@ -218,7 +218,7 @@ describe('wireless profiles tests', () => {
const getByNameSpy = jest.spyOn(wirelessProfilesTable, 'getByName')
getByNameSpy.mockResolvedValue(wirelessConfig)
await expect(wirelessProfilesTable.update(wirelessConfig)).rejects.toThrow(CONCURRENCY_MESSAGE)
expect(getByNameSpy).toHaveBeenCalledWith(wirelessConfig.profileName)
expect(getByNameSpy).toHaveBeenCalledWith(wirelessConfig.profileName, wirelessConfig.tenantId)
expect(querySpy).toBeCalledTimes(1)
expect(querySpy).toBeCalledWith(`
UPDATE wirelessconfigs
Expand Down
4 changes: 2 additions & 2 deletions src/data/postgres/tables/wirelessProfiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export class WirelessProfilesTable implements IWirelessProfilesTable {
wirelessConfig.tenantId
])
if (results?.rowCount > 0) {
const profile = await this.getByName(wirelessConfig.profileName)
const profile = await this.getByName(wirelessConfig.profileName, wirelessConfig.tenantId)
return profile
}
return null
Expand Down Expand Up @@ -186,7 +186,7 @@ export class WirelessProfilesTable implements IWirelessProfilesTable {
wirelessConfig.tenantId,
wirelessConfig.version
])
latestItem = await this.getByName(wirelessConfig.profileName)
latestItem = await this.getByName(wirelessConfig.profileName, wirelessConfig.tenantId)
if (results?.rowCount > 0) {
return latestItem
}
Expand Down
Loading

0 comments on commit 0ce2be7

Please sign in to comment.