Remove Oauth clients code

This commit is contained in:
Johannes Zellner
2020-02-06 16:57:33 +01:00
parent 2854462e0e
commit 2b6bf8d195
19 changed files with 105 additions and 1436 deletions
+5 -29
View File
@@ -29,13 +29,11 @@ exports = module.exports = {
SERVICE_STATUS_STOPPED: 'stopped'
};
var accesscontrol = require('./accesscontrol.js'),
appdb = require('./appdb.js'),
var appdb = require('./appdb.js'),
apps = require('./apps.js'),
assert = require('assert'),
async = require('async'),
BoxError = require('./boxerror.js'),
clients = require('./clients.js'),
constants = require('./constants.js'),
crypto = require('crypto'),
debug = require('debug')('box:addons'),
@@ -777,29 +775,11 @@ function setupOauth(app, options, callback) {
if (!app.sso) return callback(null);
var appId = app.id;
var redirectURI = 'https://' + app.fqdn;
var scope = accesscontrol.SCOPE_PROFILE;
const env = [];
clients.delByAppIdAndType(appId, clients.TYPE_OAUTH, function (error) { // remove existing creds
if (error && error.reason !== BoxError.NOT_FOUND) return callback(error);
debugApp(app, 'Setting oauth addon config to %j', env);
clients.add(appId, clients.TYPE_OAUTH, redirectURI, scope, function (error, result) {
if (error) return callback(error);
const envPrefix = app.manifest.manifestVersion <= 1 ? '' : 'CLOUDRON_';
var env = [
{ name: `${envPrefix}OAUTH_CLIENT_ID`, value: result.id },
{ name: `${envPrefix}OAUTH_CLIENT_SECRET`, value: result.clientSecret },
{ name: `${envPrefix}OAUTH_ORIGIN`, value: settings.adminOrigin() }
];
debugApp(app, 'Setting oauth addon config to %j', env);
appdb.setAddonConfig(appId, 'oauth', env, callback);
});
});
appdb.setAddonConfig(app.id, 'oauth', env, callback);
}
function teardownOauth(app, options, callback) {
@@ -809,11 +789,7 @@ function teardownOauth(app, options, callback) {
debugApp(app, 'teardownOauth');
clients.delByAppIdAndType(app.id, clients.TYPE_OAUTH, function (error) {
if (error && error.reason !== BoxError.NOT_FOUND) debug(error);
appdb.unsetAddonConfig(app.id, 'oauth', callback);
});
appdb.unsetAddonConfig(app.id, 'oauth', callback);
}
function setupEmail(app, options, callback) {
-179
View File
@@ -1,179 +0,0 @@
'use strict';
exports = module.exports = {
get: get,
getAll: getAll,
getAllWithTokenCount: getAllWithTokenCount,
getAllWithTokenCountByIdentifier: getAllWithTokenCountByIdentifier,
add: add,
del: del,
getByAppId: getByAppId,
getByAppIdAndType: getByAppIdAndType,
upsert: upsert,
delByAppId: delByAppId,
delByAppIdAndType: delByAppIdAndType,
_clear: clear
};
var assert = require('assert'),
BoxError = require('./boxerror.js'),
database = require('./database.js');
var CLIENTS_FIELDS = [ 'id', 'appId', 'type', 'clientSecret', 'redirectURI', 'scope' ].join(',');
var CLIENTS_FIELDS_PREFIXED = [ 'clients.id', 'clients.appId', 'clients.type', 'clients.clientSecret', 'clients.redirectURI', 'clients.scope' ].join(',');
function get(id, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + CLIENTS_FIELDS + ' FROM clients WHERE id = ?', [ id ], function (error, result) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, `Client not found: ${id}`));
callback(null, result[0]);
});
}
function getAll(callback) {
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + CLIENTS_FIELDS + ' FROM clients ORDER BY appId', function (error, results) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
callback(null, results);
});
}
function getAllWithTokenCount(callback) {
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + CLIENTS_FIELDS_PREFIXED + ',COUNT(tokens.clientId) AS tokenCount FROM clients LEFT OUTER JOIN tokens ON clients.id=tokens.clientId GROUP BY clients.id', [], function (error, results) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
callback(null, results);
});
}
function getAllWithTokenCountByIdentifier(identifier, callback) {
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + CLIENTS_FIELDS_PREFIXED + ',COUNT(tokens.clientId) AS tokenCount FROM clients LEFT OUTER JOIN tokens ON clients.id=tokens.clientId WHERE tokens.identifier=? GROUP BY clients.id', [ identifier ], function (error, results) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
callback(null, results);
});
}
function getByAppId(appId, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + CLIENTS_FIELDS + ' FROM clients WHERE appId = ? LIMIT 1', [ appId ], function (error, result) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Client not found'));
callback(null, result[0]);
});
}
function getByAppIdAndType(appId, type, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT ' + CLIENTS_FIELDS + ' FROM clients WHERE appId = ? AND type = ? LIMIT 1', [ appId, type ], function (error, result) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Client not found'));
callback(null, result[0]);
});
}
function add(id, appId, type, clientSecret, redirectURI, scope, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof clientSecret, 'string');
assert.strictEqual(typeof redirectURI, 'string');
assert.strictEqual(typeof scope, 'string');
assert.strictEqual(typeof callback, 'function');
var data = [ id, appId, type, clientSecret, redirectURI, scope ];
database.query('INSERT INTO clients (id, appId, type, clientSecret, redirectURI, scope) VALUES (?, ?, ?, ?, ?, ?)', data, function (error, result) {
if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS));
if (error || result.affectedRows === 0) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
callback(null);
});
}
function upsert(id, appId, type, clientSecret, redirectURI, scope, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof clientSecret, 'string');
assert.strictEqual(typeof redirectURI, 'string');
assert.strictEqual(typeof scope, 'string');
assert.strictEqual(typeof callback, 'function');
var data = [ id, appId, type, clientSecret, redirectURI, scope ];
database.query('REPLACE INTO clients (id, appId, type, clientSecret, redirectURI, scope) VALUES (?, ?, ?, ?, ?, ?)', data, function (error, result) {
if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS));
if (error || result.affectedRows === 0) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
callback(null);
});
}
function del(id, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('DELETE FROM clients WHERE id = ?', [ id ], function (error, result) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, `Client not found: ${id}`));
callback(null);
});
}
function delByAppId(appId, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('DELETE FROM clients WHERE appId=?', [ appId ], function (error, result) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Client not found'));
callback(null);
});
}
function delByAppIdAndType(appId, type, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('DELETE FROM clients WHERE appId=? AND type=?', [ appId, type ], function (error, result) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Client not found'));
callback(null);
});
}
function clear(callback) {
assert.strictEqual(typeof callback, 'function');
database.query('DELETE FROM clients', function (error) {
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
callback(null);
});
}
-329
View File
@@ -1,329 +0,0 @@
'use strict';
exports = module.exports = {
add: add,
get: get,
del: del,
getAll: getAll,
getByAppIdAndType: getByAppIdAndType,
getTokensByUserId: getTokensByUserId,
delTokensByUserId: delTokensByUserId,
delByAppIdAndType: delByAppIdAndType,
addTokenByUserId: addTokenByUserId,
delToken: delToken,
issueDeveloperToken: issueDeveloperToken,
addDefaultClients: addDefaultClients,
removeTokenPrivateFields: removeTokenPrivateFields,
// client ids. we categorize them so we can have different restrictions based on the client
ID_WEBADMIN: 'cid-webadmin', // dashboard oauth
ID_SDK: 'cid-sdk', // created by user via dashboard
ID_CLI: 'cid-cli', // created via cli tool
// client type enums
TYPE_EXTERNAL: 'external',
TYPE_BUILT_IN: 'built-in',
TYPE_OAUTH: 'addon-oauth',
TYPE_PROXY: 'addon-proxy'
};
var apps = require('./apps.js'),
assert = require('assert'),
async = require('async'),
BoxError = require('./boxerror.js'),
clientdb = require('./clientdb.js'),
constants = require('./constants.js'),
debug = require('debug')('box:clients'),
eventlog = require('./eventlog.js'),
hat = require('./hat.js'),
accesscontrol = require('./accesscontrol.js'),
tokendb = require('./tokendb.js'),
users = require('./users.js'),
uuid = require('uuid'),
_ = require('underscore');
function validateClientName(name) {
assert.strictEqual(typeof name, 'string');
if (name.length < 1) return new BoxError(BoxError.BAD_FIELD, 'name must be atleast 1 character', { field: 'name' });
if (name.length > 128) return new BoxError(BoxError.BAD_FIELD, 'name too long', { field: 'name' });
if (/[^a-zA-Z0-9-]/.test(name)) return new BoxError(BoxError.BAD_FIELD, 'name can only contain alphanumerals and dash', { field: 'name' });
return null;
}
function validateTokenName(name) {
assert.strictEqual(typeof name, 'string');
if (name.length > 64) return new BoxError(BoxError.BAD_FIELD, 'name too long', { field: 'name' });
return null;
}
function add(appId, type, redirectURI, scope, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof redirectURI, 'string');
assert.strictEqual(typeof scope, 'string');
assert.strictEqual(typeof callback, 'function');
var error = accesscontrol.validateScopeString(scope);
if (error) return callback(error);
error = validateClientName(appId);
if (error) return callback(error);
var id = 'cid-' + uuid.v4();
var clientSecret = hat(8 * 128);
clientdb.add(id, appId, type, clientSecret, redirectURI, scope, function (error) {
if (error) return callback(error);
var client = {
id: id,
appId: appId,
type: type,
clientSecret: clientSecret,
redirectURI: redirectURI,
scope: scope
};
callback(null, client);
});
}
function get(id, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof callback, 'function');
clientdb.get(id, function (error, result) {
if (error) return callback(error);
callback(null, result);
});
}
function del(id, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof callback, 'function');
clientdb.del(id, function (error, result) {
if (error) return callback(error);
callback(null, result);
});
}
function getAll(callback) {
assert.strictEqual(typeof callback, 'function');
clientdb.getAll(function (error, results) {
if (error && error.reason === BoxError.NOT_FOUND) return callback(null, []);
if (error) return callback(error);
var tmp = [];
async.each(results, function (record, callback) {
if (record.type === exports.TYPE_EXTERNAL || record.type === exports.TYPE_BUILT_IN) {
// the appId in this case holds the name
record.name = record.appId;
tmp.push(record);
return callback(null);
}
apps.get(record.appId, function (error, result) {
if (error) {
console.error('Failed to get app details for oauth client', record.appId, error);
return callback(null); // ignore error so we continue listing clients
}
if (record.type === exports.TYPE_PROXY) record.name = result.manifest.title + ' Website Proxy';
if (record.type === exports.TYPE_OAUTH) record.name = result.manifest.title + ' OAuth';
record.domain = result.fqdn;
tmp.push(record);
callback(null);
});
}, function (error) {
if (error) return callback(error);
callback(null, tmp);
});
});
}
function getByAppIdAndType(appId, type, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof callback, 'function');
clientdb.getByAppIdAndType(appId, type, function (error, result) {
if (error) return callback(error);
callback(null, result);
});
}
function getTokensByUserId(clientId, userId, callback) {
assert.strictEqual(typeof clientId, 'string');
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof callback, 'function');
tokendb.getByIdentifierAndClientId(userId, clientId, function (error, result) {
if (error && error.reason === BoxError.NOT_FOUND) {
// this can mean either that there are no tokens or the clientId is actually unknown
get(clientId, function (error/*, result*/) {
if (error) return callback(error);
callback(null, []);
});
return;
}
if (error) return callback(error);
callback(null, result || []);
});
}
function delTokensByUserId(clientId, userId, callback) {
assert.strictEqual(typeof clientId, 'string');
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof callback, 'function');
tokendb.delByIdentifierAndClientId(userId, clientId, function (error) {
if (error && error.reason === BoxError.NOT_FOUND) {
// this can mean either that there are no tokens or the clientId is actually unknown
get(clientId, function (error/*, result*/) {
if (error) return callback(error);
callback(null);
});
return;
}
if (error) return callback(error);
callback(null);
});
}
function delByAppIdAndType(appId, type, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof callback, 'function');
getByAppIdAndType(appId, type, function (error, result) {
if (error) return callback(error);
tokendb.delByClientId(result.id, function (error) {
if (error && error.reason !== BoxError.NOT_FOUND) return callback(error);
clientdb.delByAppIdAndType(appId, type, function (error) {
if (error) return callback(error);
callback(null);
});
});
});
}
function addTokenByUserId(clientId, userId, expiresAt, options, callback) {
assert.strictEqual(typeof clientId, 'string');
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof expiresAt, 'number');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
const name = options.name || '';
let error = validateTokenName(name);
if (error) return callback(error);
get(clientId, function (error, result) {
if (error) return callback(error);
users.get(userId, function (error, user) {
if (error) return callback(error);
accesscontrol.scopesForUser(user, function (error, userScopes) {
if (error) return callback(error);
const scope = accesscontrol.canonicalScopeString(result.scope);
const authorizedScopes = accesscontrol.intersectScopes(userScopes, scope.split(','));
const token = {
id: 'tid-' + uuid.v4(),
accessToken: hat(8 * 32),
identifier: userId,
clientId: result.id,
expires: expiresAt,
scope: authorizedScopes.join(','),
name: name
};
tokendb.add(token, function (error) {
if (error) return callback(error);
callback(null, {
accessToken: token.accessToken,
tokenScopes: authorizedScopes,
identifier: userId,
clientId: result.id,
expires: expiresAt
});
});
});
});
});
}
function issueDeveloperToken(userObject, auditSource, callback) {
assert.strictEqual(typeof userObject, 'object');
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
const expiresAt = Date.now() + (30 * 24 * 60 * 60 * 1000); // cli tokens are valid for a month
addTokenByUserId(exports.ID_CLI, userObject.id, expiresAt, {}, function (error, result) {
if (error) return callback(error);
eventlog.add(eventlog.ACTION_USER_LOGIN, auditSource, { userId: userObject.id, user: users.removePrivateFields(userObject) });
callback(null, result);
});
}
function delToken(clientId, tokenId, callback) {
assert.strictEqual(typeof clientId, 'string');
assert.strictEqual(typeof tokenId, 'string');
assert.strictEqual(typeof callback, 'function');
get(clientId, function (error) {
if (error) return callback(error);
tokendb.del(tokenId, function (error) {
if (error) return callback(error);
callback(null);
});
});
}
function addDefaultClients(origin, callback) {
assert.strictEqual(typeof origin, 'string');
assert.strictEqual(typeof callback, 'function');
debug('Adding default clients');
// The domain might have changed, therefor we have to update the record
// id, appId, type, clientSecret, redirectURI, scope
async.series([
clientdb.upsert.bind(null, exports.ID_WEBADMIN, 'Settings', 'built-in', 'secret-webadmin', origin, '*'),
clientdb.upsert.bind(null, exports.ID_SDK, 'SDK', 'built-in', 'secret-sdk', origin, '*'),
clientdb.upsert.bind(null, exports.ID_CLI, 'Cloudron Tool', 'built-in', 'secret-cli', origin, '*')
], callback);
}
function removeTokenPrivateFields(token) {
return _.pick(token, 'id', 'identifier', 'clientId', 'scope', 'expires', 'name');
}
+1 -5
View File
@@ -27,7 +27,6 @@ var apps = require('./apps.js'),
auditSource = require('./auditsource.js'),
backups = require('./backups.js'),
BoxError = require('./boxerror.js'),
clients = require('./clients.js'),
constants = require('./constants.js'),
cron = require('./cron.js'),
debug = require('debug')('box:cloudron'),
@@ -288,10 +287,7 @@ function setDashboardDomain(domain, auditSource, callback) {
const fqdn = domains.fqdn(constants.ADMIN_LOCATION, domainObject);
async.series([
(done) => settings.setAdmin(domain, fqdn, done),
(done) => clients.addDefaultClients(settings.adminOrigin(), done)
], function (error) {
settings.setAdmin(domain, fqdn, function (error) {
if (error) return callback(error);
eventlog.add(eventlog.ACTION_DASHBOARD_DOMAIN_UPDATE, auditSource, { domain: domain, fqdn: fqdn });
+1 -4
View File
@@ -104,10 +104,7 @@ function clear(callback) {
gDatabase.hostname, gDatabase.username, gDatabase.password, gDatabase.name,
gDatabase.hostname, gDatabase.username, gDatabase.password, gDatabase.name);
async.series([
child_process.exec.bind(null, cmd),
require('./clients.js').addDefaultClients.bind(null, 'https://admin-localhost')
], callback);
child_process.exec(cmd, callback);
}
function beginTransaction(callback) {
+2 -2
View File
@@ -15,7 +15,6 @@ var appstore = require('./appstore.js'),
backups = require('./backups.js'),
BoxError = require('./boxerror.js'),
constants = require('./constants.js'),
clients = require('./clients.js'),
cloudron = require('./cloudron.js'),
debug = require('debug')('box:provision'),
domains = require('./domains.js'),
@@ -29,6 +28,7 @@ var appstore = require('./appstore.js'),
sysinfo = require('./sysinfo.js'),
users = require('./users.js'),
tld = require('tldjs'),
tokens = require('./tokens.js'),
_ = require('underscore');
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
@@ -165,7 +165,7 @@ function activate(username, password, email, displayName, ip, auditSource, callb
if (error && error.reason === BoxError.ALREADY_EXISTS) return callback(new BoxError(BoxError.CONFLICT, 'Already activated'));
if (error) return callback(error);
clients.addTokenByUserId('cid-webadmin', userObject.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) {
tokens.addTokenByUserId(tokens.ID_WEBADMIN, userObject.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) {
if (error) return callback(error);
eventlog.add(eventlog.ACTION_ACTIVATE, auditSource, { });
-124
View File
@@ -1,124 +0,0 @@
'use strict';
exports = module.exports = {
add: add,
get: get,
del: del,
getAll: getAll,
addToken: addToken,
getTokens: getTokens,
delTokens: delTokens,
delToken: delToken
};
var assert = require('assert'),
BoxError = require('../boxerror.js'),
clients = require('../clients.js'),
constants = require('../constants.js'),
HttpError = require('connect-lastmile').HttpError,
HttpSuccess = require('connect-lastmile').HttpSuccess,
validUrl = require('valid-url');
function add(req, res, next) {
var data = req.body;
if (!data) return next(new HttpError(400, 'Cannot parse data field'));
if (typeof data.appId !== 'string' || !data.appId) return next(new HttpError(400, 'appId is required'));
if (typeof data.redirectURI !== 'string' || !data.redirectURI) return next(new HttpError(400, 'redirectURI is required'));
if (typeof data.scope !== 'string' || !data.scope) return next(new HttpError(400, 'scope is required'));
if (!validUrl.isWebUri(data.redirectURI)) return next(new HttpError(400, 'redirectURI must be a valid uri'));
clients.add(data.appId, clients.TYPE_EXTERNAL, data.redirectURI, data.scope, function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(201, result));
});
}
function get(req, res, next) {
assert.strictEqual(typeof req.params.clientId, 'string');
clients.get(req.params.clientId, function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, result));
});
}
function del(req, res, next) {
assert.strictEqual(typeof req.params.clientId, 'string');
clients.get(req.params.clientId, function (error, result) {
if (error) return next(BoxError.toHttpError(error));
// we do not allow to use the REST API to delete addon clients
if (result.type !== clients.TYPE_EXTERNAL) return next(new HttpError(405, 'Deleting app addon clients is not allowed.'));
clients.del(req.params.clientId, function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(204, result));
});
});
}
function getAll(req, res, next) {
clients.getAll(function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, { clients: result }));
});
}
function addToken(req, res, next) {
assert.strictEqual(typeof req.params.clientId, 'string');
assert.strictEqual(typeof req.user, 'object');
assert.strictEqual(typeof req.body, 'object');
var data = req.body;
var expiresAt = data.expiresAt ? parseInt(data.expiresAt, 10) : Date.now() + constants.DEFAULT_TOKEN_EXPIRATION;
if (isNaN(expiresAt) || expiresAt <= Date.now()) return next(new HttpError(400, 'expiresAt must be a timestamp in the future'));
if ('name' in req.body && typeof req.body.name !== 'string') return next(new HttpError(400, 'name must be a string'));
clients.addTokenByUserId(req.params.clientId, req.user.id, expiresAt, { name: req.body.name || '' }, function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(201, { token: result }));
});
}
function getTokens(req, res, next) {
assert.strictEqual(typeof req.params.clientId, 'string');
assert.strictEqual(typeof req.user, 'object');
clients.getTokensByUserId(req.params.clientId, req.user.id, function (error, result) {
if (error) return next(BoxError.toHttpError(error));
result = result.map(clients.removeTokenPrivateFields);
next(new HttpSuccess(200, { tokens: result }));
});
}
function delTokens(req, res, next) {
assert.strictEqual(typeof req.params.clientId, 'string');
assert.strictEqual(typeof req.user, 'object');
clients.delTokensByUserId(req.params.clientId, req.user.id, function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(204));
});
}
function delToken(req, res, next) {
assert.strictEqual(typeof req.params.clientId, 'string');
assert.strictEqual(typeof req.params.tokenId, 'string');
assert.strictEqual(typeof req.user, 'object');
clients.delToken(req.params.clientId, req.params.tokenId, function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(204));
});
}
+4 -4
View File
@@ -27,7 +27,6 @@ let assert = require('assert'),
async = require('async'),
auditSource = require('../auditsource.js'),
BoxError = require('../boxerror.js'),
clients = require('../clients.js'),
cloudron = require('../cloudron.js'),
constants = require('../constants.js'),
debug = require('debug')('box:routes/cloudron'),
@@ -38,6 +37,7 @@ let assert = require('assert'),
sysinfo = require('../sysinfo.js'),
system = require('../system.js'),
tokendb = require('../tokendb.js'),
tokens = require('../tokens.js'),
updater = require('../updater.js'),
users = require('../users.js'),
updateChecker = require('../updatechecker.js');
@@ -48,7 +48,7 @@ function login(req, res, next) {
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null;
const auditSource = { authType: 'basic', ip: ip };
clients.addTokenByUserId(clients.ID_WEBADMIN, req.user.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) {
tokens.addTokenByUserId(tokens.ID_WEBADMIN, req.user.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) {
if (error) return next(new HttpError(500, error));
eventlog.add(eventlog.ACTION_USER_LOGIN, auditSource, { userId: req.user.id, user: users.removePrivateFields(req.user) });
@@ -104,7 +104,7 @@ function passwordReset(req, res, next) {
if (error && error.reason === BoxError.BAD_FIELD) return next(new HttpError(406, error.message));
if (error) return next(new HttpError(500, error));
clients.addTokenByUserId('cid-webadmin', userObject.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) {
tokens.addTokenByUserId(tokens.ID_WEBADMIN, userObject.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) {
if (error) return next(new HttpError(500, error));
next(new HttpSuccess(202, { accessToken: result.accessToken }));
@@ -141,7 +141,7 @@ function setupAccount(req, res, next) {
if (error && error.reason === BoxError.BAD_FIELD) return next(new HttpError(400, error.message));
if (error) return next(new HttpError(500, error));
clients.addTokenByUserId(clients.ID_WEBADMIN, userObject.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) {
tokens.addTokenByUserId(tokens.ID_WEBADMIN, userObject.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) {
if (error) return next(new HttpError(500, error));
next(new HttpSuccess(201, { accessToken: result.accessToken }));
-1
View File
@@ -6,7 +6,6 @@ exports = module.exports = {
apps: require('./apps.js'),
appstore: require('./appstore.js'),
backups: require('./backups.js'),
clients: require('./clients.js'),
cloudron: require('./cloudron.js'),
domains: require('./domains.js'),
eventlog: require('./eventlog.js'),
+2 -18
View File
@@ -7,7 +7,6 @@
let apps = require('../../apps.js'),
async = require('async'),
child_process = require('child_process'),
clients = require('../../clients.js'),
constants = require('../../constants.js'),
crypto = require('crypto'),
database = require('../../database.js'),
@@ -29,6 +28,7 @@ let apps = require('../../apps.js'),
settingsdb = require('../../settingsdb.js'),
superagent = require('superagent'),
tokendb = require('../../tokendb.js'),
tokens = require('../../tokens.js'),
url = require('url');
var SERVER_URL = 'http://localhost:' + constants.PORT;
@@ -213,7 +213,7 @@ function startBox(done) {
token_1 = hat(8 * 32);
// HACK to get a token for second user (passwords are generated and the user should have gotten a password setup link...)
tokendb.add({ id: 'tid-1', accessToken: token_1, identifier: user_1_id, clientId: clients.ID_SDK, expires: Date.now() + 1000000, scope: 'apps', name: '' }, callback);
tokendb.add({ id: 'tid-1', accessToken: token_1, identifier: user_1_id, clientId: tokens.ID_SDK, expires: Date.now() + 1000000, scope: 'apps', name: '' }, callback);
});
},
@@ -633,22 +633,6 @@ describe('App API', function () {
});
});
it('oauth addon config', function (done) {
var appContainer = docker.getContainer(appEntry.containerId);
appContainer.inspect(function (error, data) {
expect(error).to.not.be.ok();
clients.getByAppIdAndType(APP_ID, clients.TYPE_OAUTH, function (error, client) {
expect(error).to.not.be.ok();
expect(client.id.length).to.be(40); // cid- + 32 hex chars (128 bits) + 4 hyphens
expect(client.clientSecret.length).to.be(256); // 32 hex chars (8 * 256 bits)
expect(data.Config.Env).to.contain('CLOUDRON_OAUTH_CLIENT_ID=' + client.id);
expect(data.Config.Env).to.contain('CLOUDRON_OAUTH_CLIENT_SECRET=' + client.clientSecret);
done();
});
});
});
it('installation - app can populate addons', function (done) {
superagent.get(`http://localhost:${appEntry.httpPort}/populate_addons`).end(function (error, res) {
expect(!error).to.be.ok();
-537
View File
@@ -1,537 +0,0 @@
'use strict';
/* global it:false */
/* global describe:false */
/* global before:false */
/* global after:false */
var accesscontrol = require('../../accesscontrol.js'),
async = require('async'),
constants = require('../../constants.js'),
clients = require('../../clients.js'),
database = require('../../database.js'),
expect = require('expect.js'),
uuid = require('uuid'),
hat = require('../../hat.js'),
superagent = require('superagent'),
server = require('../../server.js');
var SERVER_URL = 'http://localhost:' + constants.PORT;
var USERNAME = 'superadmin', PASSWORD = 'Foobar?1337', EMAIL ='silly@me.com';
var token = null;
function setup(done) {
async.series([
server.start,
database._clear,
function (callback) {
superagent.post(SERVER_URL + '/api/v1/cloudron/activate')
.query({ setupToken: 'somesetuptoken' })
.send({ username: USERNAME, password: PASSWORD, email: EMAIL })
.end(function (error, result) {
expect(result).to.be.ok();
expect(result.statusCode).to.equal(201);
// stash token for further use
token = result.body.token;
callback();
});
}
], done);
}
function cleanup(done) {
database._clear(function (error) {
expect(error).to.not.be.ok();
server.stop(done);
});
}
describe('OAuth Clients API', function () {
describe('add', function () {
before(setup),
after(cleanup);
it('fails without token', function (done) {
superagent.post(SERVER_URL + '/api/v1/clients')
.send({ appId: 'someApp', redirectURI: 'http://foobar.com', scope: accesscontrol.SCOPE_PROFILE })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails without appId', function (done) {
superagent.post(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.send({ redirectURI: 'http://foobar.com', scope: accesscontrol.SCOPE_PROFILE })
.end(function (error, result) {
expect(result.statusCode).to.equal(400);
done();
});
});
it('fails with empty appId', function (done) {
superagent.post(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.send({ appId: '', redirectURI: 'http://foobar.com', scope: accesscontrol.SCOPE_PROFILE })
.end(function (error, result) {
expect(result.statusCode).to.equal(400);
done();
});
});
it('fails without scope', function (done) {
superagent.post(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.send({ appId: 'someApp', redirectURI: 'http://foobar.com' })
.end(function (error, result) {
expect(result.statusCode).to.equal(400);
done();
});
});
it('fails with empty scope', function (done) {
superagent.post(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.send({ appId: 'someApp', redirectURI: 'http://foobar.com', scope: '' })
.end(function (error, result) {
expect(result.statusCode).to.equal(400);
done();
});
});
it('fails without redirectURI', function (done) {
superagent.post(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.send({ appId: 'someApp', scope: accesscontrol.SCOPE_PROFILE })
.end(function (error, result) {
expect(result.statusCode).to.equal(400);
done();
});
});
it('fails with empty redirectURI', function (done) {
superagent.post(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.send({ appId: 'someApp', redirectURI: '', scope: accesscontrol.SCOPE_PROFILE })
.end(function (error, result) {
expect(result.statusCode).to.equal(400);
done();
});
});
it('fails with malformed redirectURI', function (done) {
superagent.post(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.send({ appId: 'someApp', redirectURI: 'foobar', scope: accesscontrol.SCOPE_PROFILE })
.end(function (error, result) {
expect(result.statusCode).to.equal(400);
done();
});
});
it('fails with invalid name', function (done) {
superagent.post(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.send({ appId: '$"$%^45asdfasdfadf.adf.', redirectURI: 'http://foobar.com', scope: accesscontrol.SCOPE_PROFILE })
.end(function (error, result) {
expect(result.statusCode).to.equal(400);
done();
});
});
it('succeeds with dash', function (done) {
superagent.post(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.send({ appId: 'fo-1234-bar', redirectURI: 'http://foobar.com', scope: accesscontrol.SCOPE_PROFILE })
.end(function (error, result) {
expect(result.statusCode).to.equal(201);
done();
});
});
it('succeeds', function (done) {
superagent.post(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.send({ appId: 'someApp', redirectURI: 'http://foobar.com', scope: accesscontrol.SCOPE_PROFILE })
.end(function (error, result) {
expect(result.statusCode).to.equal(201);
expect(result.body.id).to.be.a('string');
expect(result.body.appId).to.be.a('string');
expect(result.body.redirectURI).to.be.a('string');
expect(result.body.clientSecret).to.be.a('string');
expect(result.body.scope).to.be.a('string');
expect(result.body.type).to.equal(clients.TYPE_EXTERNAL);
done();
});
});
});
describe('get', function () {
var CLIENT_0 = {
id: '',
appId: 'someAppId-0',
redirectURI: 'http://some.callback0',
scope: accesscontrol.SCOPE_PROFILE
};
before(function (done) {
async.series([
setup,
function (callback) {
superagent.post(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.send({ appId: CLIENT_0.appId, redirectURI: CLIENT_0.redirectURI, scope: CLIENT_0.scope })
.end(function (error, result) {
expect(result.statusCode).to.equal(201);
CLIENT_0 = result.body;
callback();
});
}
], done);
});
after(cleanup);
it('fails without token', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients/' + CLIENT_0.id)
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails with unknown id', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients/' + CLIENT_0.id.toUpperCase())
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(404);
done();
});
});
it('succeeds', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients/' + CLIENT_0.id)
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(200);
expect(result.body).to.eql(CLIENT_0);
done();
});
});
});
describe('del', function () {
var CLIENT_0 = {
id: '',
appId: 'someAppId-0',
redirectURI: 'http://some.callback0',
scope: accesscontrol.SCOPE_PROFILE
};
var CLIENT_1 = {
id: '',
appId: 'someAppId-1',
redirectURI: 'http://some.callback1',
scope: accesscontrol.SCOPE_PROFILE,
type: clients.TYPE_OAUTH
};
before(function (done) {
async.series([
setup,
function (callback) {
superagent.post(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.send({ appId: CLIENT_0.appId, redirectURI: CLIENT_0.redirectURI, scope: CLIENT_0.scope })
.end(function (error, result) {
expect(result.statusCode).to.equal(201);
CLIENT_0 = result.body;
callback();
});
}
], done);
});
after(cleanup);
it('fails without token', function (done) {
superagent.del(SERVER_URL + '/api/v1/clients/' + CLIENT_0.id)
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails with unknown id', function (done) {
superagent.del(SERVER_URL + '/api/v1/clients/' + CLIENT_0.id.toUpperCase())
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(404);
done();
});
});
it('succeeds', function (done) {
superagent.del(SERVER_URL + '/api/v1/clients/' + CLIENT_0.id)
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(204);
superagent.get(SERVER_URL + '/api/v1/clients/' + CLIENT_0.id)
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(404);
done();
});
});
});
it('fails for cid-webadmin', function (done) {
superagent.del(SERVER_URL + '/api/v1/clients/cid-webadmin')
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(405);
superagent.get(SERVER_URL + '/api/v1/clients/cid-webadmin')
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(200);
done();
});
});
});
it('fails for addon auth client', function (done) {
clients.add(CLIENT_1.appId, CLIENT_1.type, CLIENT_1.redirectURI, CLIENT_1.scope, function (error, result) {
expect(error).to.equal(null);
CLIENT_1.id = result.id;
superagent.del(SERVER_URL + '/api/v1/clients/' + CLIENT_1.id)
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(405);
superagent.get(SERVER_URL + '/api/v1/clients/' + CLIENT_1.id)
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(200);
done();
});
});
});
});
});
});
describe('Clients', function () {
var USER_0 = {
userId: uuid.v4(),
username: 'someusername',
password: 'Strong#$%2345',
email: 'some@email.com',
admin: true,
salt: 'somesalt',
createdAt: (new Date()).toISOString(),
modifiedAt: (new Date()).toISOString(),
resetToken: hat(256)
};
function setup2(done) {
async.series([
setup,
function (callback) {
superagent.get(SERVER_URL + '/api/v1/profile')
.query({ access_token: token })
.end(function (error, result) {
expect(result).to.be.ok();
expect(result.statusCode).to.eql(200);
USER_0.id = result.body.id;
callback();
});
}
], done);
}
describe('get', function () {
before(setup2);
after(cleanup);
it('fails due to missing token', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients')
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails due to empty token', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients')
.query({ access_token: '' })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails due to wrong token', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients')
.query({ access_token: token.toUpperCase() })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('succeeds', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients')
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(200);
expect(result.body.clients.length).to.eql(3);
done();
});
});
});
describe('get tokens by client', function () {
before(setup2);
after(cleanup);
it('fails due to missing token', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients/cid-webadmin/tokens')
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails due to empty token', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients/cid-webadmin/tokens')
.query({ access_token: '' })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails due to wrong token', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients/cid-webadmin/tokens')
.query({ access_token: token.toUpperCase() })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails due to unkown client', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients/CID-WEBADMIN/tokens')
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(404);
done();
});
});
it('succeeds', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients/cid-webadmin/tokens')
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(200);
expect(result.body.tokens.length).to.eql(1);
expect(result.body.tokens[0].identifier).to.eql(USER_0.id);
done();
});
});
});
describe('delete tokens by client', function () {
before(setup2);
after(cleanup);
it('fails due to missing token', function (done) {
superagent.del(SERVER_URL + '/api/v1/clients/cid-webadmin/tokens')
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails due to empty token', function (done) {
superagent.del(SERVER_URL + '/api/v1/clients/cid-webadmin/tokens')
.query({ access_token: '' })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails due to wrong token', function (done) {
superagent.del(SERVER_URL + '/api/v1/clients/cid-webadmin/tokens')
.query({ access_token: token.toUpperCase() })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
it('fails due to unkown client', function (done) {
superagent.del(SERVER_URL + '/api/v1/clients/CID-WEBADMIN/tokens')
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(404);
done();
});
});
it('succeeds', function (done) {
superagent.get(SERVER_URL + '/api/v1/clients/cid-webadmin/tokens')
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(200);
expect(result.body.tokens.length).to.eql(1);
expect(result.body.tokens[0].identifier).to.eql(USER_0.id);
superagent.del(SERVER_URL + '/api/v1/clients/cid-webadmin/tokens')
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(204);
// further calls with this token should not work
superagent.get(SERVER_URL + '/api/v1/profile')
.query({ access_token: token })
.end(function (error, result) {
expect(result.statusCode).to.equal(401);
done();
});
});
});
});
});
});
+1 -12
View File
@@ -176,17 +176,6 @@ function initializeExpressSync() {
router.post('/api/v1/groups/:groupId', token, authorizeAdmin, routes.groups.update);
router.del ('/api/v1/groups/:groupId', token, authorizeAdmin, routes.groups.remove);
// client/token routes
router.get ('/api/v1/clients', token, authorizeAdmin, routes.clients.getAll);
router.post('/api/v1/clients', token, authorizeAdmin, routes.clients.add);
router.get ('/api/v1/clients/:clientId', token, authorizeAdmin, routes.clients.get);
router.post('/api/v1/clients/:clientId', token, authorizeAdmin, routes.clients.add);
router.del ('/api/v1/clients/:clientId', token, authorizeAdmin, routes.clients.del);
router.get ('/api/v1/clients/:clientId/tokens', token, authorizeAdmin, routes.clients.getTokens);
router.post('/api/v1/clients/:clientId/tokens', token, authorizeAdmin, routes.clients.addToken);
router.del ('/api/v1/clients/:clientId/tokens', token, authorizeAdmin, routes.clients.delTokens);
router.del ('/api/v1/clients/:clientId/tokens/:tokenId', token, authorizeAdmin, routes.clients.delToken);
// appstore and subscription routes
router.post('/api/v1/appstore/register_cloudron', token, authorizeAdmin, routes.appstore.registerCloudron);
router.get ('/api/v1/appstore/subscription', token, authorizeAdmin, routes.appstore.getSubscription);
@@ -231,7 +220,7 @@ function initializeExpressSync() {
router.get ('/api/v1/apps/:id/logs', token, authorizeAdmin, routes.apps.getLogs);
router.get ('/api/v1/apps/:id/exec', token, authorizeAdmin, routes.apps.exec);
// websocket cannot do bearer authentication
router.get ('/api/v1/apps/:id/execws', routes.accesscontrol.websocketAuth.bind(null, ROLE_ADMIN), routes.apps.execWebSocket);
router.get ('/api/v1/apps/:id/execws', routes.accesscontrol.websocketAuth.bind(null, accesscontrol.ROLE_ADMIN), routes.apps.execWebSocket);
router.post('/api/v1/apps/:id/clone', token, authorizeAdmin, routes.apps.cloneApp);
router.get ('/api/v1/apps/:id/download', token, authorizeAdmin, routes.apps.downloadFile);
router.post('/api/v1/apps/:id/upload', token, authorizeAdmin, multipart, routes.apps.uploadFile);
+4 -64
View File
@@ -10,71 +10,11 @@ var accesscontrol = require('../accesscontrol.js'),
expect = require('expect.js');
describe('access control', function () {
describe('canonicalScopeString', function () {
it('only * scope', function () {
expect(accesscontrol.canonicalScopeString('*')).to.be(accesscontrol.VALID_SCOPES.join(','));
});
it('identity for non-*', function () {
expect(accesscontrol.canonicalScopeString('foo,bar')).to.be('bar,foo'); // becomes sorted
});
describe('verifyToken', function () {
// FIXME
});
describe('intersectScopes', function () { // args: allowed, wanted
it('both are same', function () {
expect(accesscontrol.intersectScopes([ 'apps', 'clients' ], [ 'apps', 'clients' ])).to.eql([ 'apps', 'clients' ]);
});
it('some are different', function () {
expect(accesscontrol.intersectScopes([ 'apps' ], [ 'apps', 'clients' ])).to.eql(['apps']);
expect(accesscontrol.intersectScopes([ 'clients', 'domains', 'mail' ], [ 'mail' ])).to.eql(['mail']);
});
it('everything is different', function () {
expect(accesscontrol.intersectScopes(['cloudron', 'domains' ], ['apps','clients'])).to.eql([]);
});
it('subscopes', function () {
expect(accesscontrol.intersectScopes(['apps:read' ], ['apps'])).to.eql(['apps:read']);
expect(accesscontrol.intersectScopes(['apps:read','domains','profile'], ['apps','domains:manage','profile'])).to.eql(['apps:read','domains:manage','profile']);
expect(accesscontrol.intersectScopes(['apps:read','domains','profile'], ['apps','apps:read'])).to.eql(['apps:read']);
});
});
describe('validateScopeString', function () {
it('allows valid scopes', function () {
expect(accesscontrol.validateScopeString('apps')).to.be(null);
expect(accesscontrol.validateScopeString('apps,mail')).to.be(null);
expect(accesscontrol.validateScopeString('apps:read,mail')).to.be(null);
expect(accesscontrol.validateScopeString('apps,mail:write')).to.be(null);
});
it('disallows invalid scopes', function () {
expect(accesscontrol.validateScopeString('apps, mail')).to.be.an(Error);
expect(accesscontrol.validateScopeString('random')).to.be.an(Error);
expect(accesscontrol.validateScopeString('')).to.be.an(Error);
});
});
describe('hasScopes', function () {
it('succeeds if it contains the scope', function () {
expect(accesscontrol.hasScopes([ 'apps' ], [ 'apps' ])).to.be(null);
expect(accesscontrol.hasScopes([ 'apps', 'mail' ], [ 'mail' ])).to.be(null);
expect(accesscontrol.hasScopes([ 'clients', '*', 'apps', 'mail' ], [ 'mail' ])).to.be(null);
// subscope
expect(accesscontrol.hasScopes([ 'apps' ], [ 'apps:read' ])).to.be(null);
expect(accesscontrol.hasScopes([ 'apps:read' ], [ 'apps:read' ])).to.be(null);
expect(accesscontrol.hasScopes([ 'apps' , 'mail' ], [ 'apps:*' ])).to.be(null);
expect(accesscontrol.hasScopes([ '*' ], [ 'apps:read' ])).to.be(null);
});
it('fails if it does not contain the scope', function () {
expect(accesscontrol.hasScopes([ 'apps' ], [ 'mail' ])).to.be.an(Error);
expect(accesscontrol.hasScopes([ 'apps', 'mail' ], [ 'clients' ])).to.be.an(Error);
// subscope
expect(accesscontrol.hasScopes([ 'apps:write' ], [ 'apps:read' ])).to.be.an(Error);
});
describe('hasRole', function () {
// FIXME
});
});
+3 -113
View File
@@ -10,7 +10,6 @@ var appdb = require('../appdb.js'),
async = require('async'),
backupdb = require('../backupdb.js'),
BoxError = require('../boxerror.js'),
clientdb = require('../clientdb.js'),
database = require('../database'),
domaindb = require('../domaindb'),
eventlogdb = require('../eventlogdb.js'),
@@ -723,7 +722,7 @@ describe('database', function () {
identifier: '0',
clientId: 'clientid-0',
expires: Date.now() + 60 * 60000,
scope: 'clients'
scope: ''
};
var TOKEN_1 = {
id: 'tid-1',
@@ -732,7 +731,7 @@ describe('database', function () {
identifier: '1',
clientId: 'clientid-1',
expires: Number.MAX_SAFE_INTEGER,
scope: 'settings'
scope: ''
};
var TOKEN_2 = {
id: 'tid-2',
@@ -741,7 +740,7 @@ describe('database', function () {
identifier: '2',
clientId: 'clientid-2',
expires: Date.now(),
scope: 'apps'
scope: ''
};
it('add succeeds', function (done) {
@@ -1318,115 +1317,6 @@ describe('database', function () {
});
});
describe('client', function () {
var CLIENT_0 = {
id: 'cid-0',
appId: 'someappid_0',
type: 'typeisastring',
clientSecret: 'secret-0',
redirectURI: 'http://foo.bar',
scope: '*'
};
var CLIENT_1 = {
id: 'cid-1',
appId: 'someappid_1',
type: 'typeisastring',
clientSecret: 'secret-',
redirectURI: 'http://foo.bar',
scope: '*'
};
it('add succeeds', function (done) {
clientdb.add(CLIENT_0.id, CLIENT_0.appId, CLIENT_0.type, CLIENT_0.clientSecret, CLIENT_0.redirectURI, CLIENT_0.scope, function (error) {
expect(error).to.be(null);
clientdb.add(CLIENT_1.id, CLIENT_1.appId, CLIENT_0.type, CLIENT_1.clientSecret, CLIENT_1.redirectURI, CLIENT_1.scope, function (error) {
expect(error).to.be(null);
done();
});
});
});
it('add same client id fails', function (done) {
clientdb.add(CLIENT_0.id, CLIENT_0.appId, CLIENT_0.type, CLIENT_0.clientSecret, CLIENT_0.redirectURI, CLIENT_0.scope, function (error) {
expect(error).to.be.a(BoxError);
expect(error.reason).to.equal(BoxError.ALREADY_EXISTS);
done();
});
});
it('get succeeds', function (done) {
clientdb.get(CLIENT_0.id, function (error, result) {
expect(error).to.be(null);
expect(result).to.eql(CLIENT_0);
done();
});
});
it('getByAppId succeeds', function (done) {
clientdb.getByAppId(CLIENT_0.appId, function (error, result) {
expect(error).to.be(null);
expect(result).to.eql(CLIENT_0);
done();
});
});
it('getByAppIdAndType succeeds', function (done) {
clientdb.getByAppIdAndType(CLIENT_0.appId, CLIENT_0.type, function (error, result) {
expect(error).to.be(null);
expect(result).to.eql(CLIENT_0);
done();
});
});
it('getByAppId fails for unknown client id', function (done) {
clientdb.getByAppId(CLIENT_0.appId + CLIENT_0.appId, function (error, result) {
expect(error).to.be.a(BoxError);
expect(error.reason).to.equal(BoxError.NOT_FOUND);
expect(result).to.not.be.ok();
done();
});
});
it('getAll succeeds', function (done) {
clientdb.getAll(function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an(Array);
expect(result.length).to.equal(5); // three built-in clients
expect(result[3]).to.eql(CLIENT_0);
expect(result[4]).to.eql(CLIENT_1);
done();
});
});
it('delByAppIdAndType succeeds', function (done) {
clientdb.delByAppIdAndType(CLIENT_1.appId, CLIENT_1.type, function (error) {
expect(error).to.be(null);
clientdb.getByAppIdAndType(CLIENT_1.appId, CLIENT_1.type, function (error, result) {
expect(error).to.be.a(BoxError);
expect(error.reason).to.equal(BoxError.NOT_FOUND);
expect(result).to.not.be.ok();
done();
});
});
});
it('delByAppId succeeds', function (done) {
clientdb.delByAppId(CLIENT_0.appId, function (error) {
expect(error).to.be(null);
clientdb.getByAppId(CLIENT_0.appId, function (error, result) {
expect(error).to.be.a(BoxError);
expect(error.reason).to.equal(BoxError.NOT_FOUND);
expect(result).to.not.be.ok();
done();
});
});
});
});
describe('settings', function () {
it('can set value', function (done) {
settingsdb.set('somekey', 'somevalue', function (error) {
+58
View File
@@ -0,0 +1,58 @@
'use strict';
exports = module.exports = {
addTokenByUserId: addTokenByUserId,
// token client ids. we categorize them so we can have different restrictions based on the client
ID_WEBADMIN: 'cid-webadmin', // dashboard oauth
ID_SDK: 'cid-sdk', // created by user via dashboard
ID_CLI: 'cid-cli' // created via cli tool
};
let assert = require('assert'),
BoxError = require('./boxerror.js'),
hat = require('./hat.js'),
uuid = require('uuid'),
tokendb = require('./tokendb.js');
function validateTokenName(name) {
assert.strictEqual(typeof name, 'string');
if (name.length > 64) return new BoxError(BoxError.BAD_FIELD, 'name too long', { field: 'name' });
return null;
}
function addTokenByUserId(clientId, userId, expiresAt, options, callback) {
assert.strictEqual(typeof clientId, 'string');
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof expiresAt, 'number');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
const name = options.name || '';
let error = validateTokenName(name);
if (error) return callback(error);
const token = {
id: 'tid-' + uuid.v4(),
accessToken: hat(8 * 32),
identifier: userId,
clientId: clientId,
expires: expiresAt,
scope: 'unused',
name: name
};
tokendb.add(token, function (error) {
if (error) return callback(error);
callback(null, {
accessToken: token.accessToken,
tokenScopes: 'unused',
identifier: userId,
clientId: clientId,
expires: expiresAt
});
});
}