diff --git a/src/appdb.js b/src/appdb.js index 331faff25..5e74f2e1b 100644 --- a/src/appdb.js +++ b/src/appdb.js @@ -24,6 +24,9 @@ exports = module.exports = { setRunCommand: setRunCommand, getAppStoreIds: getAppStoreIds, + setOwner: setOwner, + transferOwnership: transferOwnership, + // installation codes (keep in sync in UI) ISTATE_PENDING_INSTALL: 'pending_install', // installs and fresh reinstalls ISTATE_PENDING_CLONE: 'pending_clone', // clone @@ -506,3 +509,31 @@ function getAddonConfigByName(appId, addonId, name, callback) { callback(null, results[0].value); }); } + +function setOwner(appId, ownerId, callback) { + assert.strictEqual(typeof appId, 'string'); + assert.strictEqual(typeof ownerId, 'string'); + assert.strictEqual(typeof callback, 'function'); + + database.query('UPDATE apps SET ownerId=? WHERE appId=?', [ ownerId, appId ], function (error, results) { + if (error && error.code === 'ER_NO_REFERENCED_ROW_2') return callback(new DatabaseError(DatabaseError.NOT_FOUND, 'No such user')); + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + + if (results.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND, 'No such app')); + + callback(null); + }); +} + +function transferOwnership(oldOwnerId, newOwnerId, callback) { + assert.strictEqual(typeof oldOwnerId, 'string'); + assert.strictEqual(typeof newOwnerId, 'string'); + assert.strictEqual(typeof callback, 'function'); + + database.query('UPDATE apps SET ownerId=? WHERE ownerId=?', [ newOwnerId, oldOwnerId ], function (error, results) { + if (error && error.code === 'ER_NO_REFERENCED_ROW_2') return callback(new DatabaseError(DatabaseError.NOT_FOUND, 'No such user')); + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + + callback(null); + }); +} diff --git a/src/apps.js b/src/apps.js index faa21c9a3..ea34687e8 100644 --- a/src/apps.js +++ b/src/apps.js @@ -42,6 +42,9 @@ exports = module.exports = { downloadFile: downloadFile, uploadFile: uploadFile, + setOwner: setOwner, + transferOwnership: transferOwnership, + // exported for testing _validateHostname: validateHostname, _validatePortBindings: validatePortBindings, @@ -1357,3 +1360,28 @@ function uploadFile(appId, sourceFilePath, destFilePath, callback) { callback(null); }); } + +function setOwner(appId, ownerId, callback) { + assert.strictEqual(typeof appId, 'string'); + assert.strictEqual(typeof callback, 'function'); + + appdb.setOwner(appId, ownerId, function (error) { + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, error.message)); + if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); + + callback(); + }); +} + +function transferOwnership(oldOwnerId, newOwnerId, callback) { + assert.strictEqual(typeof oldOwnerId, 'string'); + assert.strictEqual(typeof newOwnerId, 'string'); + assert.strictEqual(typeof callback, 'function'); + + appdb.transferOwnership(oldOwnerId, newOwnerId, function (error) { + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, error.message)); + if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); + + callback(); + }); +} diff --git a/src/routes/apps.js b/src/routes/apps.js index deecab5eb..6aaa5e8fc 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -21,6 +21,8 @@ exports = module.exports = { cloneApp: cloneApp, + setOwner: setOwner, + uploadFile: uploadFile, downloadFile: downloadFile }; @@ -554,3 +556,17 @@ function downloadFile(req, res, next) { stream.pipe(res); }); } + +function setOwner(req, res, next) { + assert.strictEqual(typeof req.params.id, 'string'); + assert.strictEqual(typeof req.body, 'object'); + + if (typeof req.body.ownerId !== 'string') return next(new HttpError(400, 'ownerId must be a string')); + + apps.setOwner(req.params.id, req.body.ownerId, function (error) { + if (error && error.reason === AppsError.NOT_FOUND) return next(new HttpError(404, error.message)); + if (error) return next(new HttpError(500, error)); + + next(new HttpSuccess(200, { })); + }); +} \ No newline at end of file diff --git a/src/routes/users.js b/src/routes/users.js index 868339fe1..793a1afd8 100644 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -8,7 +8,8 @@ exports = module.exports = { remove: remove, verifyPassword: verifyPassword, sendInvite: sendInvite, - setGroups: setGroups + setGroups: setGroups, + transferOwnership: transferOwnership }; var assert = require('assert'), @@ -158,3 +159,17 @@ function setGroups(req, res, next) { next(new HttpSuccess(204)); }); } + +function transferOwnership(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + assert.strictEqual(typeof req.params.userId, 'string'); + + if (typeof req.body.ownerId !== 'string') return next(new HttpError(400, 'ownerId must be a string')); + + users.transferOwnership(req.params.userId, req.body.ownerId, function (error) { + if (error && error.reason === UsersError.NOT_FOUND) return next(new HttpError(404, 'No such user')); + if (error) return next(new HttpError(500, error)); + + next(new HttpSuccess(200, {})); + }); +} \ No newline at end of file diff --git a/src/server.js b/src/server.js index b454be695..bbc6f1250 100644 --- a/src/server.js +++ b/src/server.js @@ -149,6 +149,7 @@ function initializeExpressSync() { router.post('/api/v1/users/:userId', usersManageScope, routes.users.update); router.put ('/api/v1/users/:userId/groups', usersManageScope, routes.users.setGroups); router.post('/api/v1/users/:userId/invite', usersManageScope, routes.users.sendInvite); + router.post('/api/v1/users/:userId/transfer', usersManageScope, routes.users.transferOwnership); // Group management router.get ('/api/v1/groups', usersReadScope, routes.groups.list); @@ -208,6 +209,7 @@ function initializeExpressSync() { router.post('/api/v1/apps/:id/clone', appsManageScope, routes.apps.cloneApp); router.get ('/api/v1/apps/:id/download', appsManageScope, routes.apps.downloadFile); router.post('/api/v1/apps/:id/upload', appsManageScope, multipart, routes.apps.uploadFile); + router.post('/api/v1/apps/:id/owner', appsManageScope, routes.apps.setOwner); // settings routes (these are for the settings tab - avatar & name have public routes for normal users. see above) router.get ('/api/v1/settings/app_autoupdate_pattern', settingsScope, routes.settings.getAppAutoupdatePattern); diff --git a/src/users.js b/src/users.js index 89cf0225d..3946ae480 100644 --- a/src/users.js +++ b/src/users.js @@ -26,10 +26,13 @@ exports = module.exports = { setMembership: setMembership, setTwoFactorAuthenticationSecret: setTwoFactorAuthenticationSecret, enableTwoFactorAuthentication: enableTwoFactorAuthentication, - disableTwoFactorAuthentication: disableTwoFactorAuthentication + disableTwoFactorAuthentication: disableTwoFactorAuthentication, + transferOwnership: transferOwnership }; -var assert = require('assert'), +var apps = require('./apps.js'), + AppsError = apps.AppsError, + assert = require('assert'), crypto = require('crypto'), config = require('./config.js'), constants = require('./constants.js'), @@ -641,3 +644,16 @@ function disableTwoFactorAuthentication(userId, callback) { callback(null); }); } + +function transferOwnership(oldOwnerId, newOwnerId, callback) { + assert.strictEqual(typeof oldOwnerId, 'string'); + assert.strictEqual(typeof newOwnerId, 'string'); + assert.strictEqual(typeof callback, 'function'); + + apps.transferOwnership(oldOwnerId, newOwnerId, function (error) { + if (error && error.reason === AppsError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND, error.message)); + if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error)); + + callback(null); + }); +}