diff --git a/lib/components/state-resources/check-user-authorization/doc/example.json b/lib/components/state-resources/check-user-authorization/doc/example.json index c0ad36c..c095ed0 100644 --- a/lib/components/state-resources/check-user-authorization/doc/example.json +++ b/lib/components/state-resources/check-user-authorization/doc/example.json @@ -3,8 +3,11 @@ "Type": "Task", "Parameters": { "resourceType": "stateMachine", - "resourceName": "tymlyTest_admin_1_0", - "action": "create" + "resourceName": "tymlyTest_updatePost_1_0", + "action": "create", + "ctx": { + "userId": "molly" + } }, "Resource": "module:checkUserAuthorization", "End": true diff --git a/lib/components/state-resources/check-user-authorization/index.js b/lib/components/state-resources/check-user-authorization/index.js index f618c2c..60b3a20 100644 --- a/lib/components/state-resources/check-user-authorization/index.js +++ b/lib/components/state-resources/check-user-authorization/index.js @@ -4,10 +4,10 @@ class CheckUserAuthorization { } async run (event, context) { - const { resourceType, resourceName, action } = event + const { resourceType, resourceName, action, ctx } = event const { userId } = context - const authorized = await this.rbac.checkAuthorization(userId, null, resourceType, resourceName, action) + const authorized = await this.rbac.checkAuthorization(userId, ctx, resourceType, resourceName, action) context.sendTaskSuccess({ authorized }) } diff --git a/lib/components/state-resources/check-user-authorization/parameters-schema.j2119 b/lib/components/state-resources/check-user-authorization/parameters-schema.j2119 index 701764d..7cfc9bb 100644 --- a/lib/components/state-resources/check-user-authorization/parameters-schema.j2119 +++ b/lib/components/state-resources/check-user-authorization/parameters-schema.j2119 @@ -2,3 +2,4 @@ This document specifies a JSON object called a "User Authorization Check". A Permission Grant MUST have a string field named "resourceType". A Permission Grant MUST have a string field named "resourceName". A Permission Grant MUST have a string field named "action". +A Permission Grant MAY have an object field named "ctx". diff --git a/test/authorisation-tests.js b/test/authorisation-tests.js index 7cf530f..ec681bd 100644 --- a/test/authorisation-tests.js +++ b/test/authorisation-tests.js @@ -10,6 +10,7 @@ describe('Authorisation tests', function () { let tymlyService let rbac let rbacAdmin + let statebox describe('setup', () => { it('fire up Tymly', async () => { @@ -31,6 +32,7 @@ describe('Authorisation tests', function () { ) tymlyService = tymlyServices.tymly + statebox = tymlyServices.statebox rbac = tymlyServices.rbac rbac.debug() rbacAdmin = tymlyServices.rbacAdmin @@ -38,6 +40,23 @@ describe('Authorisation tests', function () { }) describe('checkRoleAuthorization', async () => { + const resourceType = 'stateMachine' + + const tests = [ + ['authorize boss to purge site', 'tymlyTest_purgeSite_1_0', 'boss', null, 'create', true], + ['authorize something $everyone can do', 'tymlyTest_readPost_1_0', null, null, 'create', true], + ['authorize something an $authenticated user can do', 'tymlyTest_createPost_1_0', 'john.smith', null, 'create', true], + ['deny something if user is not authenticated, when they need to be', 'tymlyTest_createPost_1_0', undefined, null, 'create', false], + ['authorize an $owner', 'tymlyTest_updatePost_1_0', 'molly', { userId: 'molly' }, 'create', true], + ['authorize something directly allowed via a role', 'tymlyTest_createPost_1_0', 'test_dev', null, 'cancel', true], + ['deny if no matching role', 'tymlyTest_createPost_1_0', 'spaceman', null, 'cancel', false], + ['deny if no appropriate role', 'tymlyTest_deletePost_1_0', 'test_dev', null, 'create', false], + ['authorize something because of role inheritance', 'tymlyTest_createPost_1_0', 'boss', null, 'cancel', true], + ['authorize something with resource and action wildcards', 'tymlyTest_purgeSite_1_0', 'test_admin', null, 'create', true], + ['authorize something with just an action wildcard', 'tymlyTest_purgeSite_1_0', 'reader', null, 'get', true], + ['fail to authorize if irrelevant action wildcard', 'tymlyTest_purgeSite_1_0', 'reader', null, 'create', false] + ] + it('set up roles', async () => { await rbacAdmin.ensureUserRoles('boss', 'tymlyTest_boss') await rbacAdmin.ensureUserRoles('test_dev', 'tymlyTest_developer') @@ -46,137 +65,38 @@ describe('Authorisation tests', function () { await rbacAdmin.ensureUserRoles('reader', 'tymlyTest_tymlyTestReadOnly') }) - it('authorize boss to purge site', async () => { - expect( - await rbac.checkAuthorization( - 'boss', // userId - null, // ctx - 'stateMachine', // resourceType - 'tymlyTest_purgeSite_1_0', // resourceName - 'create' // action - )).to.equal(true) - }) - - it('authorize something $everyone can do', async () => { - expect( - await rbac.checkAuthorization( - null, // userId - null, // ctx - 'stateMachine', // resourceType - 'tymlyTest_readPost_1_0', // resourceName - 'create' // action - )).to.equal(true) - }) - - it('authorize something an $authenticated user can do', async () => { - expect( - await rbac.checkAuthorization( - 'john.smith', // userId - null, // ctx - 'stateMachine', // resourceType - 'tymlyTest_createPost_1_0', // resourceName - 'create' // action - )).to.equal(true) - }) - - it('deny something if user is not authenticated, when they need to be', async () => { - expect( - await rbac.checkAuthorization( - undefined, // userId - null, // ctx - 'stateMachine', // resourceType - 'tymlyTest_createPost_1_0', // resourceName - 'create' // action - )).to.equal(false) - }) - - it('authorize an $owner', async () => { - expect( - await rbac.checkAuthorization( - 'molly', // userId - { userId: 'molly' }, // ctx - 'stateMachine', // resourceType - 'tymlyTest_updatePost_1_0', // resourceName - 'create' // action - )).to.equal(true) - }) - - it('authorize something directly allowed via a role', async () => { - expect( - await rbac.checkAuthorization( - 'test_dev', // userId - null, // ctx - 'stateMachine', // resourceType - 'tymlyTest_createPost_1_0', // resourceName - 'cancel' // action - )).to.equal(true) - }) - - it('deny if no matching role', async () => { - expect( - await rbac.checkAuthorization( - 'spaceman', // userId - null, // ctx - 'stateMachine', // resourceType - 'tymlyTest_createPost_1_0', // resourceName - 'cancel' // action - )).to.equal(false) - }) - - it('deny if no appropriate role', async () => { - expect( - await rbac.checkAuthorization( - 'test_dev', // userId - null, // ctx - 'stateMachine', // resourceType - 'tymlyTest_deletePost_1_0', // resourceName - 'create' // action - )).to.equal(false) - }) - - it('authorize something because of role inheritance', async () => { - expect( - await rbac.checkAuthorization( - 'boss', // userId - null, // ctx - 'stateMachine', // resourceType - 'tymlyTest_createPost_1_0', // resourceName - 'cancel' // action - )).to.equal(true) - }) - - it('authorize something with resource and action wildcards', async () => { - expect( - await rbac.checkAuthorization( - 'test_admin', // userId - null, // ctx - 'stateMachine', // resourceType - 'tymlyTest_purgeSite_1_0', // resourceName - 'create' // action - )).to.equal(true) - }) - - it('authorize something with just an action wildcard', async () => { - expect( - await rbac.checkAuthorization( - 'reader', // userId - null, // ctx - 'stateMachine', // resourceType - 'tymlyTest_purgeSite_1_0', // resourceName - 'get' // action - )).to.equal(true) - }) + for (const [title, resourceName, userId, ctx, action, expected] of tests) { + it(title, async () => { + const actual = await rbac.checkAuthorization( + userId, + ctx, + resourceType, + resourceName, + action + ) + + expect(actual).to.equal(expected) + }) + + it(`${title} (via state machine)`, async () => { + const execDesc = await statebox.startExecution( + { + resourceType, + resourceName, + action, + ctx + }, + 'tymlyTest_checkUserAuthorization_1_0', + { + sendResponse: 'COMPLETE', + userId + } + ) - it('fail to authorize if irrelevant action wildcard', async () => { - expect( - await rbac.checkAuthorization( - 'reader', // userId - null, // ctx - 'stateMachine', // resourceType - 'tymlyTest_purgeSite_1_0', // resourceName - 'create' // action - )).to.equal(false) - }) + expect(execDesc.status).to.eql('SUCCEEDED') + expect(execDesc.ctx.authorized).to.eql(expected) + }) + } }) describe('shutdown', () => { diff --git a/test/fixtures/blueprints/website-blueprint/state-machines/check-user-authorization.json b/test/fixtures/blueprints/website-blueprint/state-machines/check-user-authorization.json new file mode 100644 index 0000000..6e940e1 --- /dev/null +++ b/test/fixtures/blueprints/website-blueprint/state-machines/check-user-authorization.json @@ -0,0 +1,20 @@ +{ + "Comment": "Check user authorization", + "version": "1.0", + "StartAt": "Success", + "States": { + "Success": { + "Type": "Task", + "Resource": "module:checkUserAuthorization", + "End": true + } + }, + "restrictions": [ + { + "roleId": "$everyone", + "allows": [ + "*" + ] + } + ] +}