use new permissions system for users api

This commit is contained in:
Andris Reinman 2018-08-29 15:27:57 +03:00
parent fffc259eb0
commit 3b645d39f3
2 changed files with 865 additions and 765 deletions

View file

@ -10,6 +10,13 @@
"authentication": {
"create:any": ["*"],
"read:any": ["*"]
},
"users": {
"create:any": ["*"],
"read:any": ["*"],
"update:any": ["*"],
"delete:any": ["*"]
}
},

View file

@ -10,8 +10,16 @@ const openpgp = require('openpgp');
const addressparser = require('nodemailer/lib/addressparser');
const libmime = require('libmime');
const consts = require('../consts');
const roles = require('../roles');
const util = require('util');
module.exports = (db, server, userHandler) => {
const createUser = util.promisify(userHandler.create.bind(userHandler));
const updateUser = util.promisify(userHandler.update.bind(userHandler));
const logoutUser = util.promisify(userHandler.logout.bind(userHandler));
const resetUser = util.promisify(userHandler.reset.bind(userHandler));
const deleteUser = util.promisify(userHandler.delete.bind(userHandler));
/**
* @api {get} /users List registered Users
* @apiName GetUsers
@ -138,6 +146,9 @@ module.exports = (db, server, userHandler) => {
return next();
}
// permissions check
req.validate(roles.can(req.role).readAny('users'));
let query = result.value.query;
let limit = result.value.limit;
let page = result.value.page;
@ -361,7 +372,9 @@ module.exports = (db, server, userHandler) => {
* "error": "This username already exists"
* }
*/
server.post('/users', (req, res, next) => {
server.post(
'/users',
tools.asyncifyJson(async (req, res, next) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
@ -476,6 +489,9 @@ module.exports = (db, server, userHandler) => {
return next();
}
// permissions check
req.validate(roles.can(req.role).createAny('users'));
let targets = result.value.targets;
if (targets) {
@ -557,10 +573,20 @@ module.exports = (db, server, userHandler) => {
return next();
}
checkPubKey(result.value.pubKey)
.then(() => {
userHandler.create(result.value, (err, id) => {
if (err) {
try {
await checkPubKey(result.value.pubKey);
} catch (err) {
res.json({
error: 'PGP key validation failed. ' + err.message,
code: 'InputValidationError'
});
return next();
}
let id;
try {
id = await createUser(result.value);
} catch (err) {
res.json({
error: err.message,
code: err.code,
@ -575,16 +601,8 @@ module.exports = (db, server, userHandler) => {
});
return next();
});
})
.catch(err => {
res.json({
error: 'PGP key validation failed. ' + err.message,
code: 'InputValidationError'
});
return next();
});
});
);
/**
* @api {get} /users/resolve/:username Resolve ID for an username
@ -619,7 +637,9 @@ module.exports = (db, server, userHandler) => {
* "error": "This user does not exist"
* }
*/
server.get('/users/resolve/:username', (req, res, next) => {
server.get(
'/users/resolve/:username',
tools.asyncifyJson(async (req, res, next) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
@ -644,9 +664,14 @@ module.exports = (db, server, userHandler) => {
return next();
}
// permissions check
req.validate(roles.can(req.role).readAny('users'));
let username = result.value.username;
db.users.collection('users').findOne(
let userData;
try {
userData = await db.users.collection('users').findOne(
{
unameview: username.replace(/\./g, '')
},
@ -654,15 +679,16 @@ module.exports = (db, server, userHandler) => {
projection: {
_id: true
}
},
(err, userData) => {
if (err) {
}
);
} catch (err) {
res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
});
return next();
}
if (!userData) {
res.json({
error: 'This user does not exist',
@ -670,15 +696,15 @@ module.exports = (db, server, userHandler) => {
});
return next();
}
res.json({
success: true,
id: userData._id
});
return next();
}
})
);
});
/**
* @api {get} /users/:id Request User information
@ -793,7 +819,9 @@ module.exports = (db, server, userHandler) => {
* "error": "This user does not exist"
* }
*/
server.get('/users/:user', (req, res, next) => {
server.get(
'/users/:user',
tools.asyncifyJson(async (req, res, next) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
@ -817,20 +845,29 @@ module.exports = (db, server, userHandler) => {
return next();
}
// permissions check
if (req.user && req.user === result.value.user) {
req.validate(roles.can(req.role).readOwn('users'));
} else {
req.validate(roles.can(req.role).readAny('users'));
}
let user = new ObjectID(result.value.user);
db.users.collection('users').findOne(
{
let userData;
try {
userData = await db.users.collection('users').findOne({
_id: user
},
(err, userData) => {
if (err) {
});
} catch (err) {
res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
});
return next();
}
if (!userData) {
res.json({
error: 'This user does not exist',
@ -839,7 +876,9 @@ module.exports = (db, server, userHandler) => {
return next();
}
db.redis
let response;
try {
response = await db.redis
.multi()
// sending counters are stored in Redis
@ -867,8 +906,8 @@ module.exports = (db, server, userHandler) => {
.get('pdw:' + userData._id.toString())
.ttl('pdw:' + userData._id.toString())
.exec((err, result) => {
if (err) {
.exec();
} catch (err) {
// ignore
errors.notify(err, { userId: user });
}
@ -876,28 +915,31 @@ module.exports = (db, server, userHandler) => {
let recipients = Number(userData.recipients) || config.maxRecipients || consts.MAX_RECIPIENTS;
let forwards = Number(userData.forwards) || config.maxForwards || consts.MAX_FORWARDS;
let recipientsSent = Number(result && result[0] && result[0][1]) || 0;
let recipientsTtl = Number(result && result[1] && result[1][1]) || 0;
let recipientsSent = Number(response && response[0] && response[0][1]) || 0;
let recipientsTtl = Number(response && response[1] && response[1][1]) || 0;
let forwardsSent = Number(result && result[2] && result[2][1]) || 0;
let forwardsTtl = Number(result && result[3] && result[3][1]) || 0;
let forwardsSent = Number(response && response[2] && response[2][1]) || 0;
let forwardsTtl = Number(response && response[3] && response[3][1]) || 0;
let received = Number(result && result[4] && result[4][1]) || 0;
let receivedTtl = Number(result && result[5] && result[5][1]) || 0;
let received = Number(response && response[4] && response[4][1]) || 0;
let receivedTtl = Number(response && response[5] && response[5][1]) || 0;
let imapUpload = Number(result && result[6] && result[6][1]) || 0;
let imapUploadTtl = Number(result && result[7] && result[7][1]) || 0;
let imapUpload = Number(response && response[6] && response[6][1]) || 0;
let imapUploadTtl = Number(response && response[7] && response[7][1]) || 0;
let imapDownload = Number(result && result[8] && result[8][1]) || 0;
let imapDownloadTtl = Number(result && result[9] && result[9][1]) || 0;
let imapDownload = Number(response && response[8] && response[8][1]) || 0;
let imapDownloadTtl = Number(response && response[9] && response[9][1]) || 0;
let pop3Download = Number(result && result[10] && result[10][1]) || 0;
let pop3DownloadTtl = Number(result && result[11] && result[11][1]) || 0;
let pop3Download = Number(response && response[10] && response[10][1]) || 0;
let pop3DownloadTtl = Number(response && response[11] && response[11][1]) || 0;
getKeyInfo(userData.pubKey).then(keyInfo => {
if (err) {
let keyInfo;
try {
keyInfo = await getKeyInfo(userData.pubKey);
} catch (err) {
errors.notify(err, { userId: user, source: 'pgp' });
}
res.json({
success: true,
id: user,
@ -970,11 +1012,8 @@ module.exports = (db, server, userHandler) => {
});
return next();
});
});
}
})
);
});
/**
* @api {put} /users/:id Update User information
@ -1033,7 +1072,9 @@ module.exports = (db, server, userHandler) => {
* "error": "This user does not exist"
* }
*/
server.put('/users/:user', (req, res, next) => {
server.put(
'/users/:user',
tools.asyncifyJson(async (req, res, next) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
@ -1130,6 +1171,13 @@ module.exports = (db, server, userHandler) => {
return next();
}
// permissions check
if (req.user && req.user === result.value.user) {
req.validate(roles.can(req.role).updateOwn('users'));
} else {
req.validate(roles.can(req.role).updateAny('users'));
}
let user = new ObjectID(result.value.user);
let targets = result.value.targets;
@ -1192,30 +1240,33 @@ module.exports = (db, server, userHandler) => {
result.value.tagsview = tags.map(tag => tag.toLowerCase());
}
checkPubKey(result.value.pubKey)
.then(() => {
userHandler.update(user, result.value, (err, success) => {
if (err) {
try {
await checkPubKey(result.value.pubKey);
} catch (err) {
res.json({
error: 'PGP key validation failed. ' + err.message,
code: 'InputValidationError'
});
return next();
}
let success;
try {
success = await updateUser(user, result.value);
} catch (err) {
res.json({
error: err.message,
code: err.code
});
return next();
}
res.json({
success
});
return next();
});
})
.catch(err => {
res.json({
error: 'PGP key validation failed. ' + err.message,
code: 'InputValidationError'
});
return next();
});
});
);
/**
* @api {put} /users/:id/logout Log out User
@ -1254,7 +1305,9 @@ module.exports = (db, server, userHandler) => {
* "error": "This user does not exist"
* }
*/
server.put('/users/:user/logout', (req, res, next) => {
server.put(
'/users/:user/logout',
tools.asyncifyJson(async (req, res, next) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
@ -1286,20 +1339,30 @@ module.exports = (db, server, userHandler) => {
return next();
}
userHandler.logout(result.value.user, result.value.reason || 'Logout requested from API', (err, success) => {
if (err) {
// permissions check
if (req.user && req.user === result.value.user) {
req.validate(roles.can(req.role).readOwn('users'));
} else {
req.validate(roles.can(req.role).readAny('users'));
}
let success;
try {
success = await logoutUser(result.value.user, result.value.reason || 'Logout requested from API');
} catch (err) {
res.json({
error: err.message,
code: err.code
});
return next();
}
res.json({
success
});
return next();
});
});
})
);
/**
* @api {post} /users/:id/quota/reset Recalculate User quota
@ -1339,7 +1402,9 @@ module.exports = (db, server, userHandler) => {
* "error": "This user does not exist"
* }
*/
server.post('/users/:user/quota/reset', (req, res, next) => {
server.post(
'/users/:user/quota/reset',
tools.asyncifyJson(async (req, res, next) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
@ -1368,9 +1433,18 @@ module.exports = (db, server, userHandler) => {
return next();
}
// permissions check
if (req.user && req.user === result.value.user) {
req.validate(roles.can(req.role).updateOwn('users'));
} else {
req.validate(roles.can(req.role).updateAny('users'));
}
let user = new ObjectID(result.value.user);
db.users.collection('users').findOne(
let userData;
try {
userData = await db.users.collection('users').findOne(
{
_id: user
},
@ -1378,9 +1452,9 @@ module.exports = (db, server, userHandler) => {
projection: {
storageUsed: true
}
},
(err, userData) => {
if (err) {
}
);
} catch (err) {
res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
@ -1396,9 +1470,11 @@ module.exports = (db, server, userHandler) => {
return next();
}
let storageData;
try {
// calculate mailbox size by aggregating the size's of all messages
// NB! Scattered query
db.database
storageData = db.database
.collection('messages')
.aggregate(
[
@ -1424,8 +1500,8 @@ module.exports = (db, server, userHandler) => {
}
}
)
.toArray((err, result) => {
if (err) {
.toArray();
} catch (err) {
res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
@ -1433,10 +1509,12 @@ module.exports = (db, server, userHandler) => {
return next();
}
let storageUsed = (result && result[0] && result[0].storageUsed) || 0;
let storageUsed = (storageData && storageData[0] && storageData[0].storageUsed) || 0;
let updateResponse;
try {
// update quota counter
db.users.collection('users').findOneAndUpdate(
updateResponse = await db.users.collection('users').findOneAndUpdate(
{
_id: userData._id
},
@ -1446,10 +1524,13 @@ module.exports = (db, server, userHandler) => {
}
},
{
returnOriginal: false
},
(err, result) => {
if (err) {
returnOriginal: false,
projection: {
storageUsed: true
}
}
);
} catch (err) {
res.json({
error: 'MongoDB Error: ' + err.message,
code: 'InternalDatabaseError'
@ -1457,7 +1538,7 @@ module.exports = (db, server, userHandler) => {
return next();
}
if (!result || !result.value) {
if (!updateResponse || !updateResponse.value) {
res.json({
error: 'This user does not exist',
code: 'UserNotFound'
@ -1467,15 +1548,11 @@ module.exports = (db, server, userHandler) => {
res.json({
success: true,
storageUsed: Number(result.value.storageUsed) || 0
storageUsed: Number(updateResponse.value.storageUsed) || 0
});
return next();
}
})
);
});
}
);
});
/**
* @api {post} /users/:id/password/reset Reset password for an User
@ -1520,7 +1597,9 @@ module.exports = (db, server, userHandler) => {
* "error": "This user does not exist"
* }
*/
server.post('/users/:user/password/reset', (req, res, next) => {
server.post(
'/users/:user/password/reset',
tools.asyncifyJson(async (req, res, next) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
@ -1552,24 +1631,30 @@ module.exports = (db, server, userHandler) => {
return next();
}
// permissions check
req.validate(roles.can(req.role).updateAny('users'));
let user = new ObjectID(result.value.user);
userHandler.reset(user, result.value, (err, password) => {
if (err) {
let password;
try {
password = await resetUser(user, result.value);
} catch (err) {
res.json({
error: err.message,
code: err.code
});
return next();
}
res.json({
success: true,
password,
validAfter: result.value || new Date()
validAfter: (result.value && result.value.validAfter) || new Date()
});
return next();
});
});
})
);
/**
* @api {delete} /users/:id Delete an User
@ -1604,7 +1689,9 @@ module.exports = (db, server, userHandler) => {
* "error": "This user does not exist"
* }
*/
server.del('/users/:user', (req, res, next) => {
server.del(
'/users/:user',
tools.asyncifyJson(async (req, res, next) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
@ -1641,9 +1728,15 @@ module.exports = (db, server, userHandler) => {
return next();
}
// permissions check
req.validate(roles.can(req.role).deleteAny('users'));
let user = new ObjectID(result.value.user);
userHandler.delete(user, {}, (err, status) => {
if (err) {
let status;
try {
status = await deleteUser(user, {});
} catch (err) {
res.json({
error: err.message,
code: err.code
@ -1654,8 +1747,8 @@ module.exports = (db, server, userHandler) => {
success: status
});
return next();
});
});
})
);
};
async function getKeyInfo(pubKey) {