UNPKG

webgme-engine

Version:

WebGME server and Client API without a GUI

1,390 lines (1,232 loc) 94.8 kB
/*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)