Lisa
Lisa

Reputation: 2139

Loopback ACL "Authorization Required"

I'm using Loopback3. I'm running into an issue with my ACL roles and I'm not sure what I did wrong. I want users in specific roles to be able to write data to the endpoint, and for some reason the user I have set up (which is in one of those roles) cannot write. I get an Authorization Required error.

I have 4 roles:

For this endpoint, all authenticated users can read the data, but only admin, internal, and bot users can write data, and only admin users can delete data.

Here's how I've got my ACLs defined:

"acls": [
    {
        "accessType": "*",
        "principalType": "ROLE",
        "principalId": "$everyone",
        "permission": "DENY"
    },
    {
        "accessType": "READ",
        "principalType": "ROLE",
        "principalId": "$authenticated",
        "permission": "ALLOW"
    },
    {
        "accessType": "WRITE",
        "principalType": "ROLE",
        "principalId": "admin",
        "permission": "ALLOW"
    },
    {
        "accessType": "WRITE",
        "principalType": "ROLE",
        "principalId": "internal",
        "permission": "ALLOW"
    },
    {
        "accessType": "WRITE",
        "principalType": "ROLE",
        "principalId": "bot",
        "permission": "ALLOW"
    },
    {
        "accessType": "DELETE",
        "principalType": "ROLE",
        "principalId": "admin",
        "permission": "ALLOW"
    }
],

I have two users set up, one is a bot and one is an admin. When I do a POST request to the API for either user I get the 'Authorization Required' error, even when doing it from the explorer interface. I can do a GET with no problem, but a POST gets a failure.

If I remove all the "WRITE" acl's and replace them with this, doing a POST works.

    {
        "accessType": "WRITE",
        "principalType": "ROLE",
        "principalId": "$authenticated",
        "permission": "ALLOW"
    },

So, I can make it happen, but I don't know why my custom roles are failing.

Edit: Here is how I created the users, since I don't actually have any kind of interface built yet.

module.exports = function (app) {
    let today = new Date();

    let admin = {
        name: 'admin',
        description: 'admin users',
        created: today.toJSON(),
        modified: today.toJSON()
    };

    let internal = {
        name: 'internal',
        description: 'Internal users',
        created: today.toJSON(),
        modified: today.toJSON()
    };

    let external = {
        name: 'external',
        description: 'external users',
        created: today.toJSON(),
        modified: today.toJSON()
    };

    let bot = {
        name: 'bot',
        description: 'robots',
        created: today.toJSON(),
        modified: today.toJSON()
    };

    let model = app.models.user;

    model.create([
        {username: 'bot', email: '[email protected]', password: 'test123'},
        {username: 'admin', email: '[email protected]', password: 'test123'},
        {username: 'iAdmin', email: '[email protected]', password: 'test123'},
        {username: 'eUser', email: '[email protected]', password: 'test123'},
    ], function(err, users) {
        if (err) throw err;

        app.models.Role.create(bot, function (err, botRole) {
            if (err) throw err;

            botRole.principals.create({principalType: app.models.RoleMapping.user, principalID: users[0].id}, function(err, principal) {
                if (err) throw err;
            });
        });

        app.models.Role.create(admin, function (err, adminRole) {
            if (err) throw err;

            adminRole.principals.create({principalType: app.models.RoleMapping.user, PrincipalID: users[1].id}, function(err, principal) {
                if (err) throw err;
            });
        });

        app.models.Role.create(admin, function (err, internalRole) {
            if (err) throw err;

            internalRole.principals.create({principalType: app.models.RoleMapping.user, PrincipalID: users[2].id}, function(err, principal) {
                if (err) throw err;
            });
        });

        app.models.Role.create(external, function (err, externalRole) {
            if (err) throw err;

            externalRole.principals.create({principalType: app.models.RoleMapping.user, PrincipalID: users[3].id}, function(err, principal) {
                if (err) throw err;
            });
        });
    });
};

Upvotes: 2

Views: 1443

Answers (4)

Lisa
Lisa

Reputation: 2139

The primary issues I was experiencing revolved around the following items:

  1. Use app.models.RoleMapping.USER instead of app.models.RoleMapping.user (which was changed in the codebase early on)

  2. Use principalId instead of principalID

Since the rest of the ACLs had been removed from the application some time ago due to them not working, I can't state whether or not they were also contributing to the problems, but I will be adding them in over time making sure to use the correct permissions for LB3.

Upvotes: 1

javadib
javadib

Reputation: 39

Actually, there are some syntax and logic error your code!

  1. app.models.RoleMapping.USER instead of app.models.RoleMapping.user (as @Farid said).
  2. There is no "DELETE" acceessType in loopback acl. you should use "*", "READ", "WRITE" or "EXECUTE" for accesType value. "WRITE" support "create", "updateAttributes", "upsert" and "destroyById" resoueces. (more details in loopback documentation).

Change the acl such following code:

"acls": [ { "accessType": "*", "principalType": "ROLE", "principalId": "$everyone", "permission": "DENY" }, { "accessType": "READ", "principalType": "ROLE", "principalId": "$authenticated", "permission": "ALLOW" }, { "accessType": "*", "principalType": "ROLE", "principalId": "admin", "permission": "ALLOW" }, { "principalType": "ROLE", "principalId": "internal", "permission": "ALLOW", "property": ["create", "updateAttributes", "upsert"] }, { "principalType": "ROLE", "principalId": "bot", "permission": "ALLOW", "property": ["create", "updateAttributes", "upsert"] } ]

  1. Finally, ensure that you perform role and roleMapping correctly.

Upvotes: 0

Njain
Njain

Reputation: 46

this is how your two table should look like :

GET role -->> [table - role]

[
  {
    "roleId": 1,
    "name": "admin",
    "description": "admin",
    "created": "2016-08-23T16:46:07.572Z",
    "modified": "2016-08-23T16:46:07.572Z"
  },
  {
    "roleId": 2,
    "name": "internal",
    "description": "internal",
    "created": "2016-08-23T16:46:07.574Z",
    "modified": "2016-08-23T16:46:07.574Z"
  }
]


Get Role Mapping  [table- user-role]-->>

[
  {
    "userRoleId": 123,
    "roleId": 2, //id of role i.e. 1 for admin 
    "principalType": "USER",
    "principalId": "1234",
    "created": null,
    "modified": null
  },
  ..
  ...
]

So by looking at your code, I can say that you are not adding roleId into roleMapping table.

First add all roles in roles table,

Now create user and add entry of that user in role mapping table and assign one roleID to it, see in above code where roleId 1 denotes Admin Role.

Upvotes: 0

Undrium
Undrium

Reputation: 2678

There is a difference between roles and dynamic roles/states ($everyone and $authenticated), while your roles get stored in a table to keep the association with the user $everyone and $authenticated is just a state of the user. Look at a table named "RoleMapping", if that is correct. Also if you have a custom Role, create a custom RoleResolver.

There is really good documentation, and really not that hard to get started in Loopback 3.

https://loopback.io/doc/en/lb3/Defining-and-using-roles.html

This example gave me a great understanding of roles in general:

https://github.com/strongloop/loopback-example-access-control/blob/master/server/boot/role-resolver.js

It's an entire project so you can check out the models there aswell.

Upvotes: 0

Related Questions