diff --git a/src/accesscontrol.js b/src/accesscontrol.js index b89bb39a3..58b093087 100644 --- a/src/accesscontrol.js +++ b/src/accesscontrol.js @@ -82,7 +82,11 @@ function normalizeScope(maxScope, allowedScope) { assert.strictEqual(typeof maxScope, 'string'); assert.strictEqual(typeof allowedScope, 'string'); - if (maxScope === exports.SCOPE_ANY) return allowedScope; + const maxScopes = maxScope.split(','); + const allowedScopes = allowedScope.split(','); - return _.intersection(maxScope.split(','), allowedScope.split(',')).join(','); + if (maxScopes.indexOf(exports.SCOPE_ANY) !== -1) return allowedScope; + if (allowedScopes.indexOf(exports.SCOPE_ANY) !== -1) return maxScope; + + return _.intersection(maxScopes, allowedScopes).join(','); } diff --git a/src/auth.js b/src/auth.js index 518041d3e..330c60562 100644 --- a/src/auth.js +++ b/src/auth.js @@ -7,7 +7,8 @@ exports = module.exports = { accessTokenAuth: accessTokenAuth }; -var assert = require('assert'), +var accesscontrol = require('./accesscontrol.js'), + assert = require('assert'), BasicStrategy = require('passport-http').BasicStrategy, BearerStrategy = require('passport-http-bearer').Strategy, clients = require('./clients'), @@ -20,8 +21,7 @@ var assert = require('assert'), passport = require('passport'), tokendb = require('./tokendb'), users = require('./users.js'), - UsersError = users.UsersError, - _ = require('underscore'); + UsersError = users.UsersError; function initialize(callback) { assert.strictEqual(typeof callback, 'function'); @@ -111,14 +111,15 @@ function accessTokenAuth(accessToken, callback) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(null, false); if (error) return callback(error); - // scopes here can define what capabilities that token carries - // passport put the 'info' object into req.authInfo, where we can further validate the scopes - var info = { scope: token.scope }; - users.get(token.identifier, function (error, user) { if (error && error.reason === UsersError.NOT_FOUND) return callback(null, false); if (error) return callback(error); + // scopes here can define what capabilities that token carries + // passport put the 'info' object into req.authInfo, where we can further validate the scopes + var scope = accesscontrol.normalizeScope(user.scope, token.scope); + var info = { scope: scope }; + callback(null, user, info); }); }); diff --git a/src/routes/cloudron.js b/src/routes/cloudron.js index a20de70e1..1e12859ab 100644 --- a/src/routes/cloudron.js +++ b/src/routes/cloudron.js @@ -44,10 +44,6 @@ function getConfig(req, res, next) { cloudron.getConfig(function (error, cloudronConfig) { if (error) return next(new HttpError(500, error)); - if (!req.user.admin) { - cloudronConfig = _.pick(cloudronConfig, 'apiServerOrigin', 'webServerOrigin', 'fqdn', 'adminFqdn', 'version', 'progress', 'isDemo', 'cloudronName', 'provider'); - } - next(new HttpSuccess(200, cloudronConfig)); }); } diff --git a/src/routes/index.js b/src/routes/index.js index ea54dc1d4..2bb360fe8 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -19,5 +19,6 @@ exports = module.exports = { sysadmin: require('./sysadmin.js'), settings: require('./settings.js'), ssh: require('./ssh.js'), + user: require('./user.js'), users: require('./users.js') }; diff --git a/src/routes/test/cloudron-test.js b/src/routes/test/cloudron-test.js index 28b842cb3..d88a41b9f 100644 --- a/src/routes/test/cloudron-test.js +++ b/src/routes/test/cloudron-test.js @@ -203,25 +203,11 @@ describe('Cloudron', function () { }); }); - it('succeeds (non-admin)', function (done) { + it('fails (non-admin)', function (done) { superagent.get(SERVER_URL + '/api/v1/cloudron/config') .query({ access_token: token_1 }) .end(function (error, result) { - expect(result.statusCode).to.equal(200); - - expect(result.body.apiServerOrigin).to.eql('http://localhost:6060'); - expect(result.body.webServerOrigin).to.eql(null); - expect(result.body.adminFqdn).to.eql(config.adminFqdn()); - expect(result.body.progress).to.be.an('object'); - expect(result.body.version).to.eql(config.version()); - expect(result.body.cloudronName).to.be.a('string'); - expect(result.body.provider).to.be.a('string'); - - expect(result.body.update).to.be(undefined); - expect(result.body.size).to.be(undefined); - expect(result.body.region).to.be(undefined); - expect(result.body.memory).to.be(undefined); - + expect(result.statusCode).to.equal(403); done(); }); }); diff --git a/src/routes/test/user-test.js b/src/routes/test/user-test.js new file mode 100644 index 000000000..f0ea3a50b --- /dev/null +++ b/src/routes/test/user-test.js @@ -0,0 +1,121 @@ +'use strict'; + +/* global it:false */ +/* global describe:false */ +/* global before:false */ +/* global after:false */ + +var accesscontrol = require('../../accesscontrol.js'), + async = require('async'), + config = require('../../config.js'), + database = require('../../database.js'), + expect = require('expect.js'), + nock = require('nock'), + superagent = require('superagent'), + server = require('../../server.js'), + settings = require('../../settings.js'), + settingsdb = require('../../settingsdb.js'), + tokendb = require('../../tokendb.js'); + +var SERVER_URL = 'http://localhost:' + config.get('port'); + +var USERNAME = 'superadmin', PASSWORD = 'Foobar?1337', EMAIL ='silly@me.com'; +var token = null; // authentication token +var USERNAME_1 = 'userTheFirst', EMAIL_1 = 'taO@zen.mac', userId_1, token_1; + +function setup(done) { + nock.cleanAll(); + config._reset(); + config.setFqdn('example-cloudron-test.com'); + config.setAdminFqdn('my.example-cloudron-test.com'); + + async.series([ + server.start.bind(server), + database._clear, + settings.setBackupConfig.bind(null, { provider: 'filesystem', backupFolder: '/tmp', format: 'tgz' }), + settingsdb.set.bind(null, settings.APPSTORE_CONFIG_KEY, JSON.stringify({ userId: 'USER_ID', cloudronId: 'CLOUDRON_ID', token: 'ACCESS_TOKEN' })) + ], done); +} + +function cleanup(done) { + database._clear(function (error) { + expect(error).to.not.be.ok(); + + config._reset(); + + server.stop(done); + }); +} + +describe('User test', function () { + describe('get config', function () { + before(function (done) { + async.series([ + setup, + + 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(); + + // stash token for further use + token = result.body.token; + + callback(); + }); + }, + + function (callback) { + superagent.post(SERVER_URL + '/api/v1/users') + .query({ access_token: token }) + .send({ username: USERNAME_1, email: EMAIL_1, invite: false }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(201); + + token_1 = tokendb.generateToken(); + userId_1 = result.body.id; + + // HACK to get a token for second user (passwords are generated and the user should have gotten a password setup link...) + tokendb.add(token_1, userId_1, 'test-client-id', Date.now() + 100000, accesscontrol.SCOPE_ANY, callback); + }); + } + ], done); + }); + + after(cleanup); + + it('cannot get without token', function (done) { + superagent.get(SERVER_URL + '/api/v1/user/cloudron_config') + .end(function (error, result) { + expect(result.statusCode).to.equal(401); + done(); + }); + }); + + it('succeeds', function (done) { + superagent.get(SERVER_URL + '/api/v1/user/cloudron_config') + .query({ access_token: token_1 }) + .end(function (error, result) { + expect(result.statusCode).to.equal(200); + + expect(result.body.apiServerOrigin).to.eql('http://localhost:6060'); + expect(result.body.webServerOrigin).to.eql(null); + expect(result.body.adminFqdn).to.eql(config.adminFqdn()); + expect(result.body.progress).to.be.an('object'); + expect(result.body.version).to.eql(config.version()); + expect(result.body.cloudronName).to.be.a('string'); + expect(result.body.provider).to.be.a('string'); + + expect(result.body.update).to.be(undefined); + expect(result.body.size).to.be(undefined); + expect(result.body.region).to.be(undefined); + expect(result.body.memory).to.be(undefined); + + done(); + }); + }); + }); +}); diff --git a/src/routes/user.js b/src/routes/user.js new file mode 100644 index 000000000..c35627609 --- /dev/null +++ b/src/routes/user.js @@ -0,0 +1,20 @@ +'use strict'; + +exports = module.exports = { + getCloudronConfig: getCloudronConfig +}; + +var cloudron = require('../cloudron.js'), + HttpError = require('connect-lastmile').HttpError, + HttpSuccess = require('connect-lastmile').HttpSuccess, + _ = require('underscore'); + +function getCloudronConfig(req, res, next) { + cloudron.getConfig(function (error, cloudronConfig) { + if (error) return next(new HttpError(500, error)); + + var result = _.pick(cloudronConfig, 'apiServerOrigin', 'webServerOrigin', 'fqdn', 'adminFqdn', 'version', 'progress', 'isDemo', 'cloudronName', 'provider'); + + next(new HttpSuccess(200, result)); + }); +} diff --git a/src/server.js b/src/server.js index 1d0d8bd44..4d297b56e 100644 --- a/src/server.js +++ b/src/server.js @@ -131,6 +131,7 @@ function initializeExpressSync() { // working off the user behind the provided token router.get ('/api/v1/user/apps', profileScope, routes.apps.getAllByUser); + router.get ('/api/v1/user/cloudron_config', profileScope, routes.user.getCloudronConfig); router.get ('/api/v1/user/profile', profileScope, routes.profile.get); router.post('/api/v1/user/profile', profileScope, routes.profile.update); router.post('/api/v1/user/profile/password', profileScope, routes.users.verifyPassword, routes.profile.changePassword);