The container Model looks like this:
{
"name": "container",
"base": "Model",
...
"acls": [
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY",
"accessType": "*"
},
{
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"accessType": "WRITE"
}
]
}
When I post an image to it it returns a 401 error. I'm sure that I'm logged in as an 'admin' user because I make two requests with the same access token and the same acl in the json model. For the first (not container) it works, but for the containers one not.
Is there a problem with the container ACL?
-----Edit-----
Starting with DEBUG=loopback:security:acl it returns:
---ACL---
model container
property *
principalType ROLE
principalId $everyone
accessType *
permission DENY
with score: 7495
---ACL---
model container
property *
principalType ROLE
principalId admin
accessType WRITE
permission ALLOW
with score: -1
"property": "*" is default in acl.
For the first request (not container) it returns the same but admin-write-allow has o higher score than $everyone-*-deny.
Setting "score": -2 doesn't work.
You are not defining a property (a method basically), so I guess the ACl resolution gives higher weight to your first then second.
"acls": [
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY",
"accessType": "*"
},
{
"principalType": "ROLE",
"principalId": "admin",
"property": "*", // Add this line
"permission": "ALLOW",
"accessType": "WRITE"
}
]
But then, ACL can be tough to figure out sometimes. I would recommend using debug string to see exactly what the ACL system has resolved:
On windows:
set DEBUG=loopback:security:acl && node .
EDIT:
The issue was actually coming from the accessType (requested endpoint was EXECUTE instead of WRITE), thus ACL not resolved as expected.
Related
I would like to get some informations about my user with loopback.
For that I created a "user" model related with "accessToken" model until now a POST on /user, a POST on /user/login and a POST on /user/logout is working.
I added on /common/models/user.json
{
"name": "user",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {
"accessTokens": {
"type": "hasMany",
"model": "accessToken",
"foreignKey": "userId"
}
},
"acls": [
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "logout"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "findById"
}
],
"methods": {}
}
And when I do a GET on /user/{id} I got :
{
"error": {
"statusCode": 401,
"name": "Error",
"message": "Autorisation requise",
"code": "AUTHORIZATION_REQUIRED",
"stack": "Error: Autorisation requise\n at..."
}
}
I guess I didn't understand acl/relation very well
This could be because you are only allowing $owner to findById:
To qualify a $owner, the target model needs to have a belongsTo relation to the User model (or a model that extends User) and property matching the foreign key of the target model instance. The check for $owner is performed only for a remote method that has ‘:id’ on the path, for example, GET /api/users/:id.
Make sure the accessToken you are providing is the owner of the id of the user you are looking for.
If you are not sure, try to replace:
"principalId": "$owner" with "principalId": "$authenticated", then you'll know if that's your problem.
I am having two models : user and car
user acl :
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "find"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "login"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
}
car acl :
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "find"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
}
I am able to add car using api /cars but unable to get list of cars using api /cars , during get it is returning AUTHORIZATION_REQUIRED error code.
note : i am logged in as a user then i am trying to get list of cars owned by user.
It's possible that you're missing to add the model relation in agreement with Loopback documentation:
To qualify a $owner, the target model needs to have a belongsTo relation
to the User model (or a model extends from User) and property matching
the foreign key of the target model instance. The check for $owner is
only performed for a remote method that has ‘:id’ on the path, for
example, GET /api/users/:id.
Also I believe it would be a good idea change the default setting aclErrorStatus from 401 to 403. This way it would be easier to understand if it's an issue with authentication or authorization.
I am new to loopback. I am trying to learn & implement ACL.
I have one "PersistedModel" named 'Page'. I am using two different models as 'Employee' and 'Customer', both based on built-in 'User' model.
Relation:
A page belongs to an 'Employee' as well as a 'Customer'. Both should be owners for a page. Customer & Employee both has many Pages. So, I have added following in relations:
"customer": {
"type": "belongsTo",
"model": "Customer",
"foreignKey": "customerId"
},
"employee": {
"type": "belongsTo",
"model": "Employee",
"foreignKey": "employeeId"
}
ACL:
I want 'WRITE' permission only for owners. So, I have added following in acls:
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
}
When I try a patch request, owner customer's request gets executed successfully. But, owner employee's request gets 'Authorization error'.
What am I doing wrong here?
LoopBack documentation is updated recently and they have added these two lines in a notice: (http://loopback.io/doc/en/lb3/Using-built-in-models.html#user-model)
LoopBack does not support multiple models based on the User model in a single application. That is, you cannot have more than one model derived from the built-in User model in a single app.
So basically, I should not have created two different models those are based on 'User' model. :(
Loopback only checks for one owner relation here.
For two owner you need to write your own custom role and register by role resolver
As loopback said, you should one extend model from User model, define role (from Role model), and assign role to any user or participant via RoleMapping model (it's built-in model).
for example:
Extend model from user model and named to MyUser.
Create 'Customer' and 'Employee' role.
Add relation:
"customer": {
"type": "belongsTo",
"model": "MyUser",
"foreignKey": "customerId"
},
"employee": {
"type": "belongsTo",
"model": "MyUser",
"foreignKey": "employeeId"
}
in MyUser model, add the following ACL:
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "Customer",
"permission": "ALLOW"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "Employee",
"permission": "ALLOW"
}
As per this commit (loopback 3.13.0 released on Sept 28, 2017), you can set {ownerRelations: true} to
I'm trying to execute this request:
PUT /api/cars/564d8e792583afef310affe3/categories/rel/suv-idcat
This works fine if I'm logged in as administrator, but if I'm logged in as another role, I get a 401 response.
My Car model has the following ACLs:
...
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "__create__categories"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "__updateById__categories"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW",
"property": "__destroyById__categories"
}
...
Now, if I add this same ACL rules for a specific role:
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "StoreAdmin",
"permission": "ALLOW",
"property": "__create__categories"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "StoreAdmin",
"permission": "ALLOW",
"property": "__updateById__categories"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "StoreAdmin",
"permission": "ALLOW",
"property": "__destroyById__categories"
}
...
If I tap the endpoint logged in as a StoreAdmin user, then I get a 401 error response.
P.S. I've already taken a look at this: https://docs.strongloop.com/display/public/LB/Accessing+related+models, but there is no "hasAndBelongsTo" relation
I'll answer myself. It turns out that for the hasManyAndBelongsTo relation, the method names are not the same as the ones stated in the documentation for a hasMany relation. In fact, it is not even documented.
By running the app in debug mode: DEBUG=loopback:security:* I found out that the real method name was __link__categories.
I've been reading the loopback docs about roles. They state the following:
To qualify a $owner, the target model needs to have a belongsTo
relation to the User model (or a model extends from User) and property
matching the foreign key of the target model instance. The check for
$owner is only performed for a remote method that has ':id' on the
path, for example, GET /api/users/:id.
However, what happens when I have a "hasMany" relation and want to perform an action on some object like this:
PUT myusers/123/news/456
This would be my user.json:
{
"name": "MyUser",
"plural": "myusers",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {
"news": {
"type": "hasMany",
"model": "News",
"foreignKey": ""
}
},
"acls": [],
"methods": []
}
Based on this, this and this. I've changed the MyUser entity to Writer entity, because I like it.
As the Writer entity has many News, the News relation and ACL should be something like this (news.json).
"relations": {
"writer": {
"type":"belongsTo",
"model":"Writer",
"foreignKey":"writer_id"
}
},
"acls": [
{ // Nobody has access to nothing
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{ // But everyone can read everything
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
},
{ // And authenticated users can create news
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "create"
},
{ // And the owner of a news can update it
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
}
],
And the Writer entity has the same ACL rules but this relations (writer.json)
"relations": {
"news": {
"type": "hasMany",
"model": "News",
"foreignKey": "writer_id"
}
}
What really happens here is that, when you create a Writer, you must specify email and password becouse he inherited from User model. So if you want to perform the
PUT writers/123/news/456
You must be logged has a Writer which can be done in this endpoint: /api/writers/login (with email+password). This endpoint is going to give you the Writer token and then you'll be able to perform the update on the News if you has the $owner token on your header, url or form.
On the other hand, you can also get the user who is doing the HTTP Request and put that user has the news owner with a hook.
Hope it helps. Regards.