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

Resolves #1018 - Character and User limits #1107

Merged
merged 7 commits into from
Jul 25, 2023
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
4 changes: 4 additions & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ function getConstants () {
},
MAX_SHORTNAME_LENGTH: 32,
MIN_SHORTNAME_LENGTH: 2,
MAX_FIRSTNAME_LENGTH: 100,
MAX_LASTNAME_LENGTH: 100,
MAX_MIDDLENAME_LENGTH: 100,
MAX_SUFFIX_LENGTH: 100,
CVE_ID_PATTERN: cveSchemaV5.definitions.cveId.pattern,
// Ajv's pattern validation uses the "u" (unicode) flag:
// https://ajv.js.org/json-schema.html#pattern
Expand Down
7 changes: 7 additions & 0 deletions src/controller/org.controller/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ class OrgControllerError extends idrErr.IDRError {
err.message = 'Please have another admin user from your organization change your role.'
return err
}

userLimitReached () {
const err = {}
err.error = 'NUMBER_OF_USERS_IN_ORG_LIMIT_REACHED'
err.message = 'The requested user can not be created and added to the organization because the organization has hit its limit of 100 users. Contact the Secretariat.'
return err
}
}

module.exports = {
Expand Down
16 changes: 8 additions & 8 deletions src/controller/org.controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,10 +550,10 @@ router.post('/org/:shortname/user',
body(['username']).isString().trim().escape().notEmpty().custom(isValidUsername),
body(['org_uuid']).optional().isString().trim().escape(),
body(['uuid']).optional().isString().trim().escape(),
body(['name.first']).optional().isString().trim().escape(),
body(['name.last']).optional().isString().trim().escape(),
body(['name.middle']).optional().isString().trim().escape(),
body(['name.suffix']).optional().isString().trim().escape(),
body(['name.first']).optional().isString().trim().escape().isLength({ max: CONSTANTS.MAX_FIRSTNAME_LENGTH }).withMessage(errorMsgs.FIRSTNAME_LENGTH),
body(['name.last']).optional().isString().trim().escape().isLength({ max: CONSTANTS.MAX_LASTNAME_LENGTH }).withMessage(errorMsgs.LASTNAME_LENGTH),
body(['name.middle']).optional().isString().trim().escape().isLength({ max: CONSTANTS.MAX_MIDDLENAME_LENGTH }).withMessage(errorMsgs.MIDDLENAME_LENGTH),
body(['name.suffix']).optional().isString().trim().escape().isLength({ max: CONSTANTS.MAX_SUFFIX_LENGTH }).withMessage(errorMsgs.SUFFIX_LENGTH),
body(['authority.active_roles']).optional()
.custom(mw.isFlatStringArray)
.customSanitizer(toUpperCaseArray)
Expand Down Expand Up @@ -722,10 +722,10 @@ router.put('/org/:shortname/user/:username',
query(['active']).optional().isBoolean({ loose: true }),
query(['new_username']).optional().isString().trim().escape().notEmpty().custom(isValidUsername),
query(['org_short_name']).optional().isString().trim().escape().notEmpty().isLength({ min: CONSTANTS.MIN_SHORTNAME_LENGTH, max: CONSTANTS.MAX_SHORTNAME_LENGTH }),
query(['name.first']).optional().isString().trim().escape(),
query(['name.last']).optional().isString().trim().escape(),
query(['name.middle']).optional().isString().trim().escape(),
query(['name.suffix']).optional().isString().trim().escape(),
body(['name.first']).optional().isString().trim().escape().isLength({ max: CONSTANTS.MAX_FIRSTNAME_LENGTH }).withMessage(errorMsgs.FIRSTNAME_LENGTH),
body(['name.last']).optional().isString().trim().escape().isLength({ max: CONSTANTS.MAX_LASTNAME_LENGTH }).withMessage(errorMsgs.LASTNAME_LENGTH),
body(['name.middle']).optional().isString().trim().escape().isLength({ max: CONSTANTS.MAX_MIDDLENAME_LENGTH }).withMessage(errorMsgs.MIDDLENAME_LENGTH),
body(['name.suffix']).optional().isString().trim().escape().isLength({ max: CONSTANTS.MAX_SUFFIX_LENGTH }).withMessage(errorMsgs.SUFFIX_LENGTH),
query(['active_roles.add']).optional().toArray()
.custom(isFlatStringArray)
.customSanitizer(toUpperCaseArray)
Expand Down
6 changes: 6 additions & 0 deletions src/controller/org.controller/org.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,12 @@ async function createUser (req, res, next) {
return res.status(404).json(error.orgDnePathParam(orgShortName))
}

const users = await userRepo.findUsersByOrgUUID(orgUUID)

if (users >= 100) {
return res.status(400).json(error.userLimitReached())
}

Object.keys(req.ctx.body).forEach(k => {
const key = k.toLowerCase()

Expand Down
6 changes: 5 additions & 1 deletion src/middleware/errorMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@ module.exports = {
CVE_FILTERED_STATES: 'Invalid record state. Valid states are: PUBLISHED, REJECTED',
COUNT_ONLY: 'Invalid count_only value. Value should be 1, true, or yes to indicate true, or 0, false, or no to indicate false',
TIMESTAMP_FORMAT: "Bad date, or invalid timestamp format: valid format is yyyy-MM-ddTHH:mm:ss or yyyy-MM-ddTHH:mm:ssZZZZ (to use '+' in timezone offset, encode as '%2B)",
CNA_MODIFIED: 'Invalid cna_modified value. Value should be 1, true, or yes to indicate true, or 0, false, or no to indicate false'
CNA_MODIFIED: 'Invalid cna_modified value. Value should be 1, true, or yes to indicate true, or 0, false, or no to indicate false',
FIRSTNAME_LENGTH: 'Invalid name.first. Name must be between 1 and 100 characters in length.',
LASTNAME_LENGTH: 'Invalid name.last. Name must be between 1 and 100 characters in length.',
MIDDLENAME_LENGTH: 'Invalid name.middle. Name must be between 1 and 100 characters in length.',
SUFFIX_LENGTH: 'Invalid name.suffix. Name must be between 1 and 100 characters in length.'
}
4 changes: 4 additions & 0 deletions src/model/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ UserSchema.query.byUUID = function (uuid) {
return this.where({ UUID: uuid })
}

UserSchema.query.byOrgUUID = function (orgUUID) {
return this.where({ org_UUID: orgUUID })
}

UserSchema.query.byUserNameAndOrgUUID = function (username, orgUUID) {
return this.where({ username: username, org_UUID: orgUUID })
}
Expand Down
4 changes: 4 additions & 0 deletions src/repositories/userRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class UserRepository extends BaseRepository {
return this.collection.findOne().byUUID(UUID)
}

async findUsersByOrgUUID (orgUUID) {
return this.collection.find().byOrgUUID(orgUUID).countDocuments().exec()
}

async findOneByUserNameAndOrgUUID (userName, orgUUID) {
return this.collection.findOne().byUserNameAndOrgUUID(userName, orgUUID)
}
Expand Down
187 changes: 187 additions & 0 deletions test/integration-tests/org/postOrgUsersTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/* eslint-disable no-unused-expressions */
const chai = require('chai')
chai.use(require('chai-http'))
const expect = chai.expect
const { faker } = require('@faker-js/faker')

const constants = require('../constants.js')
const app = require('../../../src/index.js')
const getConstants = require('../../../src/constants').getConstants
const argon2 = require('argon2')
const _ = require('lodash')
const User = require('../../../src/model/user')
const cryptoRandomString = require('crypto-random-string')

const shortName = { shortname: 'win_5' }

describe('Testing user post endpoint', () => {
let orgUuid
context('Positive Tests', () => {
it('Allows creation of user', async () => {
await chai.request(app)
.post('/api/org/win_5/user')
.set({ ...constants.headers, ...shortName })
.send({
username: 'fakeuser999',
name: {
first: 'Dave',
last: 'FakeLastName',
middle: 'Cool',
suffix: 'Mr.'
},
authority: {
active_roles: [
'ADMIN'
]
}
})
.then((res, err) => {
expect(err).to.be.undefined
orgUuid = res.body.created.org_UUID
})
})
})
context('Negitive Test', () => {
it('Fails creation of user for bad long first name', async () => {
await chai.request(app)
.post('/api/org/win_5/user')
.set({ ...constants.headers, ...shortName })
.send({
username: 'fakeuser9999',
name: {
first: 'VerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnm1',
last: 'FakeLastName',
middle: 'Cool',
suffix: 'Mr.'
},
authority: {
active_roles: [
'ADMIN'
]
}
})
.then((res, err) => {
expect(res).to.have.status(400)
expect(_.some(res.body.details, { msg: 'Invalid name.first. Name must be between 1 and 100 characters in length.' })).to.be.true
expect(err).to.be.undefined
})
})
it('Fails creation of user for bad long last name', async () => {
await chai.request(app)
.post('/api/org/win_5/user')
.set({ ...constants.headers, ...shortName })
.send({
username: 'fakeuser1000',
name: {
first: 'FirstName',
last: 'VerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnm1',
middle: 'Cool',
suffix: 'Mr.'
},
authority: {
active_roles: [
'ADMIN'
]
}
})
.then((res, err) => {
expect(res).to.have.status(400)
expect(_.some(res.body.details, { msg: 'Invalid name.last. Name must be between 1 and 100 characters in length.' })).to.be.true
expect(err).to.be.undefined
})
})
it('Fails creation of user for bad long middle name', async () => {
await chai.request(app)
.post('/api/org/win_5/user')
.set({ ...constants.headers, ...shortName })
.send({
username: 'fakeuser1001',
name: {
first: 'FirstName',
last: 'LastName',
middle: 'VerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnm1',
suffix: 'Mr.'
},
authority: {
active_roles: [
'ADMIN'
]
}
})
.then((res, err) => {
expect(res).to.have.status(400)
expect(_.some(res.body.details, { msg: 'Invalid name.middle. Name must be between 1 and 100 characters in length.' })).to.be.true
expect(err).to.be.undefined
})
})
it('Fails creation of user for bad long suffix name', async () => {
await chai.request(app)
.post('/api/org/win_5/user')
.set({ ...constants.headers, ...shortName })
.send({
username: 'fakeuser1002',
name: {
first: 'FirstName',
last: 'LastName',
middle: 'MiddleName',
suffix: 'VerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnmVerylongnm1'
},
authority: {
active_roles: [
'ADMIN'
]
}
})
.then((res, err) => {
expect(res).to.have.status(400)
expect(_.some(res.body.details, { msg: 'Invalid name.suffix. Name must be between 1 and 100 characters in length.' })).to.be.true
expect(err).to.be.undefined
})
})
it('Fails creation of user for trying to add the 101th user', async () => {
const numberOfUsers = await User.where({ org_UUID: orgUuid }).countDocuments().exec()
for (let i = 0; i < (100 - numberOfUsers); i++) {
const newUser = new User()

newUser.name.first = faker.name.firstName()
newUser.name.last = faker.name.lastName()
newUser.username = faker.internet.userName({ firstName: newUser.name.first, lastName: newUser.name.last })
newUser.org_UUID = orgUuid
newUser.UUID = faker.datatype.uuid()
const randomKey = cryptoRandomString({ length: getConstants().CRYPTO_RANDOM_STRING_LENGTH })
newUser.secret = await argon2.hash(randomKey)

newUser.authority = {
active_roles: [
'ADMIN'
]
}

await User.findOneAndUpdate().byUserNameAndOrgUUID(newUser.userName, newUser.org_UUID).updateOne(newUser).setOptions({ upsert: true })
}

await chai.request(app)
.post('/api/org/win_5/user')
.set({ ...constants.headers, ...shortName })
.send({
username: 'Fake101User',
name: {
first: 'Dave',
last: 'FakeLastName',
middle: 'Cool',
suffix: 'Mr.'
},
authority: {
active_roles: [
'ADMIN'
]
}
})
.then((res, err) => {
expect(err).to.be.undefined
expect(res).to.have.status(400)
expect(res.body.error).to.contain('NUMBER_OF_USERS_IN_ORG_LIMIT_REACHED')
})
})
})
})
Loading