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

ZMS-136 #645

Merged
merged 3 commits into from
Mar 18, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
224 changes: 187 additions & 37 deletions lib/api/asps.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,70 @@ const tools = require('../tools');
const roles = require('../roles');
const util = require('util');
const { sessSchema, sessIPSchema, booleanSchema } = require('../schemas');
const { userId } = require('../schemas/request/general-schemas');
const { successRes } = require('../schemas/response/general-schemas');

module.exports = (db, server, userHandler) => {
const mobileconfigGetSignedConfig = util.promisify(mobileconfig.getSignedConfig.bind(mobileconfig));

server.get(
'/users/:user/asps',
{
path: '/users/:user/asps',
tags: ['ApplicationPasswords'],
summary: 'List Application Passwords',
validationObjs: {
requestBody: {},
queryParams: {
showAll: booleanSchema.default(false).description('If not true then skips entries with a TTL set'),
sess: sessSchema,
ip: sessIPSchema
},
pathParams: { user: userId },
response: {
200: {
description: 'Success',
model: Joi.object({
success: successRes,
results: Joi.array()
.items(
Joi.object({
id: Joi.string().required().description('ID of the Application Password'),
description: Joi.string().required().description('Description'),
scopes: Joi.array()
.items(
Joi.string()
.required()
.valid(...consts.SCOPES, '*')
)
.required()
.description('Allowed scopes for the Application Password'),
lastUse: Joi.object({
time: Joi.date().required().description('Datestring of last use or false if password has not been used'),
event: Joi.string().required().description('Event ID of the security log for the last authentication')
})
.required()
.$_setFlag('objectName', 'LastUse')
.description('Information about last use'),
created: Joi.date().required().description('Datestring'),
expires: Joi.date().required().description('Application password expires after the given date')
}).$_setFlag('objectName', 'GetASPsResult')
)
.required()
.description('Event listing')
})
}
}
}
},
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
showAll: booleanSchema.default(false),
sess: sessSchema,
ip: sessIPSchema
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

const result = schema.validate(req.params, {
Expand Down Expand Up @@ -130,15 +180,55 @@ module.exports = (db, server, userHandler) => {
);

server.get(
'/users/:user/asps/:asp',
{
path: '/users/:user/asps/:asp',
tags: ['ApplicationPasswords'],
summary: 'Request ASP information',
validationObjs: {
requestBody: {},
queryParams: {
sess: sessSchema,
ip: sessIPSchema
},
pathParams: { user: userId, asp: Joi.string().hex().lowercase().length(24).required().description('ID of the Application Password') },
response: {
200: {
description: 'Success',
model: Joi.object({
success: successRes,
id: Joi.string().required().description('ID of the Application Password'),
description: Joi.string().required().description('Description'),
scopes: Joi.array()
.items(
Joi.string()
.valid(...consts.SCOPES, '*')
.required()
)
.required()
.description('Allowed scopes for the Application Password'),
lastUse: Joi.object({
time: Joi.date().required().description('Datestring of last use or false if password has not been used'),
event: Joi.string().required().description('Event ID of the security log for the last authentication')
})
.required()
.$_setFlag('objectName', 'LastUse')
.description('Information about last use'),
created: Joi.date().required().description('Datestring'),
expires: Joi.date().required().description('Application password expires after the given date')
})
}
}
}
},
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
asp: Joi.string().hex().lowercase().length(24).required(),
sess: sessSchema,
ip: sessIPSchema
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

const result = schema.validate(req.params, {
Expand Down Expand Up @@ -204,28 +294,74 @@ module.exports = (db, server, userHandler) => {
);

server.post(
'/users/:user/asps',
{
path: '/users/:user/asps',
tags: ['ApplicationPasswords'],
summary: 'Create new Application Password',
validationObjs: {
requestBody: {
description: Joi.string().trim().max(255).required().description('Description for the Application Password entry'),
scopes: Joi.array()
.items(
Joi.string()
.valid(...consts.SCOPES, '*')
.required()
)
.unique()
.description(
'List of scopes this Password applies to. Special scope "*" indicates that this password can be used for any scope except "master"'
),
address: Joi.string()
.empty('')
.email({ tlds: false })
.description(
'E-mail address to be used as the account address in mobileconfig file. Must be one of the listed identity addresses of the user. Defaults to the main address of the user'
),
password: Joi.string()
.empty('')
.pattern(/^[a-z]{16}$/, { name: 'password' })
.description('Optional pregenerated password. Must be 16 characters, latin letters only.'),
generateMobileconfig: booleanSchema
.default(false)
.description('If true then result contains a mobileconfig formatted file with account config'),
ttl: Joi.number().empty([0, '']).description('TTL in seconds for this password. Every time password is used, TTL is reset to this value'),
sess: sessSchema,
ip: sessIPSchema
},
queryParams: {},
pathParams: { user: userId },
response: {
200: {
description: 'Success',
model: Joi.object({
success: successRes,
id: Joi.string().required().description('ID of the Application Password'),
password: Joi.string()
.required()
.description(
'Application Specific Password. Generated password is whitespace agnostic, so it could be displayed to the client as "abcd efgh ijkl mnop" instead of "abcdefghijklmnop"'
),
mobileconfig: Joi.string()
.required()
.description(
'Base64 encoded mobileconfig file. Generated profile file should be sent to the client with Content-Type value of application/x-apple-aspen-config.'
),
name: Joi.string().required().description('Account name'),
address: Joi.string().required().description('Account address or the address specified in params of this endpoint')
})
}
}
}
},
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
description: Joi.string().trim().max(255).required(),
scopes: Joi.array()
.items(
Joi.string()
.valid(...consts.SCOPES, '*')
.required()
)
.unique(),
address: Joi.string().empty('').email({ tlds: false }),
password: Joi.string()
.empty('')
.pattern(/^[a-z]{16}$/, { name: 'password' }),
generateMobileconfig: booleanSchema.default(false),
ttl: Joi.number().empty([0, '']),
sess: sessSchema,
ip: sessIPSchema
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

if (typeof req.params.scopes === 'string') {
Expand Down Expand Up @@ -432,15 +568,29 @@ module.exports = (db, server, userHandler) => {
);

server.del(
'/users/:user/asps/:asp',
{
path: '/users/:user/asps/:asp',
tags: ['ApplicationPasswords'],
summary: 'Delete an Application Password',
validationObjs: {
requestBody: {},
queryParams: {
sess: sessSchema,
ip: sessIPSchema
},
pathParams: { user: userId, asp: Joi.string().hex().lowercase().length(24).required().description('ID of the Application Password') },
response: { 200: { description: 'Success', model: Joi.object({ success: successRes }) } }
}
},
tools.responseWrapper(async (req, res) => {
res.charSet('utf-8');

const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(),
asp: Joi.string().hex().lowercase().length(24).required(),
sess: sessSchema,
ip: sessIPSchema
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;

const schema = Joi.object({
...pathParams,
...requestBody,
...queryParams
});

const result = schema.validate(req.params, {
Expand Down