This issue concerns the overall design and implementation of roles and permissions in 2.x. Before reading, it would be worth taking a look at some of the modules other people are using for permissions, roles, and access control lists.
NOTE: I will call the basic unit of permissions here a "token", but this name is only for clarity. We can call it whatever we want in the future
Implementation of Access Control
I propose that we implement access control in 2.x as a series of tokens for each route. This means, for each app.get()/app.put()/etc, we will need at least one token. In pseudo-code, it would look like this:
In the above example, the access.token() middleware would use the req.session object to make sure that the user had access to that route. If they did not have access, a 401 Unauthorized request would be returned.
The node access control that most closely resembles this structure is authorized.
Assigning Access Control to Users
We landed a PR that ties users to roles and units. The basic philosophy here is to make a finer-grained permissions model than units - we should be able to control access to each individual piece of information. Roles would then be assigned to these pieces.
We would need to maintain a list of tokens in the database. A table would look like this:
Note that parent here is just to allow a user to assign groups of tokens more easily through a tree interface, like we do for units now.
Roles are simply groups of tokens. Users can hold one or more roles. Every user must have a role.
Using Tokens on the Client for Access Control
Important: Before doing any work on Access Control on the client, we must make sure that the server works correctly and consistently. Remember, you cannot trust the client to be secure. All permissions must be enforced at the server first and then on the client.
In the same way that we will use tokens on the server, we can label different elements of the client with similar tokens (if not the same). Below will go through some examples.
Using Tokens to Show/Hide HTML
We can write a custom directive that basically performs the same operation as ngIf, but shows/hides based on the user's access level. For example, look at the patient record page below:
You can image decomposing this into the following sections (they are currently implemented as ui-views).
This architecture is now easy to put permissions on top of. All we need to do is define a directive that shows/hides HTML based on if they have access to the module. For example:
A user who only has access to patient:record:overview will only see the top box. A user who has access to patient:record:* will see all of them. And so on.
Using Tokens to Enable/Disable Links
We can do a similar thing with <a href></a> links. Often, we want to preserve the link, since it has useful information, but we don't want the user to be able to click it.
For example, imagine a user who has access to the Debtor Groups Management page. The page looks like this:
The user who has access to manage the debtor groups many not have the right to see the patients linked through the X Subscribed Debtors link.
We could limit their access by creating a directive that adds a dynamic class to the link, disabling it and coloring it grey (to indicate that they do not have access). This would look something like this:
The bhAccessLink would then check if the user (using the SessionService) had access to the link, and, if not, apply the class .bh-access-disabled which would do something like:
Finally, one of the last places we could put tokens to disable access is on the routes. This would replace our custom $stateChange events with a more consistent approach.
In the state definition we would put the token as a parameter that uses a state transition hook to allow or reject transition.
This issue concerns the overall design and implementation of roles and permissions in 2.x. Before reading, it would be worth taking a look at some of the modules other people are using for permissions, roles, and access control lists.
Here you go: https://gist.github.com/facultymatt/6370903
NOTE: I will call the basic unit of permissions here a "token", but this name is only for clarity. We can call it whatever we want in the future
Implementation of Access Control
I propose that we implement access control in 2.x as a series of tokens for each route. This means, for each
app.get()
/app.put()
/etc, we will need at least one token. In pseudo-code, it would look like this:In the above example, the
access.token()
middleware would use thereq.session
object to make sure that the user had access to that route. If they did not have access, a401 Unauthorized
request would be returned.The node access control that most closely resembles this structure is authorized.
Assigning Access Control to Users
We landed a PR that ties users to roles and units. The basic philosophy here is to make a finer-grained permissions model than units - we should be able to control access to each individual piece of information. Roles would then be assigned to these pieces.
We would need to maintain a list of tokens in the database. A table would look like this:
Note that
parent
here is just to allow a user to assign groups of tokens more easily through a tree interface, like we do forunit
s now.Roles are simply groups of
token
s. Users can hold one or more roles. Every user must have a role.Using Tokens on the Client for Access Control
Important: Before doing any work on Access Control on the client, we must make sure that the server works correctly and consistently. Remember, you cannot trust the client to be secure. All permissions must be enforced at the server first and then on the client.
In the same way that we will use tokens on the server, we can label different elements of the client with similar tokens (if not the same). Below will go through some examples.
Using Tokens to Show/Hide HTML
We can write a custom directive that basically performs the same operation as ngIf, but shows/hides based on the user's access level. For example, look at the patient record page below:
You can image decomposing this into the following sections (they are currently implemented as
ui-view
s).This architecture is now easy to put permissions on top of. All we need to do is define a directive that shows/hides HTML based on if they have access to the module. For example:
A user who only has access to
patient:record:overview
will only see the top box. A user who has access topatient:record:*
will see all of them. And so on.Using Tokens to Enable/Disable Links
We can do a similar thing with
<a href></a>
links. Often, we want to preserve the link, since it has useful information, but we don't want the user to be able to click it.For example, imagine a user who has access to the Debtor Groups Management page. The page looks like this:
The user who has access to manage the debtor groups many not have the right to see the patients linked through the
X Subscribed Debtors
link.We could limit their access by creating a directive that adds a dynamic class to the link, disabling it and coloring it grey (to indicate that they do not have access). This would look something like this:
The
bhAccessLink
would then check if the user (using the SessionService) had access to the link, and, if not, apply the class.bh-access-disabled
which would do something like:Using Tokens to Prevent Routing/State Changes
Finally, one of the last places we could put tokens to disable access is on the routes. This would replace our custom
$stateChange
events with a more consistent approach.In the state definition we would put the token as a parameter that uses a state transition hook to allow or reject transition.
Useful Links: