access-controls
Version:
rule based access-controls engine for node.js (browser compatible)
1,193 lines (843 loc) • 27.5 kB
JavaScript
var AccessControlList = require('../lib/AccessControlList.js')
var assert = require('assert')
describe('access control list', function() {
it('single attribute single role', function(done) {
var obj = {
nested: {
region: 'EMEA'
}
}
var acl = new AccessControlList({
name: 'acl1_required',
roles: ['EMEA'],
control: 'required',
actions: ['load', 'save'],
conditions: [{
attributes: {
'nested.region': 'EMEA'
}
}
]
})
assert.ok(acl.shouldApply(obj, 'load').ok)
assert.ok(acl.shouldApply(obj, 'save').ok)
assert.ok(!acl.shouldApply(obj, 'remove').ok)
acl.authorize(obj, 'load', ['EMEA'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
acl.authorize(obj, 'remove', ['EMEA'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
acl.authorize(obj, 'load', ['APAC'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(!result.authorize)
done()
})
})
})
})
it('single action', function(done) {
var obj = {region: 'EMEA'}
var acl = new AccessControlList({
name: 'acl1_required',
roles: ['EMEA'],
control: 'required',
actions: ['load'],
conditions: [{
attributes: {
'region': 'EMEA'
}
}
]
})
assert.ok(acl.shouldApply(obj, 'load').ok)
assert.ok(!acl.shouldApply(obj, 'save').ok)
assert.ok(!acl.shouldApply(obj, 'remove').ok)
acl.authorize(obj, 'load', ['EMEA'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
done()
})
})
it('should authorize action using fn condition clause', function(done) {
var obj = {
zipcode: '85050'
};
var acl = new AccessControlList({
name: 'acl2_required',
roles: ['agent'],
control: 'required',
actions: ['load'],
conditions: [{
fn: function(obj, context) {
if (!~context.user.allowedZipcodes.indexOf(obj.zipcode)) {
return {
ok: false,
reason: 'zipcode not in list of allowed zipcodes'
}
}
return { ok: true }
}
}]
})
assert.ok(acl.shouldApply(obj, 'load').ok)
acl.authorize(obj, 'load', ['agent'], {user: {allowedZipcodes: ['85032', '85050']}}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
done()
})
})
it('should not authorize action using fn condition clause', function(done) {
var obj = {
zipcode: '85050'
};
var acl = new AccessControlList({
name: 'acl2_required',
roles: ['agent'],
control: 'required',
actions: ['load'],
conditions: [{
fn: function(obj, context) {
if (!~context.user.allowedZipcodes.indexOf(obj.zipcode)) {
return {
ok: false,
reason: 'zipcode not in list of allowed zipcodes'
}
}
return { ok: true }
}
}]
})
assert.ok(acl.shouldApply(obj, 'load').ok)
acl.authorize(obj, 'load', ['agent'], {user: {allowedZipcodes: ['85032', '85054']}}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(!result.authorize)
done()
})
})
it('should fail to allow blacklisted numbers', function(done){
// Story: Each state (entity) has a number of zip codes that are
// not allowed to participate in a competition. These zip codes
// are listed as an array under the zipBlacklist property.
// We need to make sure that the context object (a city with a zip)
// is not in this list and fail authorization if it is.
var numberObj = {
state: 'Arizona',
zipBlacklist: ['85050', '85260']
};
var acl = new AccessControlList({
name: 'Check for Blacklist',
roles: ['agent'],
control: 'required',
actions: ['save','list','load'],
conditions: [{
attributes: {
'zipBlacklist': '{!city.zip}'
}
}]
});
assert.ok(acl.shouldApply(numberObj, 'load').ok);
acl.authorize(numberObj, 'load', ['agent'], {city: {zip: '85050'}}, function(err, result) {
assert.ok(!err, err);
assert.ok(result);
// Fail authorization because zip is in the blacklist
assert.ok(!result.authorize);
});
done();
});
it('should authorize when blacklisted numbers are not present', function(done){
var numberObj = {
state: 'Arizona',
zipBlacklist: ['85050', '85260']
};
var acl = new AccessControlList({
name: 'Check for Blacklist',
roles: ['agent'],
control: 'required',
actions: ['save','list','load'],
conditions: [{
attributes: {
'zipBlacklist': '{!city.zip}'
}
}]
});
assert.ok(acl.shouldApply(numberObj, 'load').ok);
acl.authorize(numberObj, 'load', ['agent'], {city: {zip: '85555'}}, function(err, result) {
assert.ok(!err, err);
assert.ok(result);
// Should authorization because zip is not in the blacklist
assert.ok(result.authorize);
});
done();
});
it('should match conditions when expected value for attribute is a literal array', function(done) {
var obj = {
zipcode: '85050'
};
var acl = new AccessControlList({
name: 'Check for Blacklist',
roles: ['agent'],
control: 'required',
actions: ['save','list','load'],
conditions: [{
attributes: {
'zipcode': ['85032', '85050']
}
}]
});
assert.ok(acl.shouldApply(obj, 'load').ok);
acl.authorize(obj, 'load', [], {}, function(err, result) {
assert.ok(!err, err);
assert.ok(result);
// Should fail to authorize because conditions match and required role is missing
assert.ok(!result.authorize);
});
done();
});
it('should fail to allow load objects outside allowed user zipcodes', function(done){
var obj = {
zipcode: '85050'
};
var acl = new AccessControlList({
name: 'Check for Blacklist',
roles: ['agent'],
control: 'required',
actions: ['save','list','load'],
conditions: [{
attributes: {
'zipcode': '{user.allowedZipcodes}'
}
}]
});
assert.ok(acl.shouldApply(obj, 'load').ok);
acl.authorize(obj, 'load', ['agent'], {user: {allowedZipcodes: ['85032', '85051']}}, function(err, result) {
assert.ok(!err, err);
assert.ok(result);
// Fail authorization because object zipcode is not in user allowed zipcodes list
assert.ok(!result.authorize);
});
done();
});
it('should authorize when object is within allowed user zipcodes', function(done) {
var obj = {
zipcode: '85050'
};
var acl = new AccessControlList({
name: 'Check for Blacklist',
roles: ['agent'],
control: 'required',
actions: ['save','list','load'],
conditions: [{
attributes: {
'zipcode': '{user.allowedZipcodes}'
}
}]
});
assert.ok(acl.shouldApply(obj, 'load').ok);
acl.authorize(obj, 'load', ['agent'], {user: {allowedZipcodes: ['85032', '85050']}}, function(err, result) {
assert.ok(!err, err);
assert.ok(result);
// Should authorize because object zipcode is within user allowed zipcodes list
assert.ok(result.authorize);
});
done();
});
it('should authorize access when groups match', function(done) {
var obj = {
groups: ['a', 'b']
};
var acl = new AccessControlList({
name: 'Check for Blacklist',
roles: ['a'],
control: 'required',
actions: ['save','list','load'],
conditions: [{
attributes: {
'groups': ['a']
}
}]
});
assert.ok(acl.shouldApply(obj, 'load').ok);
acl.authorize(obj, 'load', [], {}, function(err, result) {
assert.ok(!err, err);
assert.ok(result);
// Should fail to authorize because conditions match and required role is missing
assert.ok(!result.authorize);
});
done();
});
it('should fail to allow load objects when user is not part of any allowed groups', function(done){
var obj = {
allowedGroups: ['a', 'b']
};
var acl = new AccessControlList({
name: 'Check for Blacklist',
roles: ['agent'],
control: 'required',
actions: ['save','list','load'],
conditions: [{
attributes: {
'allowedGroups': '{user.groups}'
}
}]
});
assert.ok(acl.shouldApply(obj, 'load').ok);
acl.authorize(obj, 'load', ['agent'], {user: {groups: ['c', 'd']}}, function(err, result) {
assert.ok(!err, err);
assert.ok(result);
// Fail authorization because user is not part of any allowed groups
assert.ok(!result.authorize);
});
done();
});
it('should authorize when user is in at least one of the allowed groups', function(done) {
var obj = {
allowedGroups: ['a', 'b']
};
var acl = new AccessControlList({
name: 'Check for Blacklist',
roles: ['agent'],
control: 'required',
actions: ['save','list','load'],
conditions: [{
attributes: {
'allowedGroups': '{user.groups}'
}
}]
});
assert.ok(acl.shouldApply(obj, 'load').ok);
acl.authorize(obj, 'load', ['agent'], {user: {groups: ['c', 'a']}}, function(err, result) {
assert.ok(!err, err);
assert.ok(result);
// Should authorize because user is in one of the allowed groups
assert.ok(result.authorize);
});
done();
});
it('should grant access to foo cases', function (done) {
var obj = {caseType: 'foo'}
var acl = new AccessControlList({
name : 'acl1_required',
roles : ['EMEA'],
control : 'requisite',
actions : ['load'],
conditions: [{
attributes: {
'!caseType': 'foo'
}
}
]
})
assert.ok(acl.shouldApply(obj, 'load').ok)
acl.authorize(obj, 'load', [], {}, function (err, result) {
assert.ok(!err, err)
assert.ok(result)
//condition is false so EMEA role shouldn't be required and therefore user should
// be granted access
assert.ok(result.authorize)
done()
})
});
it('should not grant access to foo cases', function (done) {
var obj = {caseType: 'bar'}
var acl = new AccessControlList({
name : 'acl1_required',
roles : ['EMEA'],
control : 'requisite',
actions : ['load'],
conditions: [{
attributes: {
'!caseType': 'foo'
}
}
]
})
assert.ok(acl.shouldApply(obj, 'load').ok)
acl.authorize(obj, 'load', [], {}, function (err, result) {
assert.ok(!err, err)
assert.ok(result)
//condition is true so EMEA role required and therefore user should
// not be granted access
assert.ok(!result.authorize)
done()
})
});
it('should always apply on empty conditions', function(done) {
var obj1 = {region: 'EMEA'}
var obj2 = {region: 'APAC'}
var acl = new AccessControlList({
name: 'acl1_required',
roles: ['granted'],
control: 'required',
actions: ['load'],
conditions: []
})
assert.ok(acl.shouldApply(obj1, 'load').ok)
assert.ok(!acl.shouldApply(obj1, 'save').ok)
assert.ok(!acl.shouldApply(obj1, 'remove').ok)
assert.ok(acl.shouldApply(obj2, 'load').ok)
assert.ok(!acl.shouldApply(obj2, 'save').ok)
assert.ok(!acl.shouldApply(obj2, 'remove').ok)
acl.authorize(obj1, 'load', ['granted'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
acl.authorize(obj2, 'load', ['denied'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(!result.authorize)
done()
})
})
})
it('can apply to context', function(done) {
var obj = {region: 'EMEA', owner: 123}
var acl = new AccessControlList({
name: 'acl1_required',
roles: ['EMEA'],
control: 'required',
actions: ['load'],
conditions: [{
attributes: {
'owner': '{user.id}'
}
}
]
})
assert.ok(acl.shouldApply(obj, 'load').ok)
assert.ok(!acl.shouldApply(obj, 'save').ok)
assert.ok(!acl.shouldApply(obj, 'remove').ok)
acl.authorize(obj, 'load', ['EMEA'], {user: {id: 123}}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
obj.owner = 1234
acl.authorize(obj, 'load', ['EMEA'], {user: {id: 123}}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(!result.authorize)
done()
})
})
})
it('inheritance user::owner', function(done) {
var obj = {region: 'EMEA', owner: 123}
var acl = new AccessControlList({
name: 'acl1_required',
roles: ['EMEA'],
control: 'required',
actions: ['load'],
conditions: [
'{user::owner}',
{
attributes: {
'owner': '{user.id}'
}
}
]
})
assert.ok(acl.shouldApply(obj, 'load').ok)
acl.authorize(obj, 'load', ['EMEA'], {user: {id: 123}}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(result.inherit)
assert.equal(result.inherit[0].id, 123)
assert.ok(result.inherit[0].entity)
assert.ok(!result.inherit[0].entity.zone)
assert.ok(!result.inherit[0].entity.base)
assert.equal(result.inherit[0].entity.name, 'user')
obj.owner = 1234
acl.authorize(obj, 'load', ['EMEA'], {user: {id: 123}}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(!result.authorize)
done()
})
})
})
it('can handle inheritance sys/user::owner', function(done) {
var obj = {region: 'EMEA', owner: 123}
var acl = new AccessControlList({
name: 'acl1_required',
roles: ['EMEA'],
control: 'required',
actions: ['load'],
conditions: [
'{sys/user::owner}',
{
attributes: {
'owner': '{user.id}'
}
}
]
})
assert.ok(acl.shouldApply(obj, 'load').ok)
acl.authorize(obj, 'load', ['EMEA'], {user: {id: 123}}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(result.inherit)
assert.equal(result.inherit[0].id, 123)
assert.ok(result.inherit[0].entity)
assert.ok(!result.inherit[0].entity.zone)
assert.equal(result.inherit[0].entity.base, 'sys')
assert.equal(result.inherit[0].entity.name, 'user')
obj.owner = 1234
acl.authorize(obj, 'load', ['EMEA'], {user: {id: 123}}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(!result.authorize)
done()
})
})
})
it('can handle inheritance zone-1/sys/user::owner', function(done) {
var obj = {region: 'EMEA', owner: 123}
var acl = new AccessControlList({
name: 'acl1_required',
roles: ['EMEA'],
control: 'required',
actions: ['load'],
conditions: [
'{zone-1/sys/user::owner}',
{
attributes: {
'owner': '{user.id}'
}
}
]
})
assert.ok(acl.shouldApply(obj, 'load').ok)
acl.authorize(obj, 'load', ['EMEA'], {user: {id: 123}}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(result.inherit)
assert.equal(result.inherit[0].id, 123)
assert.ok(result.inherit[0].entity)
assert.equal(result.inherit[0].entity.zone, 'zone-1')
assert.equal(result.inherit[0].entity.base, 'sys')
assert.equal(result.inherit[0].entity.name, 'user')
obj.owner = 1234
acl.authorize(obj, 'load', ['EMEA'], {user: {id: 123}}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(!result.authorize)
done()
})
})
})
describe('attributes filtering', function() {
it('denied', function(done) {
var obj = {date: Date.now(), region: 'EMEA'}
var acl = new AccessControlList({
name: 'acl1_filter',
roles: ['EMEA'],
control: 'filter',
actions: ['load'],
conditions: [{
attributes: {
'region': 'EMEA'
}
}
],
filters: {
region: false
}
})
acl.authorize(obj, 'load', ['APAC'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(result.filters)
assert.equal(result.filters.length, 1)
assert.equal(result.filters[0].attribute, 'region')
assert.equal(result.filters[0].access, 'denied')
acl.authorize(obj, 'load', ['EMEA'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(!result.filters)
done()
})
})
})
it('mask', function(done) {
var obj = {date: Date.now(), region: 'EMEA', sin: '123-456-789', ssn1: '123-456-7890', ssn2: '123-456-7890', ssn3: '123-456-7890'}
var acl = new AccessControlList({
name: 'acl2_filter',
roles: ['EMEA'],
control: 'filter',
actions: ['load'],
conditions: [{
attributes: {
'region': 'EMEA'
}
}
],
filters: {
sin: function(value) {
if(value && value.length > 0) {
return '***-***-' + value.substr(-3)
} else {
return '***-***-***'
}
},
ssn1: -4, // mask all except the last 4 characters
ssn2: 3, // mask all except the first 3 characters
ssn3: -50,
ssn4: 12
}
})
acl.authorize(obj, 'load', ['APAC'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(result.filters)
assert.equal(result.filters.length, 5)
assert.equal(result.filters[0].attribute, 'sin')
assert.equal(result.filters[0].access, 'partial')
assert.equal(result.filters[0].originalValue, '123-456-789')
assert.equal(result.filters[0].filteredValue, '***-***-789')
assert.equal(result.filters[1].attribute, 'ssn1')
assert.equal(result.filters[1].access, 'partial')
assert.equal(result.filters[1].originalValue, '123-456-7890')
assert.equal(result.filters[1].filteredValue, '********7890')
assert.equal(result.filters[2].attribute, 'ssn2')
assert.equal(result.filters[2].access, 'partial')
assert.equal(result.filters[2].originalValue, '123-456-7890')
assert.equal(result.filters[2].filteredValue, '123*********')
assert.equal(result.filters[3].attribute, 'ssn3')
assert.equal(result.filters[3].access, 'partial')
assert.equal(result.filters[3].originalValue, '123-456-7890')
assert.equal(result.filters[3].filteredValue, '123-456-7890')
assert.equal(result.filters[4].attribute, 'ssn4')
assert.equal(result.filters[4].access, 'denied')
assert.equal(result.filters[4].originalValue, undefined)
acl.authorize(obj, 'load', ['EMEA'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(!result.filters)
done()
})
})
})
it('mask, denied and non existing attr', function(done) {
var obj = {date: Date.now(), region: 'EMEA', sin: '123-456-789'}
var acl = new AccessControlList({
name: 'acl2_filter',
roles: ['EMEA'],
control: 'filter',
actions: ['load'],
conditions: [{
attributes: {
'region': 'EMEA'
}
}
],
filters: {
foo: false,
region: false,
sin: function(value) {
if(value && value.length > 0) {
return '***-***-' + value.substr(-3)
} else {
return '***-***-***'
}
}
}
})
acl.authorize(obj, 'load', ['APAC'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(result.filters)
assert.equal(result.filters.length, 3)
assert.equal(result.filters[0].attribute, 'foo')
assert.equal(result.filters[0].access, 'denied')
assert.equal(result.filters[0].originalValue, undefined)
assert.equal(result.filters[1].attribute, 'region')
assert.equal(result.filters[1].access, 'denied')
assert.equal(result.filters[1].originalValue, 'EMEA')
assert.equal(result.filters[2].attribute, 'sin')
assert.equal(result.filters[2].access, 'partial')
assert.equal(result.filters[2].filteredValue, '***-***-789')
assert.equal(result.filters[2].originalValue, '123-456-789')
acl.authorize(obj, 'load', ['EMEA'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(!result.filters)
done()
})
})
})
it('does not filter when the conditions are not met', function(done) {
var obj = {date: Date.now(), region: 'APAC', sin: '123-456-789'}
var acl = new AccessControlList({
name: 'acl2_filter',
roles: ['EMEA'],
control: 'filter',
actions: ['load'],
conditions: [{
attributes: {
'region': 'EMEA'
}
}
],
filters: {
sin: function(value) {
if(value && value.length > 0) {
return '***-***-' + value.substr(-3)
} else {
return '***-***-***'
}
}
}
})
acl.authorize(obj, 'load', ['APAC'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(!result.filters)
acl.authorize(obj, 'load', ['EMEA'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(!result.filters)
done()
})
})
})
it('denies value if setting to', function(done) {
var obj = {
name: 'foo',
status: 'closed',
reason: 'invalid',
original$: {
name: 'foo',
status: 'open'
}
}
var acl = new AccessControlList({
name: 'acl3_filter',
roles: ['supervisor'],
control: 'filter',
actions: ['save_new'],
conditions: [{
attributes: {
'status': ['open', 'blocked']
}
}
],
filters: {
status: ['closed'],
reason: false
}
})
assert.ok(acl.shouldApply(obj, 'save_new').ok);
acl.authorize(obj, 'save_new', ['agent'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(result.filters)
assert.equal(result.filters.length, 2)
assert.equal(result.filters[0].attribute, 'status')
assert.equal(result.filters[0].access, 'denied')
assert.equal(result.filters[1].attribute, 'reason')
assert.equal(result.filters[1].access, 'denied')
obj.status = 'blocked'
acl.authorize(obj, 'save_new', ['agent'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(result.filters)
assert.equal(result.filters.length, 2)
assert.equal(result.filters[0].attribute, 'status')
assert.equal(result.filters[0].access, undefined)
assert.equal(result.filters[1].attribute, 'reason')
assert.equal(result.filters[1].access, 'denied')
obj.status = 'closed'
acl.authorize(obj, 'save_new', ['supervisor'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(!result.filters)
done()
}) }) })
})
})
it('write denied', function(done) {
var obj = {date: Date.now(), region: 'EMEA'}
var acl = new AccessControlList({
name: 'acl1_filter',
roles: ['EMEA'],
control: 'filter',
actions: ['save_new'],
conditions: [{
attributes: {
'region': 'EMEA'
}
}
],
filters: {
region: false
}
})
acl.authorize(obj, 'save_new', ['APAC'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(result.filters)
assert.equal(result.filters.length, 1)
assert.equal(result.filters[0].attribute, 'region')
assert.equal(result.filters[0].access, 'denied')
acl.authorize(obj, 'load', ['EMEA'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
assert.ok(!result.filters)
done()
})
})
})
it('remove denied', function(done) {
var obj = {date: Date.now(), region: 'EMEA'}
var acl = new AccessControlList({
name: 'acl1_filter',
roles: ['EMEA'],
control: 'required',
actions: ['remove'],
conditions: [{
attributes: {
'region': 'EMEA'
}
}
]
})
acl.authorize(obj, 'save_new', ['EMEA'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
acl.authorize(obj, 'remove', ['APAC'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(!result.authorize)
acl.authorize(obj, 'remove', ['EMEA'], {}, function(err, result) {
assert.ok(!err, err)
assert.ok(result)
assert.ok(result.authorize)
done()
})
})
})
})
})