b24
b24

Reputation: 2473

loopback create user by admin but update profile by user

I try using below model to manage my user managment in a LoopBack 3 API:

{
  "name": "Employee",
  "plural": "Employees",
  "base": "User",
  "idInjection": true,
  "options": {
    "validateUpsert": true,
    "strict": true
  },
  "mixins": {
    "ClearBaseAcls": true
  },
  "hidden": [
    "password",
    "verificationToken"
  ],
  "properties": {
    "name": {
      "type": "string",
      "required": true
    },
    "family": {
      "type": "string",
      "required": true
    }
  },
  "validations": [],
  "relations": {},
  "acls":
    [ { "principalType": "ROLE", "principalId": "$everyone", "permission": "DENY" },
      { "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW", "property": "login" },
      { "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW", "property": "logout" },
      { "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW", "property": "confirm" },

      { "principalType": "ROLE", "principalId": "admin", "permission": "ALLOW" },

      { "principalType": "ROLE", "principalId": "$owner", "permission": "ALLOW", "property": "findById" },
      { "principalType": "ROLE", "principalId": "$owner", "permission": "ALLOW", "property": "updateAttributes" }
    ],
  "methods": {}
}

and Also use this mixis: (ClearBaseAcls)

'use strict';

const path = require('path');
const appRoot = require('app-root-path');

function slugify(name) {
  name = name.replace(/^[A-Z]+/, s => s.toLowerCase());
  return name.replace(/[A-Z]/g, s => '-' + s.toLowerCase());
}

module.exports = (Model) => {
  const configFile = path.join('./common/models/', slugify(Model.modelName) + '.json');
  const config = appRoot.require(configFile);

  if (!config || !config.acls) {
    console.error('ClearBaseAcls: Failed to load model config from', configFile);
    return;
  }

  Model.settings.acls.length = 0;
  config.acls.forEach(r => Model.settings.acls.push(r));
};

Now anything is OK for me.
Only admin can create new user and do anything.
$everyone only can login, logout and confirm account.
But I have some issue with $owner part. Because of user creation done by admin now 'admin' is owner of any other user and nobody can't use findById or updateAttributes (update profile).


Update:

I test again and new users can use findById ( GET /Employees/{id} ) (I don't know why it's worked but it's all we need) BUUUUT another issue, new users can't use updateAttributes ( PATCH /Employees/{id} ) and show bellow error:

Authorization Required

Why findById ( GET /Employees/{id} ) worked??
Why updateAttributes ( PATCH /Employees/{id} )??
Why this two not same?
Can you guide me how to fix this? I can't find anything about this.

Upvotes: 0

Views: 780

Answers (3)

b24
b24

Reputation: 2473

Finally I found a working solution: I change this line:

{ "principalType": "ROLE", "principalId": "$owner", "permission": "ALLOW", "property": "updateAttributes" }

to below line:

{ "principalType": "ROLE", "principalId": "$owner", "permission": "ALLOW", "property": "patchAttributes" }

I found it in this file.

It's a documentation issue here where updateAttributes changed to patchAttributes but it's not changed in docs.

Upvotes: 0

Overdrivr
Overdrivr

Reputation: 6576

But I have some issue with $owner part. Because of user creation done by admin now 'admin' is owner of any other user

If you are using the endpoint POST /Employees to create new users this is not true. Admin is not the $owner. See the loopback doc:

To qualify a $owner, the target model needs to have a belongsTo relation to the User model 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, you are redefining findById and updateAttributes acls, but they are already defined in loopback as part of the base user. So you can remove those 2 lines.

 { "principalType": "ROLE", "principalId": "$owner", "permission": "ALLOW", "property": "findById" },
 { "principalType": "ROLE", "principalId": "$owner", "permission": "ALLOW", "property": "updateAttributes" }

Now, you're having weird behavior with ACLS and you are using a custom mixin that messes with the acls registration. I don't understand precisely what this mixin is supposed to do but my best guess is that the issue is coming from it.

Remove this file from your project, restart your server and test without it. Should fix your problems.

Upvotes: 1

Based on your ACL currently only the owner of the record is able to update/delete any data, in order to allow another user you must change the ACL updateAttributes the principalId to $authenticated.

"acls":
[ 
  { "principalType": "ROLE", "principalId": "$authenticated", "permission": "ALLOW", "property": "updateAttributes" }
],

But this approach let to any authenticated user change any other profile so to prevent this action you can add a beforeRemote hook look for the current userId and the profile userId to compare them and allow if this values match.

Upvotes: 1

Related Questions