I created a custom object Foo and set ACL for method updateAttributes:
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "updateAttributes"
}
After login response of API is 401 Authorization Required.
The model Role checks if the instance of model is User or subclass of user #L145 and then checks for specific property attribute #L159
In my case Model Foo extends PersistedModel and attribute to check is id.
workaround for me -> var ownerId = inst.userId || inst.owner || inst.id;
For the $owner role to be functional, the target object needs to have a belongsTo relation to User model.
The model Foo is in substitution of built-in model User. In this case solution is to implement custom role?
My case: I have create my custom model User (extend PersistedModel) and I want to use special roles '$owner', '$related', '$authenticated', '$unauthenticated', '$everyone' with model ACL. is this possible?
+1
please update if you find a solution
any more insights?
so?
Actually developing something as trivial and as simple as an app that has a users each user can have a PRIVATE (_$owner_) notebook and only he updates and reads it just doesn't seem to work, I'v searched the documents and everything followed the tutorials and I get _Unauthorized 401_ when I set my ACL INTUITIVELY to this:
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
}
Not sure if I'm missing something out, but here is a better detailed case anyway.
When I use _principalId $authenticated_ everything is perfect when it is _$owner_, I get _401_
.
_ACL and RELATIONS_
(being is the the user extension)
"relations": {
"being": {
"type": "belongsTo",
"model": "Being",
"foreignKey": "userId"
},
"sections": {
"type": "hasMany",
"model": "Section",
"foreignKey": "notebookId"
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
}
],
_Client side request method_
(Using the api explorer returned the same status code too)
function getNotebook(){
return Notebook.findOne({
filter: {
where: {
userId: AuthService.getCurrentId()
},
}
})
}
Trying the method that _fetches a hasOne relationship_ doesn't work either.
function getNotebook(){
return User.notebook()
}
_The debug log_
loopback:security:role isInRole(): $everyone +35s
loopback:security:access-context ---AccessContext--- +0ms
loopback:security:access-context principals: +1ms
loopback:security:access-context principal: {"type":"USER","id":"55e9c65d941d3ec208953644"} +0ms
loopback:security:access-context modelName Being +0ms
loopback:security:access-context modelId 55e9c65d941d3ec208953644 +0ms
loopback:security:access-context property findById +0ms
loopback:security:access-context method findById +0ms
loopback:security:access-context accessType READ +0ms
loopback:security:access-context accessToken: +0ms
loopback:security:access-context id "CRZ63uz0YZDFslVOSXxOti52EC8XVEeeMqOIdWRcxpxm0Phghwvx4auSFmYKDDoB" +0ms
loopback:security:access-context ttl 1209600 +0ms
loopback:security:access-context getUserId() 55e9c65d941d3ec208953644 +0ms
loopback:security:access-context isAuthenticated() true +0ms
loopback:security:role Custom resolver found for role $everyone +0ms
loopback:security:role isInRole(): $owner +1ms
loopback:security:access-context ---AccessContext--- +0ms
loopback:security:access-context principals: +0ms
loopback:security:access-context principal: {"type":"USER","id":"55e9c65d941d3ec208953644"} +0ms
loopback:security:access-context modelName Being +0ms
loopback:security:access-context modelId 55e9c65d941d3ec208953644 +0ms
loopback:security:access-context property findById +0ms
loopback:security:access-context method findById +0ms
loopback:security:access-context accessType READ +0ms
loopback:security:access-context accessToken: +0ms
loopback:security:access-context id "CRZ63uz0YZDFslVOSXxOti52EC8XVEeeMqOIdWRcxpxm0Phghwvx4auSFmYKDDoB" +0ms
loopback:security:access-context ttl 1209600 +0ms
loopback:security:access-context getUserId() 55e9c65d941d3ec208953644 +0ms
loopback:security:access-context isAuthenticated() true +0ms
loopback:security:role Custom resolver found for role $owner +1ms
loopback:security:role isOwner(): Being 55e9c65d941d3ec208953644 userId: 55e9c65d941d3ec208953644 +0ms
loopback:security:acl The following ACLs were searched: +0ms
loopback:security:acl ---ACL--- +0ms
loopback:security:acl model Being +0ms
loopback:security:acl property findById +0ms
loopback:security:acl principalType ROLE +0ms
loopback:security:acl principalId $owner +0ms
loopback:security:acl accessType * +0ms
loopback:security:acl permission ALLOW +0ms
loopback:security:acl with score: +1ms 8016
loopback:security:acl ---ACL--- +0ms
loopback:security:acl model Being +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 Being +0ms
loopback:security:access-context property findById +0ms
loopback:security:access-context accessType READ +0ms
loopback:security:access-context permission ALLOW +0ms
loopback:security:access-context isWildcard() false +1ms
loopback:security:access-context isAllowed() true +0ms
loopback:security:role isInRole(): $everyone +110ms
loopback:security:access-context ---AccessContext--- +0ms
loopback:security:access-context principals: +0ms
loopback:security:access-context principal: {"type":"USER","id":"55e9c65d941d3ec208953644"} +0ms
loopback:security:access-context modelName Notebook +0ms
loopback:security:access-context modelId undefined +0ms
loopback:security:access-context property findOne +0ms
loopback:security:access-context method findOne +0ms
loopback:security:access-context accessType READ +0ms
loopback:security:access-context accessToken: +1ms
loopback:security:access-context id "CRZ63uz0YZDFslVOSXxOti52EC8XVEeeMqOIdWRcxpxm0Phghwvx4auSFmYKDDoB" +0ms
loopback:security:access-context ttl 1209600 +0ms
loopback:security:access-context getUserId() 55e9c65d941d3ec208953644 +0ms
loopback:security:access-context isAuthenticated() true +0ms
loopback:security:role Custom resolver found for role $everyone +0ms
loopback:security:role isInRole(): $owner +0ms
loopback:security:access-context ---AccessContext--- +0ms
loopback:security:access-context principals: +0ms
loopback:security:access-context principal: {"type":"USER","id":"55e9c65d941d3ec208953644"} +0ms
loopback:security:access-context modelName Notebook +0ms
loopback:security:access-context modelId undefined +0ms
loopback:security:access-context property findOne +0ms
loopback:security:access-context method findOne +1ms
loopback:security:access-context accessType READ +0ms
loopback:security:access-context accessToken: +0ms
loopback:security:access-context id "CRZ63uz0YZDFslVOSXxOti52EC8XVEeeMqOIdWRcxpxm0Phghwvx4auSFmYKDDoB" +0ms
loopback:security:access-context ttl 1209600 +0ms
loopback:security:access-context getUserId() 55e9c65d941d3ec208953644 +0ms
loopback:security:access-context isAuthenticated() true +0ms
loopback:security:role Custom resolver found for role $owner +0ms
loopback:security:acl The following ACLs were searched: +0ms
loopback:security:acl ---ACL--- +0ms
loopback:security:acl model Notebook +1ms
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 Notebook +0ms
loopback:security:access-context property findOne +0ms
loopback:security:access-context accessType READ +0ms
loopback:security:access-context permission DENY +0ms
loopback:security:access-context isWildcard() false +1ms
loopback:security:access-context isAllowed() false +0ms
Thanks.
For the $owner role to be functional, the target object needs to have a belongsTo relation to User model.
@raymondfeng where can I find about this in the documentation?
I'm having the same problem @upq is having. Everything seems to be alright but still gettin 401.
Please note that $owner role only works with following REST api pattern:
/api/myModels/:id/...
The id will be used to look up the myModel instance. Then it will be checked if it belongs to the current user.
@raymondfeng I wasn't passing the owner_id when creating the object with POST /api/MyModels. So the $owner role couldn't work.
Now I'm trying to figure out the best way to validate the owner_id that's passed on creation of MyModel so that a user can't add MyModel to other users.
"The id will be used to look up the myModel instance. Then it will be checked if it belongs to the current user."
@raymondfeng Is it possible to redefine method to determine the owner, for instance if I want to call method with objectName : /api/myModels/:name/ ?
Answering to myself: was easier than I initially thought.
Customer.beforeRemote('getTokens', function (ctx,customer,next) {
if (ctx.req.accessToken.customerid !== ctx.args['userid'] ) {
var errreply = new Error();
errreply.statusCode = 400;
next(errreply);
}
else {
next();
}
});
Note that the HTTP path of the target model's remote method _must_ use the substring :id for this to work.
This _will NOT_ work:
foo.remoteMethod('fooBar', { http: { path: '/:fooId/fooBar' } });
Whereas this _will_ work:
foo.remoteMethod('fooBar', { http: { path: '/:id/fooBar' } });
Loopback looks for an id parameter (that property name only) when doing the access control check.
https://github.com/strongloop/loopback/blob/master/lib/application.js#L350
I facing similar issue. In my case, I allow $owner to delete, but turn out everyone can delete.
I opened a same issue here according to @raymondfeng 's answer.
Please note that $owner role only works with following REST api pattern:
/api/myModels/:id/...
The id will be used to look up the myModel instance. Then it will be checked if it belongs to the >current user.
the loopback build-in acl only can check one iterms which indentitied by the id 锛宻o after login I want to get (READ) a list of iterms which belongs to current user is not possible. @raymondfeng is that right?
I have an extended class of User and the method Role.isOwner(modelClass, modelID,userID,callback) returns false. isUserClass function fails on the modelClass.prototype instanceof User. Any idea ?
@raymondfeng
For the $owner role to be functional, the target object needs to have a belongsTo relation to User model.
Does this mean if I have a extended user model (like AdminUser), $owner won't work even if I set the targeted model's relations as:
"relations": {
"owner": {
"type": "belongsTo",
"model": "AdminUser",
"foreignKey": "userId"
},
Thanks for helping out!
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
This issue has been closed due to continued inactivity. Thank you for your understanding. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository.
I just ran into this. I've been trying to get an example of "I must be logged in to CRUD and I can only work with my data" and I have only been able to make it such that I have to be logged in. I had user1 make 3 objects and not only could user2 see them all, they could also get them individually.
If it helps anyone to ensure one user doesn't supply a different userId or to let the service do it for you so you don't have to explicitly pass it in, here's a simple drop-in check. Just add it to your models that have a belongTo user relation:
Model.observe('before save', (ctx, next) => {
const fkBelongToName = 'userId';
const hasOwnerRelation = !!ctx.Model.definition.properties[fkBelongToName];
if (!hasOwnerRelation) {
return next();
}
const currentUserId = ctx.options.accessToken.userId;
// Enforce current userId to prevent unauthorized changes
const suppliedUserId = ctx.data ? ctx.data[fkBelongToName] : ctx.instance[fkBelongToName];
if (!suppliedUserId) {
// user id not supplied. force assign current user
if (ctx.data) {
ctx.data[fkBelongToName] = currentUserId;
} else {
ctx.instance[fkBelongToName] = currentUserId;
}
} else if (suppliedUserId !== currentUserId) {
const err = new Error();
err.statusCode = 401;
err.message = 'Supplied userId reference does not match the current user';
err.code = 'AUTHORIZATION_REQUIRED';
return next(err);
}
return next();
});
Most helpful comment
Please note that
$ownerrole only works with following REST api pattern:/api/myModels/:id/...
The
idwill be used to look up the myModel instance. Then it will be checked if it belongs to the current user.