Fix tests depending on old behavior of default User ACLs

This commit is contained in:
Ritchie Martori 2013-12-17 21:10:05 -08:00
parent 3152960336
commit 939df463fe
8 changed files with 404 additions and 46 deletions

View File

@ -52,6 +52,11 @@ var properties = {
var options = {
acls: [
{
principalType: ACL.ROLE,
principalId: Role.EVERYONE,
permission: ACL.DENY,
},
{
principalType: ACL.ROLE,
principalId: Role.EVERYONE,

View File

@ -36,7 +36,8 @@
"mocha": "~1.14.0",
"strong-task-emitter": "0.0.x",
"supertest": "~0.8.1",
"chai": "~1.8.1"
"chai": "~1.8.1",
"loopback-testing": "0.0.4"
},
"repository": {
"type": "git",

View File

@ -0,0 +1,178 @@
var loopback = require('loopback');
var lt = require('loopback-testing');
var path = require('path');
var ACCESS_CONTROL_APP = path.join(__dirname, 'fixtures', 'access-control');
var app = require(path.join(ACCESS_CONTROL_APP, 'app.js'));
var assert = require('assert');
var USER = {email: 'test@test.test', password: 'test'};
var CURRENT_USER = {email: 'current@test.test', password: 'test'};
describe('access control - integration', function () {
lt.beforeEach.withApp(app);
describe('accessToken', function() {
it('should be a sublcass of AccessToken', function () {
assert(app.models.accessToken.prototype instanceof loopback.AccessToken);
});
it('should have a validate method', function () {
var token = new app.models.accessToken;
assert.equal(typeof token.validate, 'function');
});
});
describe('/accessToken', function() {
lt.beforeEach.givenModel('accessToken', {}, 'randomToken');
lt.it.shouldBeAllowedWhenCalledAnonymously('POST', '/api/accessTokens');
lt.it.shouldBeAllowedWhenCalledUnauthenticated('POST', '/api/accessTokens');
lt.it.shouldBeAllowedWhenCalledByUser(USER, 'POST', '/api/accessTokens');
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/accessTokens');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/accessTokens');
lt.it.shouldBeDeniedWhenCalledByUser(USER, 'GET', '/api/accessTokens');
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', '/api/accessTokens');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', '/api/accessTokens');
lt.it.shouldBeDeniedWhenCalledByUser(USER, 'PUT', '/api/accessTokens');
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForToken);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForToken);
lt.it.shouldBeDeniedWhenCalledByUser(USER, 'GET', urlForToken);
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForToken);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForToken);
lt.it.shouldBeDeniedWhenCalledByUser(USER, 'PUT', urlForToken);
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForToken);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForToken);
lt.it.shouldBeDeniedWhenCalledByUser(USER, 'DELETE', urlForToken);
function urlForToken() {
return '/api/accessTokens/' + this.randomToken.id;
}
});
describe('/users', function () {
lt.beforeEach.givenModel('user', USER, 'randomUser');
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/users');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/users');
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', '/api/users');
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForUser);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForUser);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER,'GET', urlForUser);
lt.it.shouldBeAllowedWhenCalledAnonymously('POST', '/api/users');
lt.it.shouldBeAllowedWhenCalledUnauthenticated('POST', '/api/users');
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users');
lt.describe.whenCalledRemotely('DELETE', '/api/users', function() {
lt.it.shouldNotBeFound();
});
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForUser);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForUser);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForUser);
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForUser);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForUser);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForUser);
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
beforeEach(function() {
this.url = '/api/users/' + this.user.id + '?ok';
});
lt.describe.whenCalledRemotely('DELETE', '/api/users/:id', function() {
lt.it.shouldBeAllowed();
});
lt.describe.whenCalledRemotely('GET', '/api/users/:id', function() {
lt.it.shouldBeAllowed();
});
lt.describe.whenCalledRemotely('PUT', '/api/users/:id', function() {
lt.it.shouldBeAllowed();
});
});
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForUser);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForUser);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForUser);
function urlForUser() {
return '/api/users/' + this.randomUser.id;
}
});
describe('/banks', function () {
lt.beforeEach.givenModel('bank');
lt.it.shouldBeAllowedWhenCalledAnonymously('GET', '/api/banks');
lt.it.shouldBeAllowedWhenCalledUnauthenticated('GET', '/api/banks');
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'GET', '/api/banks');
lt.it.shouldBeAllowedWhenCalledAnonymously('GET', urlForBank);
lt.it.shouldBeAllowedWhenCalledUnauthenticated('GET', urlForBank);
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'GET', urlForBank);
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/banks');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/banks');
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/banks');
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForBank);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForBank);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForBank);
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForBank);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForBank);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForBank);
function urlForBank() {
return '/api/banks/' + this.bank.id;
}
});
describe('/accounts', function () {
lt.beforeEach.givenModel('account');
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/accounts');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/accounts');
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', '/api/accounts');
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForAccount);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForAccount);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForAccount);
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/accounts');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/accounts');
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/accounts');
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForAccount);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForAccount);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForAccount);
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
beforeEach(function() {
this.url = '/api/accounts/' + this.user.accountId;
});
lt.describe.whenCalledRemotely('PUT', '/api/accounts/:id', function() {
lt.it.shouldBeAllowed();
});
lt.describe.whenCalledRemotely('DELETE', '/api/accounts/:id', function() {
lt.it.shouldBeDenied();
});
});
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForAccount);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForAccount);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForAccount);
function urlForAccount() {
return '/api/accounts/' + this.account.id;
}
});
});

View File

@ -7,6 +7,7 @@ var role = require('../lib/models/role');
var Role = role.Role;
var RoleMapping = role.RoleMapping;
var User = loopback.User;
var testModel;
function checkResult(err, result) {
// console.log(err, result);
@ -15,6 +16,17 @@ function checkResult(err, result) {
describe('security scopes', function () {
beforeEach(function() {
var ds = this.ds = loopback.createDataSource({connector: loopback.Memory});
testModel = loopback.Model.extend('testModel');
ACL.attachTo(ds);
Role.attachTo(ds);
RoleMapping.attachTo(ds);
User.attachTo(ds);
Scope.attachTo(ds);
testModel.attachTo(ds);
});
it("should allow access to models for the given scope by wildcard", function () {
Scope.create({name: 'userScope', description: 'access user information'}, function (err, scope) {
ACL.create({principalType: ACL.SCOPE, principalId: scope.id, model: 'User', property: ACL.ALL,
@ -29,27 +41,24 @@ describe('security scopes', function () {
});
it("should allow access to models for the given scope", function () {
var ds = loopback.createDataSource({connector: loopback.Memory});
Scope.attachTo(ds);
ACL.attachTo(ds);
Scope.create({name: 'userScope', description: 'access user information'}, function (err, scope) {
Scope.create({name: 'testModelScope', description: 'access testModel information'}, function (err, scope) {
ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
model: 'User', property: 'name', accessType: ACL.READ, permission: ACL.ALLOW},
model: 'testModel', property: 'name', accessType: ACL.READ, permission: ACL.ALLOW},
function (err, resource) {
ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
model: 'User', property: 'name', accessType: ACL.WRITE, permission: ACL.DENY},
model: 'testModel', property: 'name', accessType: ACL.WRITE, permission: ACL.DENY},
function (err, resource) {
// console.log(resource);
Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, function (err, perm) {
Scope.checkPermission('testModelScope', 'testModel', ACL.ALL, ACL.ALL, function (err, perm) {
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
});
Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, function (err, perm) {
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.ALL, function (err, perm) {
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
});
Scope.checkPermission('userScope', 'User', 'name', ACL.READ, function (err, perm) {
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.READ, function (err, perm) {
assert(perm.permission === ACL.ALLOW);
});
Scope.checkPermission('userScope', 'User', 'name', ACL.WRITE, function (err, perm) {
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.WRITE, function (err, perm) {
assert(perm.permission === ACL.DENY);
});
});
@ -63,9 +72,6 @@ describe('security scopes', function () {
describe('security ACLs', function () {
it("should allow access to models for the given principal by wildcard", function () {
var ds = loopback.createDataSource({connector: loopback.Memory});
ACL.attachTo(ds);
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW}, function (err, acl) {
@ -87,28 +93,25 @@ describe('security ACLs', function () {
});
it("should allow access to models by exception", function () {
var ds = loopback.createDataSource({connector: loopback.Memory});
ACL.attachTo(ds);
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.DENY}, function (err, acl) {
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.ALLOW}, function (err, acl) {
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, function (err, perm) {
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ, function (err, perm) {
assert(perm.permission === ACL.ALLOW);
});
ACL.checkPermission(ACL.USER, 'u001', 'User', ACL.ALL, ACL.READ, function (err, perm) {
ACL.checkPermission(ACL.USER, 'u001', 'testModel', ACL.ALL, ACL.READ, function (err, perm) {
assert(perm.permission === ACL.ALLOW);
});
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.WRITE, function (err, perm) {
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.WRITE, function (err, perm) {
assert(perm.permission === ACL.DENY);
});
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, function (err, perm) {
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL, function (err, perm) {
assert(perm.permission === ACL.DENY);
});
@ -119,8 +122,7 @@ describe('security ACLs', function () {
});
it("should honor defaultPermission from the model", function () {
var ds = loopback.createDataSource({connector: loopback.Memory});
ACL.attachTo(ds);
var ds = this.ds;
var Customer = ds.createModel('Customer', {
name: {
type: String,
@ -152,7 +154,7 @@ describe('security ACLs', function () {
});
it("should honor static ACLs from the model", function () {
var ds = loopback.createDataSource({connector: loopback.Memory});
var ds = this.ds;
var Customer = ds.createModel('Customer', {
name: {
type: String,
@ -188,14 +190,9 @@ describe('security ACLs', function () {
});
it("should check access against LDL, ACL, and Role", function () {
var ds = loopback.createDataSource({connector: loopback.Memory});
ACL.attachTo(ds);
Role.attachTo(ds);
RoleMapping.attachTo(ds);
User.attachTo(ds);
// var log = console.log;
var log = function() {};
var ds = this.ds;
// Create
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) {
@ -246,21 +243,7 @@ describe('security ACLs', function () {
}, function(err, access) {
assert(!err && access.permission === ACL.ALLOW);
});
/*
ACL.checkAccess({
principals: [
{principalType: ACL.USER, principalId: userId}
],
model: 'Customer',
accessType: ACL.READ
}, function(err, access) {
assert(!err && access.permission === ACL.DENY);
});
*/
});
});
});
});

14
test/fixtures/access-control/app.js vendored Normal file
View File

@ -0,0 +1,14 @@
var loopback = require('loopback');
var path = require('path');
var app = module.exports = loopback();
app.boot(__dirname);
var apiPath = '/api';
app.use(loopback.cookieParser('secret'));
app.use(loopback.token({model: app.models.accessToken}));
app.use(apiPath, loopback.rest());
app.use(app.router);
app.use(loopback.urlNotFound());
app.use(loopback.errorHandler());
app.enableAuth();

4
test/fixtures/access-control/app.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"port": 3000,
"host": "0.0.0.0"
}

View File

@ -0,0 +1,10 @@
{
"db": {
"defaultForType": "db",
"connector": "memory"
},
"mail": {
"defaultForType": "mail",
"connector": "mail"
}
}

163
test/fixtures/access-control/models.json vendored Normal file
View File

@ -0,0 +1,163 @@
{
"email": {
"options": {
"base": "Email",
"acls": [
{
"accessType": "*",
"permission": "DENY",
"principalType": "ROLE",
"principalId": "$everyone"
}
]
},
"dataSource": "mail",
"public": false
},
"user": {
"options": {
"base": "User",
"relations": {
"accessTokens": {
"model": "accessToken",
"type": "hasMany",
"foreignKey": "userId"
},
"account": {
"model": "account",
"type": "belongsTo"
},
"transactions": {
"model": "transaction",
"type": "hasMany"
}
},
"acls": [
{
"accessType": "*",
"permission": "DENY",
"principalType": "ROLE",
"principalId": "$everyone"
}
]
},
"dataSource": "db",
"public": true
},
"accessToken": {
"options": {
"base": "AccessToken",
"baseUrl": "access-tokens",
"acls": [
{
"accessType": "*",
"permission": "DENY",
"principalType": "ROLE",
"principalId": "$everyone"
},
{
"permission": "ALLOW",
"principalType": "ROLE",
"principalId": "$everyone",
"property": "create"
}
]
},
"dataSource": "db",
"public": true
},
"bank": {
"options": {
"relations": {
"users": {
"model": "user",
"type": "hasMany"
},
"accounts": {
"model": "account",
"type": "hasMany"
}
},
"acls": [
{
"accessType": "*",
"permission": "DENY",
"principalType": "ROLE",
"principalId": "$everyone"
},
{
"accessType": "READ",
"permission": "ALLOW",
"principalType": "ROLE",
"principalId": "$everyone"
}
]
},
"properties": {},
"public": true,
"dataSource": "db"
},
"account": {
"options": {
"relations": {
"transactions": {
"model": "transaction",
"type": "hasMany"
}
},
"acls": [
{
"accessType": "*",
"permission": "DENY",
"principalType": "ROLE",
"principalId": "$everyone"
},
{
"accessType": "*",
"permission": "ALLOW",
"principalType": "ROLE",
"principalId": "$owner"
},
{
"permission": "DENY",
"principalType": "ROLE",
"principalId": "$owner",
"property": "removeById"
}
]
},
"properties": {},
"public": true,
"dataSource": "db"
},
"transaction": {
"options": {
"acls": [
{
"accessType": "*",
"permission": "DENY",
"principalType": "ROLE",
"principalId": "$everyone"
}
]
},
"properties": {},
"public": true,
"dataSource": "db"
},
"alert": {
"options": {
"acls": [
{
"accessType": "WRITE",
"permission": "DENY",
"principalType": "ROLE",
"principalId": "$everyone"
}
]
},
"properties": {},
"public": true,
"dataSource": "db"
}
}