Reputation: 100386
We are using Loopback for Node.js RESTful APIs. We are wondering if we can utilize the inborn Accesss Control Lists property on models, to restrict access to only certain records/objects to certain users. I am hoping that it's possible and that we don't need to implement our own logic. Say we have two tables like so (in Postgres):
contracts
| id | name | manager_id |
|----|------|-------------|
| 1 | a | 4 |
| 2 | b | 5 |
| 3 | c | 6 |
contract_managers
| id | name |
|----|------|
| 4 | e |
| 5 | f |
| 6 | g |
what we want is to implement low level access control to the contracts table - restricting user access to only certain records. Given the docs, it's not yet clear if we can restrict access to particular records, using ACLs alone.
If someone is logged into our application and they are a contract_manager, we want them to only be able to read records from the contracts table for records where the manager_id is the logged in user id. For example, if I am logged in and my user id = 4, I am thus a contract_manager, and I should only be able to read from the contracts table where the manager_id = 4.
Is there a way to implement this simple logic with ACLs? Or do we need to create custom logic?
Upvotes: 0
Views: 205
Reputation: 30430
You can do this if you define a custom role resolver, and then add an ACL entry with that role limiting the particular access.
Role.registerResolver('$manager', function (role, ctx, callback) {
if (ctx.modelName === 'Contract') {
app.models.Contract.count({
id: ctx.modelId,
// I'm assuming the ContractManager model extends User model.
// If instead there's a relation between ContractManager and User,
// Use include and scopes to filter it.
managerId: ctx.accessToken.userId,
}, function(err, count) {
if (err) {
callback(err);
} else if (count) {
callback();
} else {
callback(new Error('Not Manager'));
}
});
} else if (ctx.modelName === 'ContractManager') {
// Again making the same assumption about ContractManager. Chnage
// accordingly.
if (ctx.accessToken.userId === ctx.modelId) {
callback();
} else {
process.nextTick(() => callback(new Error('Not Manager')));
}
} else {
process.nextTick(() => callback(new Error('Only for ContractManager or Contract')));
}
}
Then you have to add this acl(Feel free to make it more specific):
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$manager",
"permission": "ALLOW",
"property": "*",
"model": "*"
}
Now note that this role resolver works only with prototype methods(ie findById, /api/Contract/{id}
) and not with static methods(ie find, /api/Contract
), since we use the ctx.modelId, which is only available for prototype method calls.
Upvotes: 1