How can I filter REST calls results based on Roles and current user context

54 views
Skip to first unread message

Emmanuel P.

unread,
May 29, 2016, 2:00:28 PM5/29/16
to LoopbackJS
Given the following:

There are 3 models:

  1. Company
  2. Employee (derived from User)
  3. Position

Company is linked to Employee through Position (has many, has many)


There are 2 Roles:

  1. admin
  2. user
I would like to configure my REST api as follow:

When an admin is logged, can access all REST functions.

  "accessType": "*",
  "principalType": "ROLE",
  "principalId": "admin",
  "permission": "ALLOW"
 

When a user is logged:

  • GET /companies : Only return the companies in which the current user has a position.
  • GET /companies/#id: Only allow if the current user has a position in this company.

Emmanuel P.

unread,
May 31, 2016, 2:13:42 PM5/31/16
to LoopbackJS
I've wrote the following solution, which is working find, but I'd like to know if there are other solutions
Thanks

----------


The procedure follows those steps:

1: Access current user userID through the loopback current context.

*If there is no authenticated user, exit the function.*

2: Load Role of the current user, using the RoleMapping table

*If the current user Role is not "user", exit the function.*

3: Load Positions of our current user and create an array of the companies Id he's working in.

4: rewrite the current query

 - for /companies calls, inject a where condition for id
 - for /companies/#id, test if requested id is matching one of the allowed ids, if not, return an error 401


----------

    Company.observe('access', function (ctx, next) {
       
/* Observe the access to companies
         *  If the role of the logged user is 'user', will restrict access to only custom set of data
         *  Otherwise, will access all data */

   
       
// Access loopback current Context to get userID through accessToken
       
var loopbackCtx = loopback.getCurrentContext();
       
var accessToken = loopbackCtx && loopbackCtx.get('accessToken');
       
var userId = accessToken && accessToken.userId;
   
       
if (!userId) {
         
// without connected user. proceed without hook
         
return next();
       
}
   
       
// Will perform a query in the RoleMapping Model to findout the current role of connected user
       
var RoleMapping = app.models.RoleMapping;
       
var roleQuery = {
         
where: {
           
"principalId": userId,
           
"roleId": 2 // role 2: user
         
}
       
};
   
       
RoleMapping.findOne(roleQuery, function (err, result) {
   
         
if (!result) {
           
//no matching role, proceed without hook
           
return next();
         
}
   
         
// found one match in the RoleMapping table. must now restrict results in accordance with positions of the current employee
   
         
// Looking for positions for the current employee
         
var position = app.models.position;
         
var positionQuery = {
           
where: {
             
"employeeId": userId
           
}
         
};
   
          position
.find(positionQuery, function (err, results) {
           
// from the position list, create a list of companies
   
           
var allowedCompanies = [];
            results
.forEach(function (result) {
              allowedCompanies
.push(result.companyId);
           
});
   
           
//use the list of allowed companies to restrict results
           
if (!ctx.query.where) {
             
// typically call from a find() without arguments (findall)
             
// will inject a new condition
              ctx
.query = {
               
where: {
                 
"id": { inq: allowedCompanies}
               
}
             
}
           
}
           
else {
             
if (ctx.query.where.id && Number.isInteger(ctx.query.where.id)) {
               
// typically call from a find({ id: .. )}
               
// will authorize or not access to the company data
                console
.log(ctx.query.where.id);
               
if ( allowedCompanies.indexOf(ctx.query.where.id) == -1 ) {
                 
// the id is not in the permited scope, will return a 401 error
                 
var error = new Error();
                  error
.name = "Error";
                  error
.status = 401;
                  error
.statusCode = 401;
                  error
.message = 'Authorization Required';
                  error
.code = 'AUTHORIZATION_REQUIRED';
                 
return next(error);
               
}
             
}
             
// other calls (with inq) are not yet implemented
           
}
   
           
return next();
         
});
   
       
});

   
});

Reply all
Reply to author
Forward
0 new messages