UNPKG

@getanthill/datastore

Version:

Event-Sourced Datastore

1,581 lines (1,532 loc) 40.6 kB
import Ajv from 'ajv'; import Authz from './authz'; import setup from '../../test/setup'; import { AUTHORIZATION_VERB_ALLOW, AUTHORIZATION_VERB_DENY, PolicyVerbs, PolicyVerb, } from '../typings/authorizations'; describe('Authz', () => { beforeEach(async () => { jest.restoreAllMocks(); }); describe('#getScopes', () => { it('returns all possible scopes', () => { expect(Authz.getScopes(['a', 'b', 'c'])).toEqual([ ['a'], ['a', 'b'], ['a', 'b', 'c'], ]); }); it('returns empty array on empty scope', () => { expect(Authz.getScopes([])).toEqual([]); }); it('returns single array of scopes on single level scope', () => { expect(Authz.getScopes(['a'])).toEqual([['a']]); }); }); describe('#areRulesValidated', () => { it('returns true if all scopes are valid', () => { expect( Authz.areRulesValidated([ { action: { is_valid: true, errors: [], }, subject: { is_valid: true, errors: [], }, object: { is_valid: true, errors: [], }, context: { is_valid: true, errors: [], }, }, ]), ).toEqual(true); }); it('returns false if action scope is invalid', () => { expect( Authz.areRulesValidated([ { action: { is_valid: false, errors: [], }, subject: { is_valid: true, errors: [], }, object: { is_valid: true, errors: [], }, context: { is_valid: true, errors: [], }, }, ]), ).toEqual(false); }); it('returns false if subject scope is invalid', () => { expect( Authz.areRulesValidated([ { action: { is_valid: true, errors: [], }, subject: { is_valid: false, errors: [], }, object: { is_valid: true, errors: [], }, context: { is_valid: true, errors: [], }, }, ]), ).toEqual(false); }); it('returns false if object scope is invalid', () => { expect( Authz.areRulesValidated([ { action: { is_valid: true, errors: [], }, subject: { is_valid: true, errors: [], }, object: { is_valid: false, errors: [], }, context: { is_valid: true, errors: [], }, }, ]), ).toEqual(false); }); it('returns false if context scope is invalid', () => { expect( Authz.areRulesValidated([ { action: { is_valid: true, errors: [], }, subject: { is_valid: true, errors: [], }, object: { is_valid: true, errors: [], }, context: { is_valid: false, errors: [], }, }, ]), ).toEqual(false); }); it('returns false if second action scope is invalid', () => { expect( Authz.areRulesValidated([ { action: { is_valid: true, errors: [], }, subject: { is_valid: true, errors: [], }, object: { is_valid: true, errors: [], }, context: { is_valid: true, errors: [], }, }, { action: { is_valid: false, errors: [], }, subject: { is_valid: true, errors: [], }, object: { is_valid: true, errors: [], }, context: { is_valid: true, errors: [], }, }, ]), ).toEqual(false); }); }); describe('#isAllowed', () => { it('returns true if the policy is validated and verb is `allow`', () => { expect( Authz.isAllowed([ { obligations: [], verb: AUTHORIZATION_VERB_ALLOW as PolicyVerbs.Allow, validations: [ { action: { is_valid: true, errors: [], }, subject: { is_valid: true, errors: [], }, object: { is_valid: true, errors: [], }, context: { is_valid: true, errors: [], }, }, ], }, ]), ).toEqual(true); }); it('returns false if the policy is validated and verb is `deny`', () => { expect( Authz.isAllowed([ { obligations: [], verb: AUTHORIZATION_VERB_DENY as PolicyVerbs.Deny, validations: [ { action: { is_valid: true, errors: [], }, subject: { is_valid: true, errors: [], }, object: { is_valid: true, errors: [], }, context: { is_valid: true, errors: [], }, }, ], }, ]), ).toEqual(false); }); it('returns false if the policy is not validated but the verb is `allow`', () => { expect( Authz.isAllowed([ { obligations: [], verb: AUTHORIZATION_VERB_ALLOW as PolicyVerbs.Allow, validations: [ { action: { is_valid: false, errors: [], }, subject: { is_valid: true, errors: [], }, object: { is_valid: true, errors: [], }, context: { is_valid: true, errors: [], }, }, ], }, ]), ).toEqual(false); }); it('returns true if the policy is not validated but the verb is `deny`', () => { expect( Authz.isAllowed([ { obligations: [], verb: AUTHORIZATION_VERB_DENY as PolicyVerbs.Deny, validations: [ { action: { is_valid: false, errors: [], }, subject: { is_valid: true, errors: [], }, object: { is_valid: true, errors: [], }, context: { is_valid: true, errors: [], }, }, ], }, ]), ).toEqual(true); }); it('returns false if a policy is already validated and verb is `deny`', () => { expect( Authz.isAllowed([ { obligations: [], verb: AUTHORIZATION_VERB_DENY as PolicyVerbs.Deny, validations: [ { action: { is_valid: true, errors: [], }, subject: { is_valid: true, errors: [], }, object: { is_valid: true, errors: [], }, context: { is_valid: true, errors: [], }, }, ], }, { obligations: [], verb: AUTHORIZATION_VERB_ALLOW as PolicyVerbs.Allow, validations: [ { action: { is_valid: true, errors: [], }, subject: { is_valid: true, errors: [], }, object: { is_valid: true, errors: [], }, context: { is_valid: true, errors: [], }, }, ], }, ]), ).toEqual(false); }); }); describe('#validateRules', () => { let validator; beforeEach(() => { validator = new Ajv({ strict: false, useDefaults: false, coerceTypes: true, }); }); it('is invalid if no rule is defined', () => { const validations = Authz.validateRules( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [], object: [], context: [], }, [], ); expect(Authz.areRulesValidated(validations)).toEqual(false); }); it('is valid if the subject is matching a rule', () => { const validations = Authz.validateRules( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [ { is_enabled: true, scope: ['subject'], description: 'Subject is having attribute `firstname=john`', attribute: 'firstname', value: 'john', }, ], object: [], context: [], }, [ { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { attribute: { type: 'string', enum: ['firstname'], }, value: { type: 'string', enum: ['john'], }, }, }, ], }, }, }, }, object: {}, context: {}, }, ], ); expect(Authz.areRulesValidated(validations)).toEqual(true); }); it('is invalid if the subject does not match a rule', () => { const validations = Authz.validateRules( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [ { is_enabled: true, scope: ['subject'], description: 'Subject is having attribute `firstname=john`', attribute: 'firstname', value: 'john', }, ], object: [], context: [], }, [ { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=alice`', // <-- Not john action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { attribute: { type: 'string', enum: ['firstname'], }, value: { type: 'string', enum: ['alice'], }, }, }, ], }, }, }, }, object: {}, context: {}, }, ], ); expect(Authz.areRulesValidated(validations)).toEqual(false); }); it('compiles and keeps compiled version of schema in memory if $id is available', () => { const validations = Authz.validateRules( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [ { is_enabled: true, scope: ['subject'], description: 'Subject is having attribute `firstname=john`', attribute: 'firstname', value: 'john', }, ], object: [], context: [], }, [ { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { $id: 'subject', type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { attribute: { type: 'string', enum: ['firstname'], }, value: { type: 'string', enum: ['john'], }, }, }, ], }, }, }, }, object: {}, context: {}, }, ], ); expect(Authz.areRulesValidated(validations)).toEqual(true); expect(validator.getSchema('subject')).not.toBeUndefined(); }); it('is valid if the subject is matching all the rules', () => { const validations = Authz.validateRules( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [ { is_enabled: true, scope: ['subject'], description: 'Subject is having attribute `firstname=john`', attribute: 'firstname', value: 'john', }, ], object: [], context: [], }, [ { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { attribute: { type: 'string', enum: ['firstname'], }, }, }, ], }, }, }, }, object: {}, context: {}, }, { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { value: { type: 'string', enum: ['john'], }, }, }, ], }, }, }, }, object: {}, context: {}, }, ], ); expect(Authz.areRulesValidated(validations)).toEqual(true); }); it('is invalid if the subject does not match one rules among others', () => { const validations = Authz.validateRules( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [ { is_enabled: true, scope: ['subject'], description: 'Subject is having attribute `firstname=john`', attribute: 'firstname', value: 'john', }, ], object: [], context: [], }, [ { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { attribute: { type: 'string', enum: ['firstname'], }, }, }, ], }, }, }, }, object: {}, context: {}, }, { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { value: { type: 'string', enum: ['alice'], // <-- Not john }, }, }, ], }, }, }, }, object: {}, context: {}, }, ], ); expect(Authz.areRulesValidated(validations)).toEqual(false); }); }); describe('#validatePolicy', () => { let validator; beforeEach(() => { validator = new Ajv({ strict: false, useDefaults: false, coerceTypes: true, }); }); it('returns an `allow` decision if attributes are matching the policy rules', () => { const decision = Authz.validatePolicy( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [ { is_enabled: true, scope: ['subject'], description: 'Subject is having attribute `firstname=john`', attribute: 'firstname', value: 'john', }, ], object: [], context: [], }, { name: 'policy', description: 'policy description', is_enabled: true, obligations: [], scope: ['scope'], verb: AUTHORIZATION_VERB_ALLOW as PolicyVerb, rules: [ { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { attribute: { type: 'string', enum: ['firstname'], }, value: { type: 'string', enum: ['john'], }, }, }, ], }, }, }, }, object: {}, context: {}, }, ], }, ); expect(decision).toEqual({ obligations: [], validations: [ { action: { errors: [], is_valid: true, }, context: { errors: [], is_valid: true, }, object: { errors: [], is_valid: true, }, subject: { errors: [], is_valid: true, }, }, ], verb: 'allow', }); }); it('returns an `deny` decision if attributes are not matching the policy rules', () => { const decision = Authz.validatePolicy( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [ { is_enabled: true, scope: ['subject'], description: 'Subject is having attribute `firstname=john`', attribute: 'firstname', value: 'john', }, ], object: [], context: [], }, { name: 'policy', description: 'policy description', is_enabled: true, obligations: [], scope: ['scope'], verb: AUTHORIZATION_VERB_DENY as PolicyVerb, rules: [ { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { attribute: { type: 'string', enum: ['firstname'], }, value: { type: 'string', not: { enum: ['alice'] }, }, }, }, ], }, }, }, }, object: {}, context: {}, }, ], }, ); expect(decision).toEqual({ obligations: [], validations: [ { action: { errors: [], is_valid: true, }, context: { errors: [], is_valid: true, }, object: { errors: [], is_valid: true, }, subject: { errors: [], is_valid: true, }, }, ], verb: 'deny', }); }); it('returns an `allow` decision if the policy rules are not matching', () => { const decision = Authz.validatePolicy( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [ { is_enabled: true, scope: ['subject'], description: 'Subject is having attribute `firstname=john`', attribute: 'firstname', value: 'john', }, ], object: [], context: [], }, { name: 'policy', description: 'policy description', is_enabled: true, obligations: [], scope: ['scope'], verb: AUTHORIZATION_VERB_DENY as PolicyVerb, rules: [ { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { attribute: { type: 'string', enum: ['firstname'], }, value: { type: 'string', enum: ['alice'], }, }, }, ], }, }, }, }, object: {}, context: {}, }, ], }, ); expect(decision).toEqual({ obligations: [], validations: [ { action: { errors: [], is_valid: true, }, context: { errors: [], is_valid: true, }, object: { errors: [], is_valid: true, }, subject: { errors: [ { instancePath: '/_attributes/0/value', keyword: 'enum', message: 'must be equal to one of the allowed values', params: { allowedValues: ['alice'], }, schemaPath: '#/properties/_attributes/items/anyOf/0/properties/value/enum', }, { instancePath: '/_attributes/0', keyword: 'anyOf', message: 'must match a schema in anyOf', params: {}, schemaPath: '#/properties/_attributes/items/anyOf', }, ], is_valid: false, }, }, ], verb: 'allow', }); }); }); describe('#validatePolicies', () => { let validator; beforeEach(() => { validator = new Ajv({ strict: false, useDefaults: false, coerceTypes: true, }); }); it('returns a `allow` decision if no policy is provided and `noPolicyVerb=allow`', () => { expect( Authz.validatePolicies( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [], object: [], context: [], }, [], { noPolicyVerb: AUTHORIZATION_VERB_ALLOW as PolicyVerb, }, ), ).toMatchObject({ verb: 'allow', }); }); it('returns a `deny` decision if no policy is provided and `noPolicyVerb=deny`', () => { expect( Authz.validatePolicies( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [], object: [], context: [], }, [], { noPolicyVerb: AUTHORIZATION_VERB_DENY as PolicyVerb, }, ), ).toMatchObject({ verb: 'deny', }); }); it('returns an `allow` decision if attributes are matching all the policy rules', () => { const decision = Authz.validatePolicies( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [ { is_enabled: true, scope: ['subject'], description: 'Subject is having attribute `firstname=john`', attribute: 'firstname', value: 'john', }, ], object: [], context: [], }, [ { name: 'required_firstname', description: 'Firstname attribute must exist', is_enabled: true, obligations: [], scope: ['scope'], verb: AUTHORIZATION_VERB_ALLOW as PolicyVerb, rules: [ { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { attribute: { type: 'string', enum: ['firstname'], }, }, }, ], }, }, }, }, object: {}, context: {}, }, ], }, { name: 'required_john', description: 'Attribute value John must be found', is_enabled: true, obligations: [], scope: ['scope'], verb: AUTHORIZATION_VERB_ALLOW as PolicyVerb, rules: [ { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { value: { type: 'string', enum: ['john'], }, }, }, ], }, }, }, }, object: {}, context: {}, }, ], }, ], { noPolicyVerb: AUTHORIZATION_VERB_ALLOW as PolicyVerb, }, ); expect(decision).toEqual({ obligations: [], validations: [ { action: { errors: [], is_valid: true, }, context: { errors: [], is_valid: true, }, object: { errors: [], is_valid: true, }, subject: { errors: [], is_valid: true, }, }, { action: { errors: [], is_valid: true, }, context: { errors: [], is_valid: true, }, object: { errors: [], is_valid: true, }, subject: { errors: [], is_valid: true, }, }, ], verb: 'allow', }); }); it('returns an `deny` decision if attributes are not matching the policies expected ones', () => { const decision = Authz.validatePolicies( validator, { action: { scope: ['action'], }, subject: { scope: ['subject'], }, object: { scope: ['object'], }, context: { scope: ['context'], }, }, { action: [], subject: [ { is_enabled: true, scope: ['subject'], description: 'Subject is having attribute `firstname=john`', attribute: 'firstname', value: 'john', }, ], object: [], context: [], }, [ { name: 'required_firstname', description: 'Firstname attribute must exist', is_enabled: true, obligations: [], scope: ['scope'], verb: AUTHORIZATION_VERB_DENY as PolicyVerb, rules: [ { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', not: { required: ['missing'] }, }, ], }, }, }, }, object: {}, context: {}, }, ], }, { name: 'required_john', description: 'Attribute value John must be found', is_enabled: true, obligations: [], scope: ['scope'], verb: AUTHORIZATION_VERB_ALLOW as PolicyVerb, rules: [ { description: '', is_enabled: true, name: 'Subject must have attribute `firstname=john`', action: {}, subject: { type: 'object', required: ['_attributes'], properties: { _attributes: { type: 'array', items: { anyOf: [ { type: 'object', properties: { value: { type: 'string', enum: ['john'], }, }, }, ], }, }, }, }, object: {}, context: {}, }, ], }, ], { noPolicyVerb: AUTHORIZATION_VERB_ALLOW as PolicyVerb, }, ); expect(decision).toEqual({ obligations: [], validations: [ { action: { errors: [], is_valid: true, }, context: { errors: [], is_valid: true, }, object: { errors: [], is_valid: true, }, subject: { errors: [], is_valid: true, }, }, { action: { errors: [], is_valid: true, }, context: { errors: [], is_valid: true, }, object: { errors: [], is_valid: true, }, subject: { errors: [], is_valid: true, }, }, ], verb: 'deny', }); }); }); });