UNPKG

webgme-engine

Version:

WebGME server and Client API without a GUI

1,178 lines (1,044 loc) 67.2 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: server/storage/safestorage.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: server/storage/safestorage.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>/*globals requireJS*/ /*eslint-env node*/ /** * This class forwards function calls to the storage and in addition: * - checks that input data is of correct format. * - checks that users are authorized to access/change projects. * - updates _users and _projects collections on delete/createProject. * * @module Server:SafeStorage * @author pmeijer / https://github.com/pmeijer */ 'use strict'; var Q = require('q'), REGEXP = requireJS('common/regexp'), Storage = require('./storage'), UserProject = require('./userproject'); function check(cond, deferred, msg) { var rejected = false; if (!cond) { deferred.reject(new Error('Invalid argument, ' + msg)); rejected = true; } return rejected; } /** * * @param database * @param logger * @param gmeConfig * @param gmeAuth * @constructor */ function SafeStorage(database, logger, gmeConfig, gmeAuth) { Storage.call(this, database, logger, gmeConfig); this.metadataStorage = gmeAuth.metadataStorage; this.authorizer = gmeAuth.authorizer; } // Inherit from Storage SafeStorage.prototype = Object.create(Storage.prototype); SafeStorage.prototype.constructor = SafeStorage; /** * Returns and array of dictionaries for each project the user has at least read access to. * If branches is set, the returned array will be filtered based on if the projects really do exist as * collections on their own. If branches is not set, there is no guarantee that the returned projects * really exist. * * Authorization level: read access for each returned project. * * @param {object} data - input parameters * @param {boolean} [data.info] - include the info field from the _projects collection. * @param {boolean} [data.rights] - include users' authorization information for each project. * @param {boolean} [data.branches] - include a dictionary with all branches and their hash. * @param {boolean} [data.tags] - include a dictionary with all tags and their hash. * @param {boolean} [data.hooks] - include the dictionary with all hooks. * @param {string} [data.projectId] - if given will return only single matching project. * @param {string} [data.username=gmeConfig.authentication.guestAccount] * @param {function} [callback] * @returns {Promise} //TODO: jsdocify this */ SafeStorage.prototype.getProjects = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT, }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.info === 'undefined' || typeof data.info === 'boolean', deferred, 'data.info is not a boolean.') || check(typeof data.rights === 'undefined' || typeof data.rights === 'boolean', deferred, 'data.rights is not a boolean.') || check(typeof data.branches === 'undefined' || typeof data.branches === 'boolean', deferred, 'data.branches is not a boolean.') || check(typeof data.hooks === 'undefined' || typeof data.hooks === 'boolean', deferred, 'data.hooks is not a boolean.') || check(typeof data.projectId === 'undefined' || REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { (data.projectId ? Q.all([this.metadataStorage.getProject(data.projectId)]) : this.metadataStorage.getProjects()) .then(function (allProjects) { function getAuthorizedProjects(projectData) { var projectDeferred = Q.defer(); self.authorizer.getAccessRights(data.username, projectData._id, projectAuthParams) .then(function (accessRights) { if (accessRights &amp;&amp; accessRights.read === true) { if (data.rights === true) { projectData.rights = accessRights; } if (!data.info) { delete projectData.info; } if (!data.hooks) { delete projectData.hooks; } projectDeferred.resolve(projectData); } else { projectDeferred.resolve(); } }) .catch(projectDeferred.reject); return projectDeferred.promise; } return Q.all(allProjects.map(getAuthorizedProjects)); }) .then(async function (projects) { const result = []; for (const project of projects) { try { if (!project) { continue; } if (data.branches === true) { project.branches = await Storage.prototype.getBranches.call(self, {projectId: project._id}); } if (data.tags === true) { project.tags = await Storage.prototype.getTags.call(self, {projectId: project._id}); } result.push(project); } catch (err) { if (err.message.indexOf('Project does not exist') > -1) { self.logger.error('Inconsistency: project exists in user "' + data.username + '" and in _projects, but not as a collection on its own: ', project._id); // Proceed with other projects.. } else { deferred.reject(err); } } } deferred.resolve(result); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Deletes a project from the _projects collection, deletes the collection for the the project and removes * the rights from all users. * * Authorization level: delete access for project * * @param {object} data - input parameters * @param {string} data.projectId - identifier for project. * @param {string} [data.username=gmeConfig.authentication.guestAccount] * @param {function} [callback] * @returns {Promise} //TODO: jsdocify this */ SafeStorage.prototype.deleteProject = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT, }, rejected = false, didExist; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { this.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.delete) { return Storage.prototype.deleteProject.call(self, data); } else { throw new Error('Not authorized to delete project [' + data.projectId + ']'); } }) .then(function (didExist_) { didExist = didExist_; return self.authorizer.setAccessRights(true, data.projectId, {read: false, write: false, delete: false}, projectAuthParams); }) .then(function () { return self.metadataStorage.deleteProject(data.projectId); }) .then(function () { deferred.resolve(didExist); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Creates a project and assigns a projectId by concatenating the username and the provided project name. * The user with the given username becomes the owner of the project. * * Authorization level: canCreate * * @param {object} data - input parameters * @param {string} data.projectName - name of new project. * @param {string} [data.username=gmeConfig.authentication.guestAccount] * @param {string} [data.ownerId=data.username] * @param {string} [data.kind] - Category of project typically based on meta. * @param {function} [callback] * @returns {promise} //TODO: jsdocify this */ SafeStorage.prototype.createProject = function (data, callback) { var deferred = Q.defer(), self = this, userAuthParams = { entityType: self.authorizer.ENTITY_TYPES.USER }, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(data.kind === null || typeof data.kind === 'undefined' || typeof data.kind === 'string', deferred, 'data.kind is not a string: ' + data.kind) || check(typeof data.projectName === 'string', deferred, 'data.projectName is not a string.') || check(REGEXP.PROJECT_NAME.test(data.projectName), deferred, 'data.projectName failed regexp: ' + data.projectName); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (data.ownerId) { rejected = rejected || check(typeof data.ownerId === 'string', deferred, 'data.ownerId is not a string.'); } else { data.ownerId = data.username; } if (rejected === false) { this.authorizer.getAccessRights(data.username, data.ownerId, userAuthParams) .then(function (ownerRights) { var now = (new Date()).toISOString(), info = { createdAt: now, viewedAt: now, modifiedAt: now, creator: data.username, viewer: data.username, modifier: data.username, kind: data.kind }; if (ownerRights.write !== true) { throw new Error('Not authorized to create new project for [' + data.ownerId + ']'); } return self.metadataStorage.addProject(data.ownerId, data.projectName, info); }) .then(function (projectId) { data.projectId = projectId; return self.authorizer.setAccessRights(data.ownerId, projectId, { read: true, write: true, delete: true }, projectAuthParams); }) .then(function () { // Add the default projectHooks var hookIds = Object.keys(self.gmeConfig.webhooks.defaults), cnt = hookIds.length; function addHooks() { var hookData; if (cnt === 0) { return; } else { cnt -= 1; hookData = JSON.parse(JSON.stringify(self.gmeConfig.webhooks.defaults[hookIds[cnt]])); if (typeof hookData.url === 'string') { delete hookData.options; return self.metadataStorage.addProjectHook(data.projectId, hookIds[cnt], hookData) .then(function () { return addHooks(); }); } else { self.logger.debug('Url not specified in default hook - will not add it to new project.'); return addHooks(); } } } return addHooks(); }) .then(function () { return Storage.prototype.createProject.call(self, data); }) .then(function (dbProject) { var project = new UserProject(dbProject, self, self.logger, self.gmeConfig); project.setUser(data.username); deferred.resolve(project); }) .catch(function (err) { // TODO: Clean up appropriately when failure to add to model, user or projects database. deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * * Authorization level: canCreate and delete access for project * @param data * @param callback * @returns {*} */ SafeStorage.prototype.transferProject = function (data, callback) { var deferred = Q.defer(), self = this, userAuthParams = { entityType: self.authorizer.ENTITY_TYPES.USER }, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId) || check(typeof data.newOwnerId === 'string', deferred, 'data.newOwnerId is not a string.'); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.delete) { return self.authorizer.getAccessRights(data.username, data.newOwnerId, userAuthParams); } else { throw new Error('Not authorized to delete project [' + data.projectId + ']'); } }) .then(function (ownerRights) { if (ownerRights &amp;&amp; ownerRights.write !== true) { throw new Error('Not authorized to transfer project to [' + data.newOwnerId + ']'); } // Remove old and add new metadata for the project. return self.metadataStorage.transferProject(data.projectId, data.newOwnerId); }) .then(function (newProjectId) { // Rename the project collection. data.newProjectId = newProjectId; return Storage.prototype.renameProject.call(self, data); }) .then(function () { // Remove all previous project access rights. self.authorizer.setAccessRights(true, data.projectId, { read: false, write: false, delete: false }, projectAuthParams); }) .then(function () { // Add full project access rights to the new owner. return self.authorizer.setAccessRights(data.newOwnerId, data.newProjectId, { read: true, write: true, delete: true }, projectAuthParams); }) .then(function () { deferred.resolve(data.newProjectId); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Duplicates a project including all data-objects, commits, branches etc. * * Authorization level: canCreate and read access for project * * @param {object} data - input parameters * @param {string} data.projectId - id of existing project that will be duplicated. * @param {string} data.projectName - name of new project. * @param {string} [data.username=gmeConfig.authentication.guestAccount] * @param {string} [data.ownerId=data.username] * @param {function} [callback] * @returns {promise} //TODO: jsdocify this */ SafeStorage.prototype.duplicateProject = function (data, callback) { var deferred = Q.defer(), self = this, userAuthParams = { entityType: self.authorizer.ENTITY_TYPES.USER }, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId) || check(typeof data.projectName === 'string', deferred, 'data.projectName is not a string.') || check(REGEXP.PROJECT_NAME.test(data.projectName), deferred, 'data.projectName failed regexp: ' + data.projectName); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (data.ownerId) { rejected = rejected || check(typeof data.ownerId === 'string', deferred, 'data.ownerId is not a string.'); } else { data.ownerId = data.username; } if (self.gmeConfig.seedProjects.allowDuplication === false) { deferred.reject(new Error('gmeConfig.seedProjects.allowDuplication is set to false')); rejected = true; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.read) { return Q.all([ self.authorizer.getAccessRights(data.username, data.ownerId, userAuthParams), self.metadataStorage.getProject(data.projectId) ]); } else { throw new Error('Not authorized to read project [' + data.projectId + ']'); } }) .then(function (res) { var ownerRights = res[0], prevProjectData = res[1], now = (new Date()).toISOString(), info = { createdAt: now, viewedAt: now, modifiedAt: now, creator: data.username, viewer: data.username, modifier: data.username, kind: prevProjectData.info.kind }; if (ownerRights &amp;&amp; ownerRights.write !== true) { throw new Error('Not authorized to create project for [' + data.ownerId + ']'); } return self.metadataStorage.duplicateProject(data.projectId, data.ownerId, data.projectName, info); }) .then(function (newProjectId) { data.newProjectId = newProjectId; return self.authorizer.setAccessRights(data.ownerId, newProjectId, { read: true, write: true, delete: true }, projectAuthParams); }) .then(function () { return Storage.prototype.duplicateProject.call(self, data); }) .then(function (dbProject) { var project = new UserProject(dbProject, self, self.logger, self.gmeConfig); project.setUser(data.username); deferred.resolve(project); }) .catch(function (err) { // TODO: Clean up appropriately when failure to add to model, user or projects database. deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Returns a dictionary with all the branches and their hashes within a project. * Example: { * master: '#someHash', * b1: '#someOtherHash' * } * * Authorization level: read access for project * * @param {object} data - input parameters * @param {string} data.projectId - identifier for project. * @param {string} [data.username=gmeConfig.authentication.guestAccount] * @param {function} [callback] * @returns {promise} //TODO: jsdocify this */ SafeStorage.prototype.getBranches = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.read) { return Storage.prototype.getBranches.call(self, data); } else { throw new Error('Not authorized to read project [' + data.projectId + ']'); } }) .then(function (result) { deferred.resolve(result); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Returns an array of commits for a project ordered by their timestamp. * * Authorization level: read access for project * * @param {object} data - input parameters * @param {string} data.projectId - identifier for project. * @param {number} data.number - maximum number of commits to load. * @param {number|string} data.before - timestamp or commitHash to load history from. When number given it will load * data.number of commits strictly before data.before, when commitHash is given it will return that commit too. * @param {string} [data.username=gmeConfig.authentication.guestAccount] * @param {function} [callback] * @returns {promise} //TODO: jsdocify this */ SafeStorage.prototype.getCommits = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId) || check(typeof data.before === 'number' || typeof data.before === 'string', deferred, 'data.before is not a number nor string') || check(typeof data.number === 'number', deferred, 'data.number is not a number'); if (typeof data.before === 'string') { rejected = rejected || check(REGEXP.HASH.test(data.before), deferred, 'data.before is not a number nor a valid hash.'); } if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.read) { return Storage.prototype.getCommits.call(self, data); } else { throw new Error('Not authorized to read project [' + data.projectId + ']'); } }) .then(function (result) { deferred.resolve(result); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Returns an array of commits starting from either a branch(es) or commitHash(es). * They are ordered by the rules (applied in order) * 1. Descendants are always before their ancestors * 2. By their timestamp * * Authorization level: read access for project * * @param {object} data - input parameters * @param {string} data.projectId - identifier for project. * @param {number} data.number - maximum number of commits to load. * @param {string|string[]} data.start - BranchName or commitHash, or an array of such. * @param {string} [data.username=gmeConfig.authentication.guestAccount] * @param {function} [callback] * @returns {promise} //TODO: jsdocify this */ SafeStorage.prototype.getHistory = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId) || check(typeof data.start === 'string' || (typeof data.start === 'object' &amp;&amp; data.start instanceof Array), deferred, 'data.start is not a string or array') || check(typeof data.number === 'number', deferred, 'data.number is not a number'); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.read) { return Storage.prototype.getHistory.call(self, data); } else { throw new Error('Not authorized to read project [' + data.projectId + ']'); } }) .then(function (result) { deferred.resolve(result); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Returns the latest commit data for a branch within the project. (This is the same data that is provided during * a BRANCH_UPDATE event.) * * Example: { * projectId: 'guest+TestProject', * branchName: 'master', * commitObject: { * _id: '#someCommitHash', * root: '#someNodeHash', * parents: ['#someOtherCommitHash'], * update: ['guest'], * time: 1430169614741, * message: 'createChild(/1/2)', * type: 'commit' * }, * coreObject: [{coreObj}, ..., {coreObj}], * } * * Authorization level: read access for project * * @param {object} data - input parameters * @param {string} data.projectId - identifier for project. * @param {string} data.branchName - name of the branch. * @param {string} [data.username=gmeConfig.authentication.guestAccount] * @param {function} [callback] * @returns {promise} //TODO: jsdocify this */ SafeStorage.prototype.getLatestCommitData = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId) || check(typeof data.branchName === 'string', deferred, 'data.branchName is not a string.') || check(REGEXP.BRANCH.test(data.branchName), deferred, 'data.branchName failed regexp: ' + data.branchName); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.read) { return Storage.prototype.getLatestCommitData.call(self, data); } else { throw new Error('Not authorized to read project [' + data.projectId + ']'); } }) .then(function (result) { deferred.resolve(result); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Authorization: write access for data.projectId * @param data * @param callback * @returns {*} */ SafeStorage.prototype.makeCommit = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId) || check(data.commitObject !== null &amp;&amp; typeof data.commitObject === 'object', deferred, 'data.commitObject not an object.') || check(data.coreObjects !== null &amp;&amp; typeof data.coreObjects === 'object', deferred, 'data.coreObjects not an object.'); // Checks when branchName is given and the branch will be updated if (rejected === false &amp;&amp; typeof data.branchName !== 'undefined') { rejected = check(typeof data.branchName === 'string', deferred, 'data.branchName is not a string.') || check(REGEXP.BRANCH.test(data.branchName), deferred, 'data.branchName failed regexp: ' + data.branchName) || check(typeof data.commitObject._id === 'string', deferred, 'data.commitObject._id is not a string.') || check(typeof data.commitObject.root === 'string', deferred, 'data.commitObject.root is not a string.') || check(REGEXP.HASH.test(data.commitObject._id), deferred, 'data.commitObject._id is not a valid hash: ' + data.commitObject._id) || check(data.commitObject.parents instanceof Array, deferred, 'data.commitObject.parents is not an array.') || check(data.commitObject.parents.length === 0 || typeof data.commitObject.parents[0] === 'string', deferred, 'data.commitObject.parents[0] is not a string.') || check( data.commitObject.parents.length === 0 || data.commitObject.parents[0] === '' || REGEXP.HASH.test(data.commitObject.parents[0]), deferred, 'data.commitObject.parents[0] is not a valid hash: ' + data.commitObject.parents[0]) || check(REGEXP.HASH.test(data.commitObject.root), deferred, 'data.commitObject.root is not a valid hash: ' + data.commitObject.root); // Commits without coreObjects is valid now (the assumption is that the rootObject does exist. //check(typeof data.coreObjects[data.commitObject.root] === 'object', deferred, // 'data.coreObjects[data.commitObject.root] is not an object'); if (typeof data.oldHash === 'string') { // Provide the possibility to refer to an oldHash explicitly rather than from the commitObj, // the is needed when e.g. undoing/redoing. check(data.oldHash === '' || REGEXP.HASH.test(data.oldHash), deferred, 'data.oldHash is not a valid hash: ' + data.oldHash); } } if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.write) { return Storage.prototype.makeCommit.call(self, data); } else { throw new Error('Not authorized to write project [' + data.projectId + ']'); } }) .then(function (result) { deferred.resolve(result); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Authorization: write access for data.projectId * @param {object} data - input parameters * @param {string} data.projectId - identifier for project. * @param {string} data.fromCommit - starting point of the squash. * @param {string} data.toCommitOrBranch - branch or commit where the endpoint of squash can be found. * @param {string} [data.username=gmeConfig.authentication.guestAccount] * @param callback * @returns {*} */ SafeStorage.prototype.squashCommits = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId) || check(typeof data.fromCommit === 'string', deferred, 'data.fromCommit not a string.') || check(REGEXP.HASH.test(data.fromCommit), deferred, 'data.fromCommit failed regexp: ' + data.fromCommit) || check(typeof data.toCommitOrBranch === 'string', deferred, 'data.toCommitOrBranch not a string.'); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.write) { return Storage.prototype.squashCommits.call(self, data); } else { throw new Error('Not authorized to write project [' + data.projectId + ']'); } }) .then(function (result) { deferred.resolve(result); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Authorization: read access for data.projectId * @param data * @param callback * @returns {*} */ SafeStorage.prototype.getBranchHash = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId) || check(typeof data.branchName === 'string', deferred, 'data.branchName is not a string.') || check(REGEXP.BRANCH.test(data.branchName), deferred, 'data.branchName failed regexp: ' + data.branchName); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.read) { return Storage.prototype.getBranchHash.call(self, data); } else { throw new Error('Not authorized to read project [' + data.projectId + ']'); } }) .then(function (result) { deferred.resolve(result); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Authorization: write access for data.projectId * @param data * @param callback * @returns {*} */ SafeStorage.prototype.setBranchHash = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId) || check(typeof data.branchName === 'string', deferred, 'data.branchName is not a string.') || check(REGEXP.BRANCH.test(data.branchName), deferred, 'data.branchName failed regexp: ' + data.branchName) || check(typeof data.oldHash === 'string', deferred, 'data.oldHash is not a string.') || check(data.oldHash === '' || REGEXP.HASH.test(data.oldHash), deferred, 'data.oldHash is not a valid hash: ' + data.oldHash) || check(typeof data.newHash === 'string', deferred, 'data.newHash is not a string.') || check(data.newHash === '' || REGEXP.HASH.test(data.newHash), deferred, 'data.newHash is not a valid hash: ' + data.newHash); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.write) { return Storage.prototype.setBranchHash.call(self, data); } else { throw new Error('Not authorized to write project [' + data.projectId + ']'); } }) .then(function (result) { deferred.resolve(result); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Authorization: read access for data.projectId * @param data * @param callback * @returns {*} */ SafeStorage.prototype.getCommonAncestorCommit = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId) || check(typeof data.commitA === 'string', deferred, 'data.commitA is not a string.') || check(data.commitA === '' || REGEXP.HASH.test(data.commitA), deferred, 'data.commitA is not a valid hash: ' + data.commitA) || check(typeof data.commitB === 'string', deferred, 'data.commitB is not a string.') || check(data.commitB === '' || REGEXP.HASH.test(data.commitB), deferred, 'data.commitB is not a valid hash: ' + data.commitB); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.read) { return Storage.prototype.getCommonAncestorCommit.call(self, data); } else { throw new Error('Not authorized to read project [' + data.projectId + ']'); } }) .then(function (commonHash) { deferred.resolve(commonHash); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Authorization: write access for data.projectId * @param data * @param callback * @returns {*} */ SafeStorage.prototype.createBranch = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId) || check(typeof data.branchName === 'string', deferred, 'data.branchName is not a string.') || check(REGEXP.BRANCH.test(data.branchName), deferred, 'data.branchName failed regexp: ' + data.branchName) || check(typeof data.hash === 'string', deferred, 'data.hash is not a string.') || check(data.hash === '' || REGEXP.HASH.test(data.hash), deferred, 'data.hash is not a valid hash: ' + data.hash); data.oldHash = ''; data.newHash = data.hash; if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.write) { return Storage.prototype.setBranchHash.call(self, data); } else { throw new Error('Not authorized to write project [' + data.projectId + ']'); } }) .then(function (result) { deferred.resolve(result); }) .catch(function (err) { deferred.reject(err); }); } return deferred.promise.nodeify(callback); }; /** * Authorization: write access for data.projectId * @param data * @param callback * @returns {*} */ SafeStorage.prototype.deleteBranch = function (data, callback) { var deferred = Q.defer(), self = this, projectAuthParams = { entityType: self.authorizer.ENTITY_TYPES.PROJECT }, rejected = false; rejected = check(data !== null &amp;&amp; typeof data === 'object', deferred, 'data is not an object.') || check(typeof data.projectId === 'string', deferred, 'data.projectId is not a string.') || check(REGEXP.PROJECT.test(data.projectId), deferred, 'data.projectId failed regexp: ' + data.projectId) || check(typeof data.branchName === 'string', deferred, 'data.branchName is not a string.') || check(REGEXP.BRANCH.test(data.branchName), deferred, 'data.branchName failed regexp: ' + data.branchName); if (Object.hasOwn(data, 'username')) { rejected = rejected || check(typeof data.username === 'string', deferred, 'data.username is not a string.'); } else { data.username = this.gmeConfig.authentication.guestAccount; } if (rejected === false) { self.authorizer.getAccessRights(data.username, data.projectId, projectAuthParams) .then(function (projectAccess) { if (projectAccess &amp;&amp; projectAccess.write) { return Storage.prototype.getBranchHash.call(self, data); } else { throw new Error('Not authorized to write project [' + data.projectId + ']'); } }) .then(function (branchHash) { data.oldHash = branchHash; data.newHash = ''; return Storage.prototype.setBranchHash.call(self, data); }) .then(function (result) { deferred.resolve(result); }) .catch(function (err) { d