The design of User Permissions is dangerous

This issue has come up a few times in the past, but it has never gotten much traction. I am bringing it up again because Frappe/ERPNext is at the center of some very heated discussion right now, taking place on a professional listserv I belong to.

At one organization, a glitch in the User Permissions creation process lead to an employee being able to see all of the salary slips for everyone in the organization going back several years. Those involved are blaming the design of User Permissions. Though I am usually a strident Frappe evangelist in my professional circles, in this case I am very inclined to agree with them.

Background

For those who don’t know, User Permissions provide another layer of permissions management.

  • Most people are familiar with the Role Permission Manager, which allows System Managers to give different types of access to different doctypes based on Role assignments.
  • User Permissions instead filter permissions by specific fields. So, a company might want department managers to see and process Expense Claims, but only for their own department. User Permissions make this possible.

The implementation of this feature is clever and flexible, using automatic scanning of link fields to allow User Permissions to extend over a wide range of doctypes automatically. That much is great.

The Problem

There is one massive flaw: User Permissions work backwards.

Role Permissions are additive. If I want my HR staff to have read permissions for all Salary Slips, I have to create a role and then a rule that grants read permissions to that role.

User Permissions, on the other hand, are subtractive. I don’t grant permission to a specific field value, but rather I take away permissions from everything but certain specified values.

The result: if I want to grant employees access to only their own salary slips, I first have to give them access to all salary slips with a Role Permission and then restrict that access to only their own with a User Permission.

This design goes against all best practices.

The Result

It’s obvious where this is going. Something went wrong. HR made a new employee, and they linked that employee to a new user account. The “Create User Permissions” checkbox on the Employee doctype was supposed to make sure that this new employee could see only their own Salary Slips, but for whatever reason those User Permissions didn’t get created properly. It might have been a server glitch, a bug in the code, or human error. Who knows.

An automated process silently failed, and as a consequence this organization experienced a significant internal data breach. This is every System Manager’s nightmare.

In hindsight, I’m not sure what they should have done differently. Maybe they should have been manually auditing their permissions more frequently. It’s easy to say that in retrospect. In any case, right now, auditing this effectively requires manual comparison of three different unlinked doctypes. This is not a good situation to be in.

Proposal

I don’t think there is any good argument for keeping User Permissions the way they currently are. The design is inherently dangerous.

But, if the consensus is to keep the current functionality, I would propose at least making a way out of it. One possible approach: in the Role Permission Manager, create a new checkbox option parallel to the “Is Owner” option that can toggle how User Permissions are interpreted:

This checkbox, if set true, would stipulate that the Role-based permission does nothing until user permissions are explicitly set. This would make User Permissions Additive, allowing System Managers to create User-specific permissions that fall back to no access (not full access like happens now).

I’ve looked into permissions.py, and this looks like a relatively lightweight change, both in terms of code complexity and performance. I’m not asking somebody else to do this work for us. I’m very willing to write the code and put it through review. I’m posting here first, however, because permissions changes are always high stakes.

If I get confirmation from the maintainers that there’s support for something like this, I’ll put together a PR. I posted about this last year and didn’t get a response, but if there is interest now I am very eager to put in the time and resources.

21 Likes

I agree,

in SAP, it is called org level, there is wild card (*) which means full access supported. so in user permission, we need to consider * as valid value to grant some power users full access.

1 Like

Are any maintainers able or willing to comment on this?

Sharing szufisher’s proposal and PR for reference

In case of frappe apps (no erpnext) I have a workaround. May not be appropriate for employee case. Sharing here if anyone else finds it helpful.

  • user given basic role on login to create a “Permission Request”.
  • fresh user selects and create request to ask for Roles and User Permission.
  • on approval of the request by user with higher role, programmatically create User Permission and assign roles.
  • instead of updating role and user permission directly, update programmatically through the “Permission Request” workflow and doc_events. You can even track changes here.
3 Likes

This is what I thought before, there is another web based workflow tool to handle user / role application and approval, sync to SAP in the backend, your proposed workaround fully integrate within EN is a valid and viable option.

If possible please kindly share more details, the permission request doctype definition, and hooked event code etc. thanks.

Are you suggesting using an external application for authorization management? Is that possible? I’m aware of the has_permission hook, but I thought that could only restrict permission, not add them.

It is just an idea that I propose. Nothing in code.

Permission Request

  • user: link to User.
  • requested_roles: child for Role.
  • granted_roles: child for Role.
  • child tables for link fields of DocType to restrict, and data required for creating User Permission.
  • Only “Permission Manager” allowed to enter and update granted_roles and approve user permission.
  • Track Changes if needed.

Events:

  • on_update: check granted_roles and assign roles to self.user.
  • on_trash: remove Roles and User Permission for self.user.
  • validate: optionally validate as per your workflow.

permission_request.js:

  • auto populate granted_roles from requested_roles.
  • any other convenience scripts for approving user.

Additional ideas:

  • Add Webhook / Event Streaming for propagating “Permission Request”
  • Permission Dashboard can be a separate site which propagates or manages Role and User permission across different sites. Use service account user / api keys to make changes in other sites. In case of multi-site enterprise.
  • If using SSO, user creation on remote sites can also be done with this API. That way the user gets access to any site seamlessly after “Permission Request” activity is complete.

External Application just does things what user would have done, it does it programatically. That way it can be tracked and improved and possibly reduce human error. Human intervention is restricted to approving, updating or rejecting “Permission Request”.

I appreciate the utility of the workflow. That’s the process the Employee document tries to automate too. I would still say though that it doesn’t actually fix the issue, which is that it is necessary to give users universal access before that access can be restricted by another document.

2 Likes

It’s not a fix! It’s a workaround that may work for some. Shared it as idea if it helps someone.

2 Likes

Ah, I misunderstood what you were saying! Thanks for sharing the insight.

Oh boy ! This would be awesome !!! I know some seasoned Frappe implementation teams that still struggle with this design because it’s just counter-intuitive. Several times users are given access that they shouldn’t have just because of this very issue… still had to fix one just a couple of days ago

The suggestion you have made does seem very light but would make a WORLD of difference. I hope this gets built and merged yesterday !

Kind regards,

4 Likes

I had this problem some time ago and it is certainly scary every time that I have to give permissions to a user, not to say give the responsibility to someone else.

Even though it looks like it’s going to be another year without any attention to this, your post is of great help for us, the community, specially the newcomers.

I wish there could be a non official ERPNext Tips and “be aware of” this kind of things

Thanks Peter

@peterg

This change alone seems small and harmless. We can add this for sure, can you send a PR?

9 Likes

Thanks @ankush, I’ll dig into it.

1 Like

WIP PR: feat: Explicit User Permissions by ankush · Pull Request #24254 · frappe/frappe · GitHub

3 Likes

Thanks @peterg for the explanation and highlighting this problem. I had written in past that ERPNext has serious confidentiality issues as user is able to escape their permissions - as certain standard reports do not force the use of filters (default is blank).

Dont know if this fix will address that??? A branch accounts user (assigned to specific accounting dimension or cost center) given access to the standard P&L or Balance sheet report can see the P&L for the whole company even if a user permission was set to only allow documents for the branch cost center.

1 Like

Hi @ankush

Looks like this PR went cold… Is there a plan to revive it or is the issue being addressed some other way ?

Kind regards,

So as I see, there is no implementation of any of this permission features yet? How did @szufisher handle the problems? I would love to help to implement this. I really need a strong permission system and the current one is really bad…

I also seen the danger of current user permission. And I attempt to fix it by a new doctype “DocType Permission”.

As soon as this document is created, there will be no permission to this document. And then by addion each Role’s Additional Permission, the permission will be added (using OR condition).

So, the combined condition would be,

doc_perm_conditions = (false OR role_1_cond OR role_2_cond)

This doc_perm_conditions will then be AND with other permission query, user permissions.

It works for me so far, but do you see any flaw?

3 Likes

seems a smart solution. if there is any user request, I will try.

thanks for sharing.

1 Like