webgme-engine
Version:
WebGME server and Client API without a GUI
1,390 lines (1,232 loc) • 94.8 kB
JavaScript
/*globals requireJS*/
/*eslint-env node*/
/*eslint camelcase: 0*/
/**
* @module Server:API
* @author lattmann / https://github.com/lattmann
* @author pmeijer / https://github.com/pmeijer
* @author kecso / https://github.com/kecso
*/
'use strict';
var express = require('express'),
Q = require('q'),
path = require('path'),
fs = require('fs'),
webgme = require('../../../index'),
StorageUtil = webgme.requirejs('common/storage/util'),
webgmeUtils = require('../../utils'),
GUID = webgme.requirejs('common/util/guid'),
BlobClientClass = webgme.requirejs('blob/BlobClient'),
CONSTANTS = webgme.requirejs('common/Constants');
/**
* Mounts the API functions to a given express app.
*
* @param app Express application
* @param mountPath {string} mount point e.g. /api
* @param middlewareOpts
*/
function createAPI(app, mountPath, middlewareOpts) {
var router = express.Router(),
apiDocumentationMountPoint = '/developer/api',
logger = middlewareOpts.logger.fork('api'),
gmeAuth = middlewareOpts.gmeAuth,
metadataStorage = gmeAuth.metadataStorage,
authorizer = gmeAuth.authorizer,
safeStorage = middlewareOpts.safeStorage,
ensureAuthenticated = middlewareOpts.ensureAuthenticated,
gmeConfig = middlewareOpts.gmeConfig,
getUserId = middlewareOpts.getUserId,
STORAGE_CONSTANTS = CONSTANTS.STORAGE,
CORE_CONSTANTS = CONSTANTS.CORE,
versionedAPIPath = mountPath + '/v1',
latestAPIPath = mountPath,
registerEndPoint = typeof gmeConfig.authentication.allowUserRegistration === 'string' ?
require(gmeConfig.authentication.allowUserRegistration)(middlewareOpts) :
require('./defaultRegisterEndPoint')(middlewareOpts),
seedToBlobHash = {},
paths,
mailer = middlewareOpts.mailer,
mailerAvailable = mailer === null ? false : true;
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views'));
app.get(apiDocumentationMountPoint, function (req, res) {
res.sendFile(path.join(__dirname, '..', '..', '..', 'docs', 'REST', 'index.html'));
});
function getFullUrl(req, name) {
return req.protocol + '://' + req.headers.host + middlewareOpts.getMountedPath(req) + req.baseUrl + name;
}
function getNewJWToken(userId, callback) {
var deferred = Q.defer();
if (gmeConfig.authentication.enable === true) {
gmeAuth.generateJWTokenForAuthenticatedUser(userId)
.then(deferred.resolve)
.catch(deferred.reject);
} else {
deferred.resolve();
}
return deferred.promise.nodeify(callback);
}
function exportProject(req, res) {
var userId = getUserId(req),
projectId = StorageUtil.getProjectIdFromOwnerIdAndProjectName(req.params.ownerId,
req.params.projectName),
workerParameters = {
command: CONSTANTS.SERVER_WORKER_REQUESTS.EXPORT_PROJECT_TO_FILE,
projectId: projectId,
branchName: req.params.branchId || null,
commitHash: req.params.commitHash ? StorageUtil.getHashTaggedHash(req.params.commitHash) : null,
tagName: req.params.tagId || null,
withAssets: true
};
getNewJWToken(userId)
.then(function (token) {
workerParameters.webgmeToken = token;
return Q.ninvoke(middlewareOpts.workerManager, 'request', workerParameters);
})
.then(function (result) {
res.redirect(result.downloadUrl);
return;
})
.catch(function (err) {
logger.error('Cannot handle export request', err);
res.status(403).send('Cannot process request: ' + err);
});
}
function exportModel(req, res) {
var userId = getUserId(req),
projectId = StorageUtil.getProjectIdFromOwnerIdAndProjectName(req.params.ownerId,
req.params.projectName),
workerParameters = {
command: CONSTANTS.SERVER_WORKER_REQUESTS.EXPORT_SELECTION_TO_FILE,
projectId: projectId,
branchName: req.params.branchId || null,
commitHash: req.params.commitHash ? StorageUtil.getHashTaggedHash(req.params.commitHash) : null,
tagName: req.params.tagId || null,
paths: ['/' + req.params[0]],
withAssets: true
};
getNewJWToken(userId)
.then(function (token) {
workerParameters.webgmeToken = token;
return Q.ninvoke(middlewareOpts.workerManager, 'request', workerParameters);
})
.then(function (result) {
res.redirect(result.downloadUrl);
return;
})
.catch(function (err) {
logger.error('Cannot handle export request', err);
res.status(403).send('Cannot process request: ' + err);
});
}
// ensure authenticated can be used only after this rule
router.use('*', function (req, res, next) {
// TODO: set all headers, check rate limit, etc.
res.setHeader('X-WebGME-Media-Type', 'webgme.v1');
next();
});
router.post('/register', registerEndPoint);
if (gmeConfig.authentication.enable && gmeConfig.authentication.allowPasswordReset) {
router.post('/reset', function (req, res) {
if (gmeConfig.mailer.sendPasswordReset && mailerAvailable) {
mailer.passwordReset({userId: req.body.userId, hostUrlPrefix: req.headers.host})
.then(info => {
logger.info('reset email sent: ', JSON.stringify(info, null, 2));
res.sendStatus(200);
})
.catch(err => {
logger.error(err);
res.sendStatus(404);
});
} else {
gmeAuth.resetPassword(req.body.userId)
.then(function (resetToken) {
res.status(200);
res.json({
resetHash: resetToken
});
})
.catch(function (err) {
logger.error('cannot process reset request:', err);
res.sendStatus(404);
});
}
});
router.get('/reset', function (req, res) {
gmeAuth.isValidReset(req.query.userId, req.query.resetHash)
.then(() => {
res.sendStatus(200);
})
.catch((err)=> {
logger.error('invalid reset password request for user: ', req.query.userId, ' : ', err);
res.sendStatus(404);
});
});
router.patch('/reset', function (req, res) {
gmeAuth.changePassword(req.body.userId, req.body.resetHash, req.body.newPassword)
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
logger.error('failed to change password: ', err);
res.sendStatus(404);
});
});
}
// modifications are allowed only if the user is authenticated
// all get rules by default do NOT require authentication, if the get rule has to be protected add inline
// the ensureAuthenticated function middleware
router.post('*', ensureAuthenticated);
router.put('*', ensureAuthenticated);
router.patch('*', ensureAuthenticated);
router.delete('*', ensureAuthenticated);
router.get('/', function (req, res/*, next*/) {
if (gmeConfig.api.useEnhancedStarterPage) {
let options = [
{title: 'user info', link: getFullUrl(req, '/user')},
{title: 'oraginzations info', link: getFullUrl(req, '/orgs')},
{
title: 'REST API documentation',
link: req.protocol + '://' + req.headers.host + apiDocumentationMountPoint
},
{
title: 'Source code documentation',
link: req.protocol + '://' + req.headers.host + '/docs/source/index.html'
}
];
res.render('index', {options: options});
} else {
res.json({
current_user_url: getFullUrl(req, '/user'),
organization_url: getFullUrl(req, '/orgs/{org}'),
project_url: getFullUrl(req, '/projects/{owner}/{project}'),
user_url: getFullUrl(req, '/users/{user}'),
api_documentation_url: req.protocol + '://' + req.headers.host + apiDocumentationMountPoint,
source_code_documentation_url: req.protocol + '://' + req.headers.host + '/docs/source/index.html'
});
}
});
function putUser(receivedData, req, res, next) {
var userId = getUserId(req);
gmeAuth.getUser(userId)
.then(function (data) {
if (!data.siteAdmin) {
res.status(403);
throw new Error('site admin role is required for this operation');
}
gmeAuth.addUser(receivedData.userId,
receivedData.email,
receivedData.password,
receivedData.canCreate === 'true' || receivedData.canCreate === true,
{overwrite: receivedData.overwrite})
.then(function (newData) {
res.json(newData);
})
.catch(function (err) {
res.status(400);
next(err);
});
})
.catch(next);
}
function ensureSameUserOrSiteAdmin(req, res) {
var userId = getUserId(req);
return gmeAuth.getUser(userId)
.then(function (userData) {
if (userData.siteAdmin || userId === req.params.username) {
return userData;
} else {
res.status(403);
throw new Error('site admin role is required for this operation');
}
});
}
/**
* @param req
* @param res
* @param callback
* @returns {*}
*/
function getUserEntry(req, res, callback) {
var deferred = Q.defer(),
userId = getUserId(req),
query = {disabled: undefined};
gmeAuth.getUser(userId, query)
.then(function (userData) {
if (userData.disabled === true) {
res.clearCookie(gmeConfig.authentication.jwt.cookieId);
res.status(401);
deferred.reject(new Error('user has been disabled [' + userId + ']'));
} else {
deferred.resolve(userData);
}
})
.catch(deferred.reject);
return deferred.promise.nodeify(callback);
}
function filterUsersOrOrgs(userData, projects, usersOrOrgs) {
var result = [],
readableProjects = {},
filteredData,
i;
function getFilteredData(data) {
var filteredProjects = {};
data = usersOrOrgs[i];
if (userData._id === data._id) {
return userData;
} else if (userData.siteAdmin === true) {
return data;
} else {
if (data.type === 'Organization') {
if (userData._id === gmeConfig.authentication.guestAccount &&
data.users.indexOf(userData._id) === -1) {
// The guest can only view organization where he/she is a member.
return;
}
} else {
// Clear out user-data.
data.settings = {};
data.data = {};
data.email = '';
data.siteAdmin = false;
data.canCreate = false;
}
// We only return project info for projects the requesting user has access to.
Object.keys(data.projects).forEach(function (projectId) {
if (readableProjects[projectId]) {
filteredProjects[projectId] = data.projects[projectId];
}
});
data.projects = filteredProjects;
return data;
}
}
projects.forEach(function (pData) {
readableProjects[pData._id] = true;
});
for (i = 0; i < usersOrOrgs.length; i += 1) {
filteredData = getFilteredData(usersOrOrgs[i]);
if (filteredData) {
result.push(filteredData);
}
}
return result;
}
// AUTHENTICATED
router.get('/user', ensureAuthenticated, function (req, res, next) {
getUserEntry(req, res)
.then(function (data) {
res.json(data);
})
.catch(next);
});
// Example: curl -i -H "Content-Type: application/json" -X PATCH
// -d "{\"email\":\"asdf@alkfm.com\",\"canCreate\":false}" http://demo:demo@127.0.0.1:8888/api/v1/user
router.patch('/user', function (req, res, next) {
var userId = getUserId(req);
gmeAuth.getUser(userId)
.then(function (userData) {
var receivedData = req.body;
if (userData.siteAdmin !== true &&
(Object.hasOwn(receivedData, 'siteAdmin') || Object.hasOwn(receivedData, 'canCreate'))) {
res.status(403);
throw new Error('setting siteAdmin and/or canCreate property requires site admin role');
}
return gmeAuth.updateUser(userId, receivedData);
})
.then(function (newData) {
res.json(newData);
})
.catch(next);
});
router.delete('/user', function (req, res, next) {
var userId = getUserId(req);
gmeAuth.deleteUser(userId, false)
.then(function () {
res.sendStatus(204);
})
.catch(next);
});
router.get(/\/user\/data\/?(.*)/, ensureAuthenticated, function (req, res, next) {
const userId = getUserId(req);
const keys = getUserDataKeys(req);
gmeAuth.getUserDataField(userId, keys)
.then(function (data) {
res.json(data);
})
.catch(next);
});
router.put(/\/user\/data\/?(.*)/, function (req, res, next) {
const userId = getUserId(req);
const keys = getUserDataKeys(req);
const {encrypt = false} = req.query;
const options = {
encrypt,
overwrite: true
};
gmeAuth.setUserDataField(userId, keys, req.body, options)
.then(function (data) {
res.json(data);
})
.catch(next);
});
router.patch(/\/user\/data\/?(.*)/, function (req, res, next) {
const userId = getUserId(req);
const keys = getUserDataKeys(req);
const {encrypt = false} = req.query;
const options = {
encrypt,
overwrite: false
};
gmeAuth.setUserDataField(userId, keys, req.body, options)
.then(function (data) {
res.json(data);
})
.catch(next);
});
router.delete(/\/user\/data\/?(.*)/, function (req, res, next) {
const userId = getUserId(req);
const keys = getUserDataKeys(req);
gmeAuth.deleteUserDataField(userId, keys)
.then(function (/*data*/) {
res.sendStatus(204);
})
.catch(next);
});
function getUserDataKeys(req) {
const encodedKeys = req.params[0].split('/');
encodedKeys.map(decodeURIComponent);
let i = encodedKeys.length;
if (i > 0) {
while (i--) {
if (encodedKeys[i] === '') {
encodedKeys.splice(i, 1);
}
}
}
return encodedKeys;
}
router.get('/user/token', ensureAuthenticated, function (req, res, next) {
var userId = getUserId(req);
if (gmeConfig.authentication.enable === false) {
res.status(404);
res.json({
message: 'Authentication is turned off',
});
return;
}
if (req.userData.token && req.userData.newToken === true) {
res.status(200);
res.json({
webgmeToken: req.userData.token
});
} else {
getNewJWToken(userId)
.then(function (token) {
res.status(200);
res.json({webgmeToken: token});
})
.catch(function (err) {
next(err);
});
}
});
router.get('/componentSettings', ensureAuthenticated, function (req, res, next) {
webgmeUtils.getComponentsJson(logger)
.then(function (componentsJson) {
res.json(componentsJson);
})
.catch(next);
});
router.get('/componentSettings/:componentId', ensureAuthenticated, function (req, res, next) {
webgmeUtils.getComponentsJson(logger)
.then(function (componentsJson) {
res.json(componentsJson[req.params.componentId] || {});
})
.catch(next);
});
router.get('/user/settings', ensureAuthenticated, function (req, res, next) {
getUserEntry(req, res)
.then(function (userData) {
res.json(userData.settings || {});
})
.catch(next);
});
router.put('/user/settings', function (req, res, next) {
getUserEntry(req, res)
.then(function (userData) {
return gmeAuth.updateUserSettings(userData._id, req.body, true);
})
.then(function (settings) {
res.json(settings);
})
.catch(next);
});
router.patch('/user/settings', function (req, res, next) {
getUserEntry(req, res)
.then(function (userData) {
return gmeAuth.updateUserSettings(userData._id, req.body);
})
.then(function (settings) {
res.json(settings);
})
.catch(next);
});
router.delete('/user/settings', function (req, res, next) {
getUserEntry(req, res)
.then(function (userData) {
return gmeAuth.updateUserSettings(userData._id, {}, true);
})
.then(function (/*settings*/) {
res.sendStatus(204);
})
.catch(next);
});
router.get('/user/settings/:componentId', ensureAuthenticated, function (req, res, next) {
getUserEntry(req, res)
.then(function (userData) {
res.json(userData.settings[req.params.componentId] || {});
})
.catch(next);
});
router.put('/user/settings/:componentId', function (req, res, next) {
getUserEntry(req, res)
.then(function (userData) {
return gmeAuth.updateUserComponentSettings(userData._id, req.params.componentId, req.body, true);
})
.then(function (settings) {
res.json(settings);
})
.catch(next);
});
router.patch('/user/settings/:componentId', function (req, res, next) {
getUserEntry(req, res)
.then(function (userData) {
return gmeAuth.updateUserComponentSettings(userData._id, req.params.componentId, req.body);
})
.then(function (settings) {
res.json(settings);
})
.catch(next);
});
router.delete('/user/settings/:componentId', function (req, res, next) {
getUserEntry(req, res)
.then(function (userData) {
return gmeAuth.updateUserComponentSettings(userData._id, req.params.componentId, {}, true);
})
.then(function (/*settings*/) {
res.sendStatus(204);
})
.catch(next);
});
router.get('/users', ensureAuthenticated, function (req, res, next) {
var userId = getUserId(req),
userData,
query,
projection;
if (req.query.displayName) {
gmeAuth.listUsers({displayName: {$type: 2}}, {displayName: 1})
.then(function (result) {
res.json(result);
})
.catch(next);
} else {
gmeAuth.getUser(userId)
.then(function (userData_) {
var doGetProjects = userId !== gmeConfig.authentication.guestAccount && !userData_.siteAdmin;
userData = userData_;
if (req.query.includeDisabled && userData.siteAdmin) {
query = {disabled: undefined};
}
if (userId === gmeConfig.authentication.guestAccount) {
query = {_id: userId};
} else if (!userData.siteAdmin) {
projection = {
data: 0,
settings: 0,
email: 0,
password: 0
};
}
return Q.all([
doGetProjects ? safeStorage.getProjects({username: userId}) : Q.resolve([]),
gmeAuth.listUsers(query, projection)
]);
})
.then(function (results) {
res.json(filterUsersOrOrgs(userData, results[0], results[1]));
})
.catch(next);
}
});
router.put('/users', function (req, res, next) {
//"userId: "newUser"
//"email": "user@example.com",
//"password": "pass",
//"canCreate": null,
putUser(req.body, req, res, next);
});
router.get('/users/:username', ensureAuthenticated, function (req, res, next) {
var userId = getUserId(req);
gmeAuth.getUser(userId)
.then(function (userData) {
if (userId === req.params.username) {
return Q.resolve(userData);
} else if (userId === gmeConfig.authentication.guestAccount) {
res.status(404);
return Q.reject(new Error('no such user'));
} else {
return Q.all([
userData.siteAdmin ? [] : safeStorage.getProjects({username: userId}),
gmeAuth.getUser(req.params.username)
])
.then(function (results) {
return filterUsersOrOrgs(userData, results[0], [results[1]])[0];
});
}
})
.then(function (data) {
res.json(data);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
router.put('/users/:username', function (req, res, next) {
var receivedData = {
userId: req.params.username,
email: req.body.email,
password: req.body.password,
canCreate: req.body.canCreate || false,
data: req.body.data || {},
overwrite: req.body.overwrite
};
putUser(receivedData, req, res, next);
});
router.patch('/users/:username', function (req, res, next) {
// body params
//"email": "user@example.com",
//"password": "pass",
//"canCreate": null,
//"siteAdmin": false,
//"disabled": false, // Only applicable if false -> will re-enable user
//"data": {}
ensureSameUserOrSiteAdmin(req, res)
.then(function (userData) {
if (userData.siteAdmin !== true &&
(Object.hasOwn(req.body, 'siteAdmin') || Object.hasOwn(req.body, 'canCreate'))) {
res.status(403);
throw new Error('setting siteAdmin and/or canCreate property requires site admin role');
}
if (Object.hasOwn(req.body, 'disabled') && req.body.disabled === false) {
if (userData.siteAdmin === true) {
return gmeAuth.reEnableUser(req.params.username);
} else {
res.status(403);
throw new Error('re-enabling users requires site admin role');
}
} else {
return gmeAuth.updateUser(req.params.username, req.body);
}
})
.then(function (userData) {
res.json(userData);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
//TODO: why is this 400 and not 404?
res.status(400);
}
next(err);
});
});
router.delete('/users/:username', function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function (userData) {
var force = req.query.force && userData.siteAdmin === true;
return gmeAuth.deleteUser(req.params.username, force);
})
.then(function () {
res.sendStatus(204);
})
.catch(function (err) {
if (err.message.indexOf('no such user') > -1) {
res.status(404);
}
next(err);
});
});
router.get('/users/:username/data', ensureAuthenticated, function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.getUser(req.params.username);
})
.then(function (userData) {
res.json(userData.data);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
router.put('/users/:username/data', function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.updateUserDataField(req.params.username, req.body, true);
})
.then(function (data) {
res.json(data);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
router.patch('/users/:username/data', function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.updateUserDataField(req.params.username, req.body);
})
.then(function (data) {
res.json(data);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
router.delete('/users/:username/data', function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.updateUserDataField(req.params.username, {}, true);
})
.then(function (/*userData*/) {
res.sendStatus(204);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
router.get('/users/:username/settings', ensureAuthenticated, function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.getUser(req.params.username);
})
.then(function (userData) {
res.json(userData.settings);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
router.put('/users/:username/settings', function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.updateUserSettings(req.params.username, req.body, true);
})
.then(function (settings) {
res.json(settings);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
router.patch('/users/:username/settings', function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.updateUserSettings(req.params.username, req.body);
})
.then(function (settings) {
res.json(settings);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
router.delete('/users/:username/settings', function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.updateUserSettings(req.params.username, {}, true);
})
.then(function (/*settings*/) {
res.sendStatus(204);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
router.get('/users/:username/settings/:componentId', ensureAuthenticated, function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.getUser(req.params.username);
})
.then(function (userData) {
res.json(userData.settings[req.params.componentId] || {});
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
router.put('/users/:username/settings/:componentId', function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.updateUserComponentSettings(req.params.username, req.params.componentId, req.body, true);
})
.then(function (settings) {
res.json(settings);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
router.patch('/users/:username/settings/:componentId', function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.updateUserComponentSettings(req.params.username, req.params.componentId, req.body);
})
.then(function (settings) {
res.json(settings);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
router.delete('/users/:username/settings/:componentId', function (req, res, next) {
ensureSameUserOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.updateUserComponentSettings(req.params.username, req.params.componentId, {}, true);
})
.then(function (/*settings*/) {
res.sendStatus(204);
})
.catch(function (err) {
if (err.message.indexOf('no such user [' + req.params.username) === 0) {
res.status(404);
}
next(err);
});
});
//ORGANIZATIONS
function ensureOrgOrSiteAdmin(req, res) {
var userId = getUserId(req),
userData;
return gmeAuth.getUser(userId)
.then(function (data) {
userData = data;
return gmeAuth.getAdminsInOrganization(req.params.orgId);
})
.then(function (admins) {
if (!userData.siteAdmin && admins.indexOf(userId) === -1) {
res.status(403);
throw new Error('site admin role or organization admin is required for this operation');
}
return userData;
});
}
router.get('/orgs', ensureAuthenticated, function (req, res, next) {
var userId = getUserId(req),
userData,
query;
gmeAuth.getUser(userId)
.then(function (userData_) {
userData = userData_;
if (req.query.includeDisabled && userData.siteAdmin) {
query = {disabled: undefined};
}
return Q.all([
safeStorage.getProjects({username: userId}),
gmeAuth.listOrganizations(query)
]);
})
.then(function (results) {
res.json(filterUsersOrOrgs(userData, results[0], results[1]));
})
.catch(next);
});
router.put('/orgs/:orgId', function (req, res, next) {
var userId = getUserId(req);
gmeAuth.getUser(userId)
.then(function (data) {
if (!(data.siteAdmin || data.canCreate)) {
res.status(403);
throw new Error('site admin role or can create is required for this operation');
}
return gmeAuth.addOrganization(req.params.orgId, req.body.info);
})
.then(function () {
return gmeAuth.setAdminForUserInOrganization(userId, req.params.orgId, true);
})
.then(function () {
return gmeAuth.addUserToOrganization(userId, req.params.orgId);
})
.then(function () {
return gmeAuth.getOrganization(req.params.orgId);
})
.then(function (orgData) {
res.json(orgData);
})
.catch(function (err) {
if (err.message.indexOf('user or org already exists') > -1) {
res.status(400);
}
next(err);
});
});
router.get('/orgs/:orgId', ensureAuthenticated, function (req, res, next) {
var userId = getUserId(req);
gmeAuth.getUser(userId)
.then(function (userData) {
return Q.all([
userData.siteAdmin ? [] : safeStorage.getProjects({username: userId}),
gmeAuth.getOrganization(req.params.orgId)
])
.then(function (results) {
return filterUsersOrOrgs(userData, results[0], [results[1]])[0];
});
})
.then(function (orgData) {
if (!orgData) {
// This is the case where the guest is not a member.
throw new Error('no such organization [');
}
res.json(orgData);
})
.catch(function (err) {
if (err.message.indexOf('no such organization [') > -1) {
res.status(404);
}
next(err);
});
});
router.patch('/orgs/:orgId', function (req, res, next) {
// body params
//"info": {}
//"disabled": false, // Only applicable if false -> will re-enable org
function updateOrg() {
var userId;
if (Object.hasOwn(req.body, 'disabled') && req.body.disabled === false) {
userId = getUserId(req);
return gmeAuth.getUser(userId)
.then(function (userData) {
if (userData.siteAdmin === true) {
return gmeAuth.reEnableOrganization(req.params.orgId);
} else {
res.status(403);
throw new Error('re-enabling organizations requires site admin role');
}
});
} else {
return ensureOrgOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.updateOrganizationInfo(req.params.orgId, req.body.info);
});
}
}
updateOrg()
.then(function (orgData) {
res.json(orgData);
})
.catch(function (err) {
if (err.message.indexOf('no such organization [' + req.params.orgId) === 0 ||
err.message.indexOf('info is not an object') > -1) {
res.status(400);
}
next(err);
});
});
router.delete('/orgs/:orgId', function (req, res, next) {
function deleteOrg() {
var userId;
if (req.query.force) {
userId = getUserId(req);
return gmeAuth.getUser(userId)
.then(function (userData) {
if (userData.siteAdmin === true) {
return gmeAuth.removeOrganizationByOrgId(req.params.orgId, true);
} else {
res.status(403);
throw new Error('force deletion requires site admin role');
}
});
} else {
return ensureOrgOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.removeOrganizationByOrgId(req.params.orgId, req.body.info);
});
}
}
deleteOrg(req, res)
.then(function () {
res.sendStatus(204);
})
.catch(function (err) {
if (err.message.indexOf('no such organization [') > -1) {
res.status(404);
}
next(err);
});
});
router.put('/orgs/:orgId/users/:username', function (req, res, next) {
ensureOrgOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.addUserToOrganization(req.params.username, req.params.orgId);
})
.then(function () {
res.sendStatus(204);
})
.catch(function (err) {
if (err.message.indexOf('no such organization [') > -1 ||
err.message.indexOf('no such user [') > -1) {
res.status(404);
}
next(err);
});
});
router.delete('/orgs/:orgId/users/:username', function (req, res, next) {
ensureOrgOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.removeUserFromOrganization(req.params.username, req.params.orgId);
})
.then(function () {
res.sendStatus(204);
})
.catch(function (err) {
if (err.message.indexOf('no such organization [') > -1) {
res.status(404);
}
next(err);
});
});
router.put('/orgs/:orgId/admins/:username', function (req, res, next) {
ensureOrgOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.setAdminForUserInOrganization(req.params.username, req.params.orgId, true);
})
.then(function () {
res.sendStatus(204);
})
.catch(function (err) {
if (err.message.indexOf('no such organization [') > -1 ||
err.message.indexOf('no such user [') > -1) {
res.status(404);
}
next(err);
});
});
router.delete('/orgs/:orgId/admins/:username', function (req, res, next) {
ensureOrgOrSiteAdmin(req, res)
.then(function () {
return gmeAuth.setAdminForUserInOrganization(req.params.username, req.params.orgId, false);
})
.then(function () {
res.sendStatus(204);
})
.catch(function (err) {
if (err.message.indexOf('no such organization [') > -1 ||
err.message.indexOf('no such user [') > -1) {
res.status(404);
}
next(err);
});
});
// PROJECTS
function loadNodePathByCommitHash(userId, projectId, commitHash, path) {
var getCommitParams = {
username: userId,
projectId: projectId,
number: 1,
before: commitHash
};
return safeStorage.getCommits(getCommitParams)
.then(function (commits) {
var loadPathsParams = {
projectId: projectId,
username: userId,
pathsInfo: [
{
parentHash: commits[0].root,
path: path
}
],
excludeParents: true
};
return safeStorage.loadPaths(loadPathsParams);
})
.then(function (dataObjects) {
var hashes = Object.keys(dataObjects),
dataObj,
newOvr,
relid,
hash,
ovrPath;
if (hashes.length === 1) {
return dataObjects[hashes[0]];
} else if (hashes.length === 0) {
throw new Error('Path does not exist ' + path);
} else {
// There are multiple hashes -> the overlay is shared so build up the complete object
for (hash in dataObjects) {
if (dataObjects[hash].type !== STORAGE_CONSTANTS.OVERLAY_SHARD_TYPE) {
dataObj = dataObjects[hash];
break;
}
}
if (!dataObj) {
throw new Error('loadPaths did not return with a dataObj hash, only shards');
} else if (!dataObj.ovr || Object.keys(dataObj.ovr) === 0) {
throw new Error('loadPaths returned with multiple objects but missing or empty ovr..');
}
newOvr = {};
for (relid in dataObj.ovr) {
if (relid !== CORE_CONSTANTS.OVERLAY_SHARD_INDICATOR) {
hash = dataObj.ovr[relid];
if (dataObjects[hash] && dataObjects[hash].type === STORAGE_CONSTANTS.OVERLAY_SHARD_TYPE) {
for (ovrPath in dataObjects[hash].items) {
newOvr[ovrPath] = dataObjects[hash].items[ovrPath];
}
} else {
logger.error('Did not find shard for overlay', hash);
}
}
}
dataObj.ovr = newOvr;
return dataObj;
}
});
}
function canUserAuthorizeProject(req) {
var userId = getUserId(req);
return gmeAuth.getUser(userId)
.then(function (userData) {
// Make sure user is authorized (owner, admin in owner Org or siteAdmin).
if (userId === req.params.ownerId || userData.siteAdmin === true) {
return true;
} else {
return gmeAuth.getOrganization(req.params.ownerId)
.then(function (orgData) {
if (orgData.admins.indexOf(userId) > -1) {
return true;
}
return false;
})
.catch(function (err) {
logger.debug(err);
return false;
});
}
});
}
router.get('/projects', ensureAuthenticated, function (req, res, next) {
var userId = getUserId(req);
safeStorage.getProjects({username: userId, info: true})
.then(function (result) {
res.json(result);
})
.catch(function (err) {
next(err);
});
});
router.get('/projects/:ownerId/:projectName', ensureAuthenticated, function (req, res, next) {
var userId = getUserId(req),
projectId = StorageUtil.getProjectIdFromOwnerIdAndProjectName(req.params.ownerId, req.params.projectName),
data = {
username: userId,
projectId: projectId
},
branches;
safeStorage.getBranches(data)
.then(function (branches_) {
branches = branches_;
return metadataStorage.getProject(projectId);
})
.then(function (projectData) {
projectData.branches = branches;
res.json(projectData);
})
.catch(function (err) {
next(err);
});
});
router.patch('/projects/:ownerId/:projectName', function (req, res, next) {
var userId = getUserId(req),
projectAuthParams = {
entityType: authorizer.ENTITY_TYPES.PROJECT
},
projectId = StorageUtil.getProjectIdFromOwnerIdAndProjectName(req.params.ownerId, req.params.projectName);
authorizer.getAccessRights(userId, projectId, projectAuthParams)
.then(function (projectAccess) {
if (projectAccess && projectAccess.write) {
return;
} else {
res.status(403);
throw new Error('Not authorized to modify project');
}
})
.then(function () {
return metadataStorage.updateProjectInfo(projectId, req.body);
})
.then(function (projectData) {
res.json(projectData);
})
.catch(function (err) {
next(err);
});
});
/**
* Creating project by seed
*
* @param {string} req.body.type - sets if the seed is coming from file (==='file') source or from some
* existing project(==='db').
* @param {string} req.body.seedName - the name or rather id of the seed
* db - projectId
* seed - name of the seed-file (no extension - matches json file)
* @param {string} [req.body.seedBranch='master'] - for 'db' optional branch name to seed from.
* @param {string} [req.body.seedCommit] - for 'db' optional commit-hash to seed from
* (if given seedBranch is not used).
* @param {string} [req.body.kind] - If not given:
* 1) type is seed - will use kind stored in seed else name of seed.
* 2) type is db - will use kind stored project info.
* @example {type:'file', seedName:'EmptyProject'}
* @example {type:'db', seedName:'guest+aFSMProject', seedBranch:'release', kind: 'FiniteStateMachine'}
*/
router.put('/projects/:ownerId/:projectName', function (req, res, next) {
var userId = getUserId(req),
command = req.body;
command.command = 'seedProject';
command.userId = userId;
command.ownerId = req.params.ownerId;
command.projectName = req.params.projectName;
getNewJWToken(userId)