I'm new in Loopback and I'm trying to do things one step at a time.
Before I put my ACL in DB, my ACL definition in JSON file of the models works.
When I put the ACL in DB (mysql), it's not working. I think the ACL for the model base User ('Owner' in my code) loads the default ACL for User model and the model base PersistedModel ('Task' in my code) do not have any ACL.
Did I forget to set anything? Please help.
Further Details:
My models datasource are all "mysql".
_server/model-config.json_
...
"AccessToken": {
"dataSource": "mysql",
"public": false
},
"ACL": {
"dataSource": "mysql",
"public": false
},
"RoleMapping": {
"dataSource": "mysql",
"public": false
},
"Role": {
"dataSource": "mysql",
"public": false
},
"Task": {
"dataSource": "mysql",
"public": true
},
"Owner": {
"dataSource": "mysql",
"public": true
}
Below is the definition of my server/datasource.json
_server/datasource.json_
{
"db": {
"name": "db",
"connector": "memory"
},
"mysql": {
"host": "localhost",
"port": 3306,
"url": "",
"database": "loopback_tasks",
"password": "qwertyuiop",
"name": "mysql",
"user": "loopback",
"connector": "mysql"
},
"mongo":{
"host": "localhost",
"name": "",
"password": "",
"port": 27017,
"database":"loopback_tasks",
"connector": "mongodb"
}
}
I execute this script separately before running the API server. This creates the built-in model to datasource.
_server/bin/create-lb-tables.js_
var path = require('path');
var server = require(path.resolve(__dirname, '../server'));
var my = server.dataSources.mysql;
var mo = server.dataSources.mongo;
var lbTables = ['Owner','Task','AccessToken','ACL','RoleMapping','Role'];
my.automigrate(lbTables,function(er){
if (er) throw er;
console.log('Loopback tables [' + lbTables + '] created in '+my.adapter.name);
my.disconnect();
process.exit(0);
});
I also execute this script separately before running the API server. This creates the initial data in db.
_server/bin/sample-data.js_
var path = require('path');
var async = require('async');
var app = require(path.resolve(__dirname, '../server'));
var User = app.models.Owner;
var Task = app.models.Task;
var Role = app.models.Role;
var RoleMapping = app.models.RoleMapping;
var ACL = app.models.ACL;
User.create(
[
{email: "[email protected]", password: "qwerty"},
{email: "[email protected]", password: "qwerty"},
],
function(err, owners){
if (err) throw err;
console.log("Owners created: ",owners);
async.parallel({
role: async.apply(createRole),
tasks: async.apply(createTask),
acls: async.apply(createACLs),
}, function(err, results){
if (err) throw err;
console.log('Done!');
process.exit(0);
});
function createRole(cb){
Role.create({
name: 'admin'
}, function(err, role) {
if (err) return cb(err);
console.log(role);
// Make Bob an admin
role.principals.create({
principalType: RoleMapping.USER,
principalId: owners[0].id
}, function(err, principal) {
if (err) return cb(err);
console.log(principal);
return cb(null, true);
});
});
}
function createTask(cb){
Task.create(
[
{
task: "Arf",
description: "Arf arrrffff arf",
ownerId : owners[1].id
},
{
task: "Aww",
description: "Aww aww aw",
ownerId : owners[1].id
}
], function(err, task){
if (err) return cb(err);
console.log("Task created: ",task);
return cb(null, true);
//process.exit(0);
}
);
}
function createACLs(cb){
ACL.create(
[
{
"model":"Owner",
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"model":"Owner",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$unauthenticated",
"permission": "ALLOW",
"property": "post"
},
{
"model":"Owner",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$unauthenticated",
"permission": "ALLOW",
"property": "login"
},
{
"model":"Owner",
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"model":"Owner",
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
}
,
{
"model":"Task",
"accessType": ACL.ALL,
"principalType": ACL.ROLE,
"principalId": Role.EVERYONE,
"permission": ACL.DENY
},
{
"model":"Task",
"accessType": ACL.WRITE,
"principalType": ACL.ROLE,
"principalId": Role.AUTHENTICATED,
"permission": ACL.ALLOW,
"property": "create"
},
{
"model":"Task",
"accessType": ACL.WRITE,
"principalType": ACL.ROLE,
"principalId": Role.OWNER,
"permission": ACL.ALLOW,
"property": "upsert"
},
{
"model":"Task",
"accessType": ACL.READ,
"principalType": ACL.ROLE,
"principalId": Role.AUTHENTICATED,
"permission": ACL.ALLOW
}
/*,{
"model":"Owner",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "__create__Tasks"
}, {
"model":"Owner",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "__count__Tasks"
}, {
"model":"Owner",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "__findById__Tasks"
}, {
"model":"Owner",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__updateById__Tasks"
},{
"model":"Owner",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "__destroyById__Tasks"
},*/
],
function(err, acls){
if (err) return cb(err);
console.log(acls);
return cb(null, true);
});
}
}
);
Below is the sample output from the DEBUG=loopback.security.* node .. I notice that if I try to access Owner related API, the loopback calls the isInRole method. But I try to access Task related API, the loopback does not call the isInRole method.
Web server listening at: http://0.0.0.0:3000
Browse your REST API at http://0.0.0.0:3000/explorer
loopback:security:role isInRole(): $everyone +0ms
loopback:security:access-context ---AccessContext--- +1ms
loopback:security:access-context principals: [] +1ms
loopback:security:access-context modelName Owner +0ms
loopback:security:access-context modelId undefined +0ms
loopback:security:access-context property find +0ms
loopback:security:access-context method find +0ms
loopback:security:access-context accessType READ +0ms
loopback:security:access-context accessToken: +0ms
loopback:security:access-context id "$anonymous" +0ms
loopback:security:access-context ttl 1209600 +0ms
loopback:security:access-context getUserId() null +0ms
loopback:security:access-context isAuthenticated() false +0ms
loopback:security:role Custom resolver found for role $everyone +0ms
loopback:security:acl The following ACLs were searched: +1ms
loopback:security:acl ---ACL--- +1ms
loopback:security:acl model Owner +0ms
loopback:security:acl property * +0ms
loopback:security:acl principalType ROLE +0ms
loopback:security:acl principalId $everyone +0ms
loopback:security:acl accessType * +0ms
loopback:security:acl permission DENY +0ms
loopback:security:acl with score: +0ms 7495
loopback:security:acl ---Resolved--- +0ms
loopback:security:access-context ---AccessRequest--- +0ms
loopback:security:access-context model Owner +0ms
loopback:security:access-context property find +0ms
loopback:security:access-context accessType READ +0ms
loopback:security:access-context permission DENY +0ms
loopback:security:access-context isWildcard() false +0ms
loopback:security:access-context isAllowed() false +0ms
loopback:security:acl The following ACLs were searched: +18s
loopback:security:acl ---Resolved--- +0ms
loopback:security:access-context ---AccessRequest--- +0ms
loopback:security:access-context model Task +0ms
loopback:security:access-context property find +0ms
loopback:security:access-context accessType READ +1ms
loopback:security:access-context permission ALLOW +0ms
loopback:security:access-context isWildcard() false +0ms
loopback:security:access-context isAllowed() true +0ms
@pusaphil : Could you please explain what you're trying to achieve here by defining ACL rules in a boot-script?
I think the ACL for the model base User ('Owner' in my code) loads the default ACL for User model and the model base PersistedModel ('Task' in my code) do not have any ACL.
That's correct.
It would be great if you can provide me with your sample repo, or you can for our sandbox from here
@gunjpan, Thank you for responding.
I was trying to create the DB tables and sample data before staring the loopback application, like import initial DB and data for production setup. I put them inside server/bin instead of putting them inside of server/boot to prevent loopback from calling it again and again whenever I restart the application. I hope you get my point here. :)
The command is node server/bin/create-lb-tables.js && node server/bin/sample-data.js
After that, I run the loopback command: DEBUG=loopback.security.* node .
I already found the solution just today.
The User, AccessToken, Role, RoleMapping and ACL from DB works according to what I expected.
The following solutions work for me, and I want to share with you.
Please see below:
lbTables in the example according to what the model names are.dataSource at server/model-config.json to your desired dataSource - which is defined in server/datasources.jsondataSource at server/model-config.json to your desired dataSource (no need to 'extend' this model). When I tried to 'extend' the AccessToken model, there are _no AccessToken saved in the DB table_, so just used the built-in model. Don't forget to add this model in Creating database tables for built-in models.option property of your model JSON file. This is the reference: http://loopback.io/doc/en/lb2/Model-definition-JSON-file.html{
"name": "MyACL",
"base": "ACL",
"idInjection": true,
"options": {
"validateUpsert": true,
"mysql": {
"table": "MyACL"
}
},
...
}property field in ACL table is NULL; I noticed that the ACL definition with NULL values is not implemented. If you have a NULL value especially for property field, try to use ALL instead of NULL.@pusaphil : I'm glad that you found the workaround and thank you for taking the time to detail the solution.
Is this good to close now?
@crandmck Can you make sure the above points are documented in the relevant sections? They are all good points to note. @pusaphil Thanks for the detailed notes. :+1:
@gunjpan Yep, this issue is good to close.
@superkhau NP. :)
I know this issue was closed I while ago, but I was working on something similar and I was still having the same problem, I was creating my ACL but it did not have any effect to my API. To summarize I was just trying to deny find access to all the models using
ACL.create({
model: model,
accessType: "EXECUTE",
principalType: "ROLE",
principalId: "$everyone",
permission: "DENY",
property: "find"
}, function (err, acl) {
console.log('ACL entry created: %j', acl);
});
, but when I was testing from explorer to see if it worked I still was able to access all my models, the ACL had no effect.
After reading for so long I found this post , and it solve my problem.
Maybe I miss some part of the lb docs or a part of this issue's comments, but in case I help anyone, when you do ACL.create, you are just saving your ACL to your datasource, but that, apparently, does not have any impact on your app. You need to do app.models.MyModel.settings.acls.push(yourNewAcl) so that it will take effect over your app.
full example:
var acl = {
accessType: "EXECUTE",
principalType: "ROLE",
principalId: "$everyone",
permission: "DENY",
property: "find"
}
app.models.MyModel.settings.acls.push(acl);
It is obvious to me now, but I leave the comment in case I help anyone.
It does not matter if you create an acl table in the db and populate it with acls. The acls are read from the acl settings in the model, so you can push them in boot scripts or specify them in the model.
I have been trying to see its effect from the database and at last i decided to drop all acls in myacl table, it didn't have any effect on the app. What prompted all this is the question i was having, why push acls to the model settings during runtime when they are already in the database.
Most helpful comment
I know this issue was closed I while ago, but I was working on something similar and I was still having the same problem, I was creating my ACL but it did not have any effect to my API. To summarize I was just trying to deny find access to all the models using
, but when I was testing from explorer to see if it worked I still was able to access all my models, the ACL had no effect.
After reading for so long I found this post , and it solve my problem.
Maybe I miss some part of the lb docs or a part of this issue's comments, but in case I help anyone, when you do ACL.create, you are just saving your ACL to your datasource, but that, apparently, does not have any impact on your app. You need to do
app.models.MyModel.settings.acls.push(yourNewAcl)so that it will take effect over your app.full example:
It is obvious to me now, but I leave the comment in case I help anyone.