There are many use-cases or scenario's where we felt like, we need some custom roles in our application, apart from the provided built in ones ($authenticated/$owner/$everyone).
LoopBack 3 provides a flexibility, where we can define custom roles, based on our application requirements. It has majorly divides the custom roles into two categories, (Static and Dynamic one's).
Generally a beginner, who have just started looking into what loopback is, struggles in finding, what will be the scenario, where we should use static role or dynamic one, how they are different, and from where should he start.
In this article, i will explain each of them, with common scenarios/examples.
Static Roles : Suppose you owe a restaurant, and it has both managers and waiter's, manager's role is mostly related to administration, and waiter's role is mainly to deal with the customer orders.
Manager role is also to decide, what should be the rate of any specific dish, at any particular season, and also to decide the menu list, based on the season and the availability. No waiter can modify the rates of it.
So, now we have a predefined role and its responsibilities, that, only a person with role manager can do the above mentioned things. So, this is pretty straight forward. We know that the manager is a user, but with some administration responsibilities.
So, how will we assign the user with this manager responsibilities in loopback :
1. We create a user in the user model.
2. We define a new role called "Manager" (Role Model).
3. We mapped the newly created user with the newly created role. (Role Mapping Model).
All of the above are predefined/built in models, we just need to create a entry in it. Now, we have defined the role, but not have defined its responsibilities.
Suppose, we have our custom model's called (Rate List) and (Menu List), we can define acl's on it, that, only a principal type "Role", with a principal id "Manager" can perform update/create operations on it, and all other's can only read/view it.
Sample, acl's for it.
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "manager",
"permission": "ALLOW",
"property": "create"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "manager",
"permission": "ALLOW"
}
So, static role is something, where you do not need to query multiple custom model's for checking, whether we should allow the request or not. It is pretty straight forward, you have the user and its mapping done.
It is generally used, for implementing a restriction at the high level.What i mean by that is, suppose in the IT park, where lot of organization have offices, now a person is only allowed in the campus, if he has the company id card. So, we will only do a high level checking here :
a. First that company should have a office in the campus.
b. Should be a valid employee of that company.
Now, what if he swipes a card on the entry gate of any restricted area. We need to check, if his card is still valid or expired, do he has the enough permission or not. To check this we will need to query our models, if this card id is still valid or not, whether he/she access to that gate/room or not. So, here comes the role of dynamic roles, you cannot directly check if it belongs to that role, and let him go. You need to take decision here, based on other responses, and then only allow/reject the request.
Lets discuss this in detail, with different example.
Dynamic Roles : Suppose you have a leave management system, where every employee of your company can submit his request, and only a manager of that employee/or either HR manager can approve/reject the request.
Following ?
So, here we have two models till now :
A. User model, consist of all employees, having a assigned designated manager.
B. Time-Off model, consist of all time offs.
Roles : HR Manager (static role), employee (static role) can create time off request.
So, suppose employee "B" is a manager of "C" & "D", and employee "A" is a manager of "B". i.e. C & D reports to B, and B reports to A, and "H" is the HR manager.
So, suppose, employee "C" has applied for a leave, and now we want only his manager or HR can approve his time off.
Static role, won't work in this case, as just by checking the "manager" role, we will not be able to identify whether he is actually his manager or not. We need to query user table, with the current user id, to find, whether he is actually the manager of the "C" or not, then only we can allow him to update/approve the request.
In overall, i mean to say that, every time we need to take decision dynamically, based on the requested context( C or B, or D) find out whether current user is his manager or not, or he/she is the HR manager, and then only allow the request to be executed. So, we can do this with dynamic roles.
Dynamic roles, can be defined by writing the role resolver, and then register it in the boot script.
Example : role-resolver.js (inside boot directory) sample code.
module.exports = function(app) {
var Role = app.models.Role;
Role.registerResolver('approver', function(role, context, cb) {
if (context.modelName !== 'Timeoff') {
return cb(null, false);
}
var managerId = context.accessToken.userId;
if (!managerId) {
return cb(null, false);
}
context.model.findById(context.modelId, function(err, timeoff) {
if(err) return cb(err);
if(!timeoff) return cb(new Error("Timeoff not found"));
let User = app.models.User;
User.count({
userId: timeoff.empId,
managerId: managerId
}, function(err, count) {
if (err) return cb(err);
if(count > 0){
return cb(null, true);
}
else{
return cb(null, false);
}
});
});
});
};
And then on the Timeoff model, we can apply the acl for the same.
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": ["approver"],
"permission": "ALLOW",
"property": "approve"
}
I have applied the acl on the remote method "approve". So, if we have directly applied on all the methods, of write, then only manager can update the item(change date/reason), not even the employee who has created it. So, that is the reason custom remote endpoint is created.
Also, we can create a relation of Timeoff model with the User (belongsTo) , then we can add one more acl to the Timeoff model. $owner for rest of the write methods, so only the user/employee who have created the request can only modify it. No, other employee should modify each other timeoff's request accidentally.
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
}
The whole idea of this blog, to give understanding of static/dynamic roles in loopback 3, and, how we can use it in our application. Hope this helps!.
LoopBack 3 provides a flexibility, where we can define custom roles, based on our application requirements. It has majorly divides the custom roles into two categories, (Static and Dynamic one's).
Generally a beginner, who have just started looking into what loopback is, struggles in finding, what will be the scenario, where we should use static role or dynamic one, how they are different, and from where should he start.
In this article, i will explain each of them, with common scenarios/examples.
Static Roles : Suppose you owe a restaurant, and it has both managers and waiter's, manager's role is mostly related to administration, and waiter's role is mainly to deal with the customer orders.
Manager role is also to decide, what should be the rate of any specific dish, at any particular season, and also to decide the menu list, based on the season and the availability. No waiter can modify the rates of it.
So, now we have a predefined role and its responsibilities, that, only a person with role manager can do the above mentioned things. So, this is pretty straight forward. We know that the manager is a user, but with some administration responsibilities.
So, how will we assign the user with this manager responsibilities in loopback :
1. We create a user in the user model.
2. We define a new role called "Manager" (Role Model).
3. We mapped the newly created user with the newly created role. (Role Mapping Model).
All of the above are predefined/built in models, we just need to create a entry in it. Now, we have defined the role, but not have defined its responsibilities.
Suppose, we have our custom model's called (Rate List) and (Menu List), we can define acl's on it, that, only a principal type "Role", with a principal id "Manager" can perform update/create operations on it, and all other's can only read/view it.
Sample, acl's for it.
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "manager",
"permission": "ALLOW",
"property": "create"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "manager",
"permission": "ALLOW"
}
So, static role is something, where you do not need to query multiple custom model's for checking, whether we should allow the request or not. It is pretty straight forward, you have the user and its mapping done.
It is generally used, for implementing a restriction at the high level.What i mean by that is, suppose in the IT park, where lot of organization have offices, now a person is only allowed in the campus, if he has the company id card. So, we will only do a high level checking here :
a. First that company should have a office in the campus.
b. Should be a valid employee of that company.
Now, what if he swipes a card on the entry gate of any restricted area. We need to check, if his card is still valid or expired, do he has the enough permission or not. To check this we will need to query our models, if this card id is still valid or not, whether he/she access to that gate/room or not. So, here comes the role of dynamic roles, you cannot directly check if it belongs to that role, and let him go. You need to take decision here, based on other responses, and then only allow/reject the request.
Lets discuss this in detail, with different example.
Dynamic Roles : Suppose you have a leave management system, where every employee of your company can submit his request, and only a manager of that employee/or either HR manager can approve/reject the request.
Following ?
So, here we have two models till now :
A. User model, consist of all employees, having a assigned designated manager.
B. Time-Off model, consist of all time offs.
Roles : HR Manager (static role), employee (static role) can create time off request.
So, suppose employee "B" is a manager of "C" & "D", and employee "A" is a manager of "B". i.e. C & D reports to B, and B reports to A, and "H" is the HR manager.
So, suppose, employee "C" has applied for a leave, and now we want only his manager or HR can approve his time off.
Static role, won't work in this case, as just by checking the "manager" role, we will not be able to identify whether he is actually his manager or not. We need to query user table, with the current user id, to find, whether he is actually the manager of the "C" or not, then only we can allow him to update/approve the request.
In overall, i mean to say that, every time we need to take decision dynamically, based on the requested context( C or B, or D) find out whether current user is his manager or not, or he/she is the HR manager, and then only allow the request to be executed. So, we can do this with dynamic roles.
Dynamic roles, can be defined by writing the role resolver, and then register it in the boot script.
Example : role-resolver.js (inside boot directory) sample code.
module.exports = function(app) {
var Role = app.models.Role;
Role.registerResolver('approver', function(role, context, cb) {
if (context.modelName !== 'Timeoff') {
return cb(null, false);
}
var managerId = context.accessToken.userId;
if (!managerId) {
return cb(null, false);
}
context.model.findById(context.modelId, function(err, timeoff) {
if(err) return cb(err);
if(!timeoff) return cb(new Error("Timeoff not found"));
let User = app.models.User;
User.count({
userId: timeoff.empId,
managerId: managerId
}, function(err, count) {
if (err) return cb(err);
if(count > 0){
return cb(null, true);
}
else{
return cb(null, false);
}
});
});
});
};
And then on the Timeoff model, we can apply the acl for the same.
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": ["approver"],
"permission": "ALLOW",
"property": "approve"
}
I have applied the acl on the remote method "approve". So, if we have directly applied on all the methods, of write, then only manager can update the item(change date/reason), not even the employee who has created it. So, that is the reason custom remote endpoint is created.
Also, we can create a relation of Timeoff model with the User (belongsTo) , then we can add one more acl to the Timeoff model. $owner for rest of the write methods, so only the user/employee who have created the request can only modify it. No, other employee should modify each other timeoff's request accidentally.
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
}
The whole idea of this blog, to give understanding of static/dynamic roles in loopback 3, and, how we can use it in our application. Hope this helps!.