Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ async function getUser (req, res, next) {
return res.status(404).json(error.userDne(identifier))
}

org = await repo.getOrg(orgUUID, true)
org = await repo.findOneByUUID(orgUUID)

userToGetParameters = {
org: org.short_name,
Expand All @@ -133,7 +133,7 @@ async function getUser (req, res, next) {
return res.status(404).json(error.userDne(userToGetParameters.username))
}

org = await repo.getOrg(req.ctx.params.shortname)
org = await repo.findOneByShortName(req.ctx.params.shortname)
userToGetParameters = {
org: org.short_name,
username: result.username
Expand Down
58 changes: 50 additions & 8 deletions src/repositories/baseOrgRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,43 @@ const {
handleAuthorityModelChange
} = require('./baseOrgRepositoryHelpers')
const { normalizeOrgCveWebsiteUpdateDate } = require('../utils/dateOnly')
const RegistryOrgResponseSchema = require('../../schemas/registry-org/get-registry-org-response.json')

const INTERNAL_UNDERSCORE_FIELDS = ['_id', '__t', '__v']

function isResponseExtensionField (key) {
return key.startsWith('_') && !INTERNAL_UNDERSCORE_FIELDS.includes(key)
}

function maskObjectToSchemaProperties (obj, schema) {
if (!_.isPlainObject(obj) || !schema?.properties) return obj

const maskedObj = {}
Object.keys(obj).forEach(key => {
const propertySchema = schema.properties[key]
if (!propertySchema) return

if (_.isPlainObject(obj[key]) && propertySchema.properties) {
maskedObj[key] = maskObjectToSchemaProperties(obj[key], propertySchema)
} else {
maskedObj[key] = obj[key]
}
})

return maskedObj
}

function maskOrgForResponseSchema (orgObj, fieldsToPreserve = []) {
const preservedFields = _.pick(orgObj, fieldsToPreserve)
const extensionFields = _.pickBy(orgObj, (_value, key) => isResponseExtensionField(key))
const maskedOrg = maskObjectToSchemaProperties(_.cloneDeep(orgObj), RegistryOrgResponseSchema)

return {
...maskedOrg,
...extensionFields,
...preservedFields
}
}

/**
* @function setAggregateOrgObj
Expand Down Expand Up @@ -109,15 +146,16 @@ function getOrgProjection (isSecretariat = false) {
return projection
}

function filterOrg (orgObj, isSecretariat = false) {
function filterOrg (orgObj, isSecretariat = false, applyResponseMask = false, fieldsToPreserve = []) {
const CONSTANTS = getConstants()
const _ = require('lodash')
normalizeOrgCveWebsiteUpdateDate(orgObj, { output: true })
let fieldsToOmit = [...CONSTANTS.ORG_EXCLUDED_FIELDS]
if (!isSecretariat) {
fieldsToOmit = [...fieldsToOmit, ...CONSTANTS.ORG_RESTRICTED_FIELDS]
}
return _.omit(orgObj, fieldsToOmit)
const filteredOrg = _.omit(orgObj, fieldsToOmit)
return applyResponseMask ? maskOrgForResponseSchema(filteredOrg, fieldsToPreserve) : filteredOrg
}

class BaseOrgRepository extends BaseRepository {
Expand Down Expand Up @@ -436,11 +474,12 @@ class BaseOrgRepository extends BaseRepository {

// Strip nulls returned by DocumentDB to prevent schema validation errors
if (pg.itemsList) {
pg.itemsList.forEach(org => {
pg.itemsList = pg.itemsList.map(org => {
if (org.reports_to === null) {
delete org.reports_to
}
normalizeOrgCveWebsiteUpdateDate(org, { output: true })
return returnLegacyFormat ? org : filterOrg(org, isSecretariat, true)
})
}

Expand Down Expand Up @@ -517,8 +556,11 @@ class BaseOrgRepository extends BaseRepository {
}
}

normalizeOrgCveWebsiteUpdateDate(result, { output: true })
return deepRemoveEmpty(result)
if (returnLegacyFormat) {
normalizeOrgCveWebsiteUpdateDate(result, { output: true })
return deepRemoveEmpty(result)
}
return deepRemoveEmpty(filterOrg(result, isSecretariat, true))
}

/**
Expand Down Expand Up @@ -737,7 +779,7 @@ class BaseOrgRepository extends BaseRepository {
}

const rawRegistryOrgObject = registryObject.toObject()
return filterOrg(deepRemoveEmpty(rawRegistryOrgObject), isSecretariat)
return filterOrg(deepRemoveEmpty(rawRegistryOrgObject), isSecretariat, true)
}

/**
Expand Down Expand Up @@ -928,7 +970,7 @@ class BaseOrgRepository extends BaseRepository {
return filterOrg(deepRemoveEmpty(legacyOrg.toObject()), isSecretariat)
}

return filterOrg(deepRemoveEmpty(registryOrg.toObject()), isSecretariat)
return filterOrg(deepRemoveEmpty(registryOrg.toObject()), isSecretariat, true)
}

/**
Expand Down Expand Up @@ -1051,7 +1093,7 @@ class BaseOrgRepository extends BaseRepository {
const plainJavascriptRegistryOrg = updatedRegistryOrg.toObject()
plainJavascriptRegistryOrg.conversation = conversationArray
plainJavascriptRegistryOrg.joint_approval_required = !(isSecretariat || _.isEmpty(jointApprovalFieldsRegistry))
return filterOrg(deepRemoveEmpty(plainJavascriptRegistryOrg), isSecretariat)
return filterOrg(deepRemoveEmpty(plainJavascriptRegistryOrg), isSecretariat, true, ['joint_approval_required'])
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/repositories/baseUserRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ class BaseUserRepository extends BaseRepository {
const currentOrgUUID = legacyUser ? legacyUser.org_UUID : registryUser?.org_UUID // Fallback if schema supports it
const currentOrg = currentOrgUUID ? await baseOrgRepository.findOneByUUID(currentOrgUUID) : null
const newOrg = await baseOrgRepository.findOneByShortName(incomingUser.org_short_name)
const wasAdmin = currentOrg?.admins?.includes(identifier) ?? false

if (!newOrg) {
throw new Error(`Organization ${incomingUser.org_short_name} not found`)
Expand All @@ -687,7 +688,7 @@ class BaseUserRepository extends BaseRepository {
if (!Array.isArray(newOrg.users)) newOrg.users = []
newOrg.users.addToSet(identifier)

const isAdmin = updatedRegistryUser?.role === 'ADMIN' || (updatedLegacyUser?.authority?.active_roles?.includes('ADMIN'))
const isAdmin = wasAdmin || updatedRegistryUser?.role === 'ADMIN' || (updatedLegacyUser?.authority?.active_roles?.includes('ADMIN'))
if (isAdmin) {
if (!Array.isArray(newOrg.admins)) {
newOrg.admins = []
Expand Down
71 changes: 71 additions & 0 deletions test/integration-tests/registry-org/registryOrgCRUDTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ describe('Testing /registryOrg endpoints', () => {
.set(constants.nonSecretariatUserHeaders)
.then((res) => {
expect(res).to.have.status(200)
expect(postedConvoUUID).to.not.be.undefined
expect(res.body.conversation).to.be.an('array')
// Remember non-secretariats don't see the UUID field returned in conversation so we can't search by UUID!
// We just check the latest one or search by body.
Expand All @@ -381,6 +382,72 @@ describe('Testing /registryOrg endpoints', () => {
expect(convo).to.not.have.property('author_id')
})
})
it('Masks stale schema fields from registry org GET responses', async () => {
const BaseOrg = require('../../../src/model/baseorg')
const staleOrg = {
...testRegistryOrg,
short_name: 'registry_org_stale_mask',
long_name: 'Registry Org Stale Mask'
}

await chai.request(app)
.post('/api/registry/org')
.set(secretariatHeaders)
.send(staleOrg)
.then((res) => {
expect(res).to.have.status(200)
})

await BaseOrg.collection.updateOne(
{ short_name: staleOrg.short_name },
{
$set: {
'program_data.advisory_location_require_credentials': true,
'program_data.vulnerability_advisory_location_for_web_scraping': ['https://example.com/stale'],
users: ['d41d8cd9-8f00-3204-a980-0998ecf8427e'],
admins: ['d41d8cd9-8f00-3204-a980-0998ecf8427e']
}
}
)

let orgFromList
await chai.request(app)
.get('/api/registry/org')
.set(secretariatHeaders)
.then((res) => {
expect(res).to.have.status(200)
orgFromList = res.body.organizations.find(org => org.short_name === staleOrg.short_name)
expect(orgFromList).to.not.be.undefined
expect(orgFromList).to.have.property('created')
expect(orgFromList).to.have.property('last_updated')
expect(orgFromList.users).to.deep.equal(['d41d8cd9-8f00-3204-a980-0998ecf8427e'])
expect(orgFromList.admins).to.deep.equal(['d41d8cd9-8f00-3204-a980-0998ecf8427e'])
expect(orgFromList.program_data).to.not.have.property('advisory_location_require_credentials')
expect(orgFromList.program_data).to.not.have.property('vulnerability_advisory_location_for_web_scraping')
})

const putBody = { ...orgFromList }
delete putBody.created
delete putBody.last_updated
delete putBody.users
delete putBody.admins
delete putBody.conversation
delete putBody.reports_to

await chai.request(app)
.put(`/api/registry/org/${staleOrg.short_name}`)
.set(secretariatHeaders)
.send({
...putBody,
long_name: 'Registry Org Stale Mask Updated'
})
.then((res) => {
expect(res).to.have.status(200)
expect(res.body.updated.long_name).to.equal('Registry Org Stale Mask Updated')
expect(res.body.updated.program_data).to.not.have.property('advisory_location_require_credentials')
expect(res.body.updated.program_data).to.not.have.property('vulnerability_advisory_location_for_web_scraping')
})
})
})
context('Negative Tests', () => {
it('Fails to get a registry organization that does not exist', async () => {
Expand Down Expand Up @@ -637,6 +704,10 @@ describe('Testing /registryOrg endpoints', () => {
expect(res.body.updated.oversees).to.be.an('array').that.includes(createdSubOrgUUID)
})

const BaseOrg = require('../../../src/model/baseorg')
const registryOrgCheck = await BaseOrg.findOne({ short_name: createdOrg.short_name })
expect(registryOrgCheck.oversees).to.be.an('array').that.includes(createdSubOrgUUID)

// Assert that the sub org dynamically returns reports_to matching the main org's UUID
await chai.request(app)
.get(`/api/registry/org/${subOrg.short_name}`)
Expand Down
Loading