diff --git a/src/routes/accesscontrol.js b/src/routes/accesscontrol.js index 58031105e..259225e74 100644 --- a/src/routes/accesscontrol.js +++ b/src/routes/accesscontrol.js @@ -94,6 +94,7 @@ function authorize(requiredRole) { return function (req, res, next) { assert.strictEqual(typeof req.user, 'object'); + assert.strictEqual(typeof req.token, 'object'); if (users.compareRoles(req.user.role, requiredRole) < 0) return next(new HttpError(403, `role '${requiredRole}' is required but user has only '${req.user.role}'`)); if (!tokens.hasScope(req.token, req.method, req.path)) return next(new HttpError(403, 'access token does not have this scope')); @@ -106,6 +107,7 @@ async function authorizeOperator(req, res, next) { assert.strictEqual(typeof req.params.id, 'string'); assert.strictEqual(typeof req.user, 'object'); assert.strictEqual(typeof req.app, 'object'); + assert.strictEqual(typeof req.token, 'object'); if (!tokens.hasScope(req.token, req.method, req.path)) return next(new HttpError(403, 'access token does not have this scope')); if (apps.isOperator(req.app, req.user)) return next(); diff --git a/src/routes/test/tokens-test.js b/src/routes/test/tokens-test.js index 6114d87b1..033795dbe 100644 --- a/src/routes/test/tokens-test.js +++ b/src/routes/test/tokens-test.js @@ -64,10 +64,10 @@ describe('Tokens API', function () { expect(tokenIds).to.contain(readOnlyToken.id); }); - it('cannot create applink with read only token', async function () { - const response = await superagent.post(`${serverUrl}/api/v1/applinks`) + it('cannot create token with read only token', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/tokens`) .query({ access_token: readOnlyToken.accessToken }) - .send({ upstreamUri: 'https://cloudron.io' }) + .send({ name: 'somename' }) .ok(() => true); expect(response.status).to.equal(403); diff --git a/src/server.js b/src/server.js index 6be8f6ea8..371218c9c 100644 --- a/src/server.js +++ b/src/server.js @@ -79,14 +79,17 @@ function initializeExpressSync() { const multipart = middleware.multipart({ maxFieldsSize: FIELD_LIMIT, limit: FILE_SIZE_LIMIT, timeout: FILE_TIMEOUT }); - // to keep routes code short + // authentication const password = routes.accesscontrol.passwordAuth; const token = routes.accesscontrol.tokenAuth; + + // authorization const authorizeOwner = routes.accesscontrol.authorize(users.ROLE_OWNER); const authorizeAdmin = routes.accesscontrol.authorize(users.ROLE_ADMIN); const authorizeOperator = routes.accesscontrol.authorizeOperator; const authorizeUserManager = routes.accesscontrol.authorize(users.ROLE_USER_MANAGER); const authorizeMailManager = routes.accesscontrol.authorize(users.ROLE_MAIL_MANAGER); + const authorizeUser = routes.accesscontrol.authorize(users.ROLE_USER); // public routes router.post('/api/v1/cloudron/setup', json, routes.provision.setupTokenAuth, routes.provision.providerTokenAuth, routes.provision.setup); // only available until no-domain @@ -121,8 +124,8 @@ function initializeExpressSync() { router.get ('/api/v1/cloudron/eventlog', token, authorizeAdmin, routes.eventlog.list); router.get ('/api/v1/cloudron/eventlog/:eventId', token, authorizeAdmin, routes.eventlog.get); router.post('/api/v1/cloudron/sync_external_ldap', json, token, authorizeAdmin, routes.cloudron.syncExternalLdap); - router.get ('/api/v1/cloudron/server_ipv4', token, authorizeAdmin, routes.cloudron.getServerIpv4); - router.get ('/api/v1/cloudron/server_ipv6', token, authorizeAdmin, routes.cloudron.getServerIpv6); + router.get ('/api/v1/cloudron/server_ipv4', token, authorizeAdmin, routes.cloudron.getServerIpv4); + router.get ('/api/v1/cloudron/server_ipv6', token, authorizeAdmin, routes.cloudron.getServerIpv6); // task routes router.get ('/api/v1/tasks', token, authorizeAdmin, routes.tasks.list); @@ -144,31 +147,31 @@ function initializeExpressSync() { router.post('/api/v1/backups/:backupId', json, token, authorizeAdmin, routes.backups.update); // config route (for dashboard). can return some private configuration unlike status - router.get ('/api/v1/config', token, routes.cloudron.getConfig); + router.get ('/api/v1/config', token, authorizeUser, routes.cloudron.getConfig); // working off the user behind the provided token - router.get ('/api/v1/profile', token, routes.profile.get); - router.post('/api/v1/profile', json, token, routes.profile.authorize, routes.profile.update); - router.get ('/api/v1/profile/avatar/:identifier', routes.profile.getAvatar); // this is not scoped so it can used directly in img tag - router.post('/api/v1/profile/avatar', json, token, (req, res, next) => { return typeof req.body.avatar === 'string' ? next() : multipart(req, res, next); }, routes.profile.setAvatar); // avatar is not exposed in LDAP. so it's personal and not locked - router.get ('/api/v1/profile/backgroundImage', token, routes.profile.getBackgroundImage); - router.post('/api/v1/profile/backgroundImage', token, multipart, routes.profile.setBackgroundImage); // backgroundImage is not exposed in LDAP. so it's personal and not locked - router.post('/api/v1/profile/password', json, token, routes.users.verifyPassword, routes.profile.setPassword); - router.post('/api/v1/profile/twofactorauthentication_secret', json, token, routes.profile.setTwoFactorAuthenticationSecret); - router.post('/api/v1/profile/twofactorauthentication_enable', json, token, routes.profile.enableTwoFactorAuthentication); - router.post('/api/v1/profile/twofactorauthentication_disable', json, token, routes.users.verifyPassword, routes.profile.disableTwoFactorAuthentication); + router.get ('/api/v1/profile', token, authorizeUser, routes.profile.get); + router.post('/api/v1/profile', json, token, authorizeUser, routes.profile.authorize, routes.profile.update); + router.get ('/api/v1/profile/avatar/:identifier', routes.profile.getAvatar); // this is not scoped so it can used directly in img tag + router.post('/api/v1/profile/avatar', json, token, authorizeUser, (req, res, next) => { return typeof req.body.avatar === 'string' ? next() : multipart(req, res, next); }, routes.profile.setAvatar); // avatar is not exposed in LDAP. so it's personal and not locked + router.get ('/api/v1/profile/backgroundImage', token, authorizeUser, routes.profile.getBackgroundImage); + router.post('/api/v1/profile/backgroundImage', token, authorizeUser, multipart, routes.profile.setBackgroundImage); // backgroundImage is not exposed in LDAP. so it's personal and not locked + router.post('/api/v1/profile/password', json, token, authorizeUser, routes.users.verifyPassword, routes.profile.setPassword); + router.post('/api/v1/profile/twofactorauthentication_secret', json, token, authorizeUser, routes.profile.setTwoFactorAuthenticationSecret); + router.post('/api/v1/profile/twofactorauthentication_enable', json, token, authorizeUser, routes.profile.enableTwoFactorAuthentication); + router.post('/api/v1/profile/twofactorauthentication_disable', json, token, authorizeUser, routes.users.verifyPassword, routes.profile.disableTwoFactorAuthentication); // app password routes - router.get ('/api/v1/app_passwords', token, routes.appPasswords.list); - router.post('/api/v1/app_passwords', json, token, routes.appPasswords.add); - router.get ('/api/v1/app_passwords/:id', token, routes.appPasswords.get); - router.del ('/api/v1/app_passwords/:id', token, routes.appPasswords.del); + router.get ('/api/v1/app_passwords', token, authorizeUser, routes.appPasswords.list); + router.post('/api/v1/app_passwords', json, token, authorizeUser, routes.appPasswords.add); + router.get ('/api/v1/app_passwords/:id', token, authorizeUser, routes.appPasswords.get); + router.del ('/api/v1/app_passwords/:id', token, authorizeUser, routes.appPasswords.del); // access tokens - router.get ('/api/v1/tokens', token, routes.tokens.list); - router.post('/api/v1/tokens', json, token, routes.tokens.add); - router.get ('/api/v1/tokens/:id', token, routes.tokens.verifyOwnership, routes.tokens.get); - router.del ('/api/v1/tokens/:id', token, routes.tokens.verifyOwnership, routes.tokens.del); + router.get ('/api/v1/tokens', token, authorizeUser, routes.tokens.list); + router.post('/api/v1/tokens', json, token, authorizeUser, routes.tokens.add); + router.get ('/api/v1/tokens/:id', token, authorizeUser, routes.tokens.verifyOwnership, routes.tokens.get); + router.del ('/api/v1/tokens/:id', token, authorizeUser, routes.tokens.verifyOwnership, routes.tokens.del); // user routes router.get ('/api/v1/users', token, authorizeUserManager, routes.users.list); @@ -179,12 +182,12 @@ function initializeExpressSync() { router.post('/api/v1/users/:userId/password', json, token, authorizeUserManager, routes.users.load, routes.users.setPassword); router.post('/api/v1/users/:userId/ghost', json, token, authorizeAdmin, routes.users.load, routes.users.setGhost); router.put ('/api/v1/users/:userId/groups', json, token, authorizeUserManager, routes.users.load, routes.users.setGroups); - router.post('/api/v1/users/:userId/twofactorauthentication_disable', json, token, authorizeUserManager, routes.users.load, routes.users.disableTwoFactorAuthentication); - router.get ('/api/v1/users/:userId/password_reset_link', json, token, authorizeUserManager, routes.users.load, routes.users.getPasswordResetLink); - router.post('/api/v1/users/:userId/send_password_reset_email', json, token, authorizeUserManager, routes.users.load, routes.users.sendPasswordResetEmail); - router.get ('/api/v1/users/:userId/invite_link', json, token, authorizeUserManager, routes.users.load, routes.users.getInviteLink); - router.post('/api/v1/users/:userId/send_invite_email', json, token, authorizeUserManager, routes.users.load, routes.users.sendInviteEmail); router.post('/api/v1/users/:userId/make_local', json, token, authorizeUserManager, routes.users.load, routes.users.makeLocal); + router.get ('/api/v1/users/:userId/password_reset_link', json, token, authorizeUserManager, routes.users.load, routes.users.getPasswordResetLink); + router.post('/api/v1/users/:userId/send_password_reset_email', json, token, authorizeUserManager, routes.users.load, routes.users.sendPasswordResetEmail); + router.get ('/api/v1/users/:userId/invite_link', json, token, authorizeUserManager, routes.users.load, routes.users.getInviteLink); + router.post('/api/v1/users/:userId/send_invite_email', json, token, authorizeUserManager, routes.users.load, routes.users.sendInviteEmail); + router.post('/api/v1/users/:userId/twofactorauthentication_disable', json, token, authorizeUserManager, routes.users.load, routes.users.disableTwoFactorAuthentication); // Group management router.get ('/api/v1/groups', token, authorizeUserManager, routes.groups.list); @@ -197,19 +200,19 @@ function initializeExpressSync() { // appstore and subscription routes router.post('/api/v1/appstore/register_cloudron', json, token, authorizeOwner, routes.appstore.registerCloudron); router.get ('/api/v1/appstore/web_token', json, token, authorizeOwner, routes.appstore.getWebToken); - router.get ('/api/v1/appstore/subscription', token, routes.appstore.getSubscription); // for all users + router.get ('/api/v1/appstore/subscription', token, authorizeUser, routes.appstore.getSubscription); // for all users router.get ('/api/v1/appstore/apps', token, authorizeAdmin, routes.appstore.getApps); router.get ('/api/v1/appstore/apps/:appstoreId', token, authorizeAdmin, routes.appstore.getApp); router.get ('/api/v1/appstore/apps/:appstoreId/versions/:versionId', token, authorizeAdmin, routes.appstore.getAppVersion); // app routes - router.post('/api/v1/apps/install', json, token, authorizeAdmin, routes.apps.install); - router.get ('/api/v1/apps', token, routes.apps.listByUser); + router.post('/api/v1/apps/install', json, token, authorizeAdmin, routes.apps.install); + router.get ('/api/v1/apps', token, authorizeUser, routes.apps.listByUser); router.get ('/api/v1/apps/:id', token, routes.apps.load, authorizeOperator, routes.apps.getApp); - router.get ('/api/v1/apps/:id/icon', token, routes.apps.load, routes.apps.getAppIcon); - router.post('/api/v1/apps/:id/uninstall', json, token, authorizeAdmin, routes.apps.load, routes.apps.uninstall); - router.post('/api/v1/apps/:id/configure/access_restriction', json, token, authorizeAdmin, routes.apps.load, routes.apps.setAccessRestriction); - router.post('/api/v1/apps/:id/configure/operators', json, token, authorizeAdmin, routes.apps.load, routes.apps.setOperators); + router.get ('/api/v1/apps/:id/icon', token, routes.apps.load, authorizeUser, routes.apps.getAppIcon); + router.post('/api/v1/apps/:id/uninstall', json, token, routes.apps.load, authorizeAdmin, routes.apps.uninstall); + router.post('/api/v1/apps/:id/configure/access_restriction', json, token, routes.apps.load, authorizeAdmin, routes.apps.setAccessRestriction); + router.post('/api/v1/apps/:id/configure/operators', json, token, routes.apps.load, authorizeAdmin, routes.apps.setOperators); router.post('/api/v1/apps/:id/configure/label', json, token, routes.apps.load, authorizeOperator, routes.apps.setLabel); router.post('/api/v1/apps/:id/configure/tags', json, token, routes.apps.load, authorizeOperator, routes.apps.setTags); router.post('/api/v1/apps/:id/configure/icon', json, token, routes.apps.load, authorizeOperator, routes.apps.setIcon); @@ -255,15 +258,15 @@ function initializeExpressSync() { router.get ('/api/v1/apps/:id/exec/:execId', token, routes.apps.load, authorizeOperator, routes.apps.getExec); // websocket cannot do bearer authentication - router.get ('/api/v1/apps/:id/exec/:execId/startws', token, routes.apps.load, routes.accesscontrol.authorizeOperator, routes.apps.startExecWebSocket); + router.get ('/api/v1/apps/:id/exec/:execId/startws', token, routes.apps.load, authorizeOperator, routes.apps.startExecWebSocket); // app links in dashboard - router.get ('/api/v1/applinks', token, routes.applinks.listByUser); + router.get ('/api/v1/applinks', token, authorizeUser, routes.applinks.listByUser); router.post('/api/v1/applinks', json, token, authorizeAdmin, routes.applinks.add); router.get ('/api/v1/applinks/:id', token, authorizeAdmin, routes.applinks.get); router.post('/api/v1/applinks/:id', json, token, authorizeAdmin, routes.applinks.update); router.del ('/api/v1/applinks/:id', token, authorizeAdmin, routes.applinks.remove); - router.get ('/api/v1/applinks/:id/icon', token, routes.applinks.getIcon); + router.get ('/api/v1/applinks/:id/icon', token, authorizeUser, routes.applinks.getIcon); // branding routes router.get ('/api/v1/branding/:setting', token, authorizeOwner, routes.branding.get); @@ -331,10 +334,10 @@ function initializeExpressSync() { // domain routes router.post('/api/v1/domains', json, token, authorizeAdmin, routes.domains.add); - router.get ('/api/v1/domains', token, routes.domains.list); + router.get ('/api/v1/domains', token, authorizeUser, routes.domains.list); router.get ('/api/v1/domains/:domain', token, authorizeAdmin, routes.domains.get); // this is manage scope because it returns non-restricted fields - router.post('/api/v1/domains/:domain/config', json, token, authorizeAdmin, routes.domains.setConfig); - router.post('/api/v1/domains/:domain/wellknown', json, token, authorizeAdmin, routes.domains.setWellKnown); + router.post('/api/v1/domains/:domain/config', json, token, authorizeAdmin, routes.domains.setConfig); + router.post('/api/v1/domains/:domain/wellknown', json, token, authorizeAdmin, routes.domains.setWellKnown); router.del ('/api/v1/domains/:domain', token, authorizeAdmin, routes.domains.del); router.get ('/api/v1/domains/:domain/dns_check', token, authorizeAdmin, routes.domains.checkDnsRecords);