-
Notifications
You must be signed in to change notification settings - Fork 0
Role Based Access Control
This page outlines the architecture and design principles of the Permit Connect Navigator Service (PCNS) role based access control (RBAC) system.

Audit columns removed from image for readability
Users can belong to one or many groups. A group is associated with a specific initiative. A group is defined by its associated roles, which further dictate the exact policies & permission sets a user may have. User are tracked by their SSO identity ID, and user to group associations are kept in the identity_group table.
Roles are defined by a unique name. Roles are a collection of zero to many policies.
A policy is a unique set of an Action and a Resource. This defines a specific permission that will be granted to a user in the application. Policies may have additional modifiers, called Attributes, which further define how a resource is acted upon.
A Resource is a collection of data. These are typically defined by existing API routes and database tables, but could be extended to anything, including front end application concerns.
Examples: Submission & Permit
An Action is an allowable action within the application. These are typically defined by existing API methods, but could be extended to anything, including front end application concerns.
Examples: Create & Read
An attribute is an optional modifier for a policy. They can apply globally to all groups, or act specifically against a group. These are simply strings that the application will have to take into account accordingly. The primary use of an attribute is defining the scope of a resource a group can access (eg: only access resources created by current user vs access to all resources), but they could be used for just about anything.
| group | |
|---|---|
| PK | group_id: int |
| FK | initiative_id: uuid |
| name : text | |
| description : text | |
| created_by: text | |
| created_at: timestamp | |
| updated_by: text | |
| updated_at: timestamp | |
| UNIQUE | group_id, initiative_id |
| role | |
|---|---|
| PK | role_id: int |
| name: text | |
| created_by: text | |
| created_at: timestamp | |
| updated_by: text | |
| updated_at: timestamp | |
| UNIQUE | name |
| policy | |
|---|---|
| PK | policy_id: int |
| FK | resource_id: int |
| FK | action_id: int |
| created_by: text | |
| created_at: timestamp | |
| updated_by: text | |
| updated_at: timestamp |
| resource | |
|---|---|
| PK | resource_id: int |
| name: text | |
| created_by: text | |
| created_at: timestamp | |
| updated_by: text | |
| updated_at: timestamp | |
| UNIQUE | name |
| action | |
|---|---|
| PK | action_id: int |
| name: text | |
| created_by: text | |
| created_at: timestamp | |
| updated_by: text | |
| updated_at: timestamp | |
| UNIQUE | name |
| attribute | |
|---|---|
| PK | attribute_id: int |
| name: text | |
| description: text | |
| created_by: text | |
| created_at: timestamp | |
| updated_by: text | |
| updated_at: timestamp | |
| UNIQUE | name |
| subject_group | |
|---|---|
| FK | sub: int |
| FK | group_id: int |
| created_by: text | |
| created_at: timestamp | |
| updated_by: text | |
| updated_at: timestamp | |
| UNIQUE | sub, group_id |
| group_role | |
|---|---|
| FK | group_id: int |
| FK | role_id: int |
| created_by: text | |
| created_at: timestamp | |
| updated_by: text | |
| updated_at: timestamp | |
| UNIQUE | group_id, role_id |
| role_policy | |
|---|---|
| FK | role_id: int |
| FK | policy_id: int |
| created_by: text | |
| created_at: timestamp | |
| updated_by: text | |
| updated_at: timestamp | |
| UNIQUE | role_id, policy_id |
| policy_attribute | |
|---|---|
| FK | policy_id: int |
| FK | attribute_id: int |
| created_by: text | |
| created_at: timestamp | |
| updated_by: text | |
| updated_at: timestamp | |
| UNIQUE | policy_id, attribute_id |
| attribute_group | |
|---|---|
| FK | attribute_id: int |
| FK | group_id: int |
| created_by: text | |
| created_at: timestamp | |
| updated_by: text | |
| updated_at: timestamp | |
| UNIQUE | attribute_id, group_id |
-
Middleware
- Three new middlewares will be created:
requireSomeGroup,hasAuthorization, andhasAccess. -
requireSomeGrouptakes zero parameters. It's sole responsibility is to check if a user is currently assigned to any groups. If not, it will attempt to assign the default group. -
hasAuthorizationwill take two parameters,resourceandaction. The middleware has 2 primary responsibilities- To ensure the requesting user has a policy set matching the requested parameters
- Inject the attributes associated to any matching policies into the request
-
hasAccesswill take one parameter, being a route parameter of some kind (eg:submissionId). The middleware has 1 responsibility- To check if the attributes injected in
hasAuthorizationcontain resource access scoping. If so then this middleware will ensure that the requesting user is allowed to access the resource in question.
- To check if the attributes injected in
- Three new middlewares will be created:
-
Controller layer
- Unfortunately there is no straight forward approach to handling attributes. Developers will have to be cognizant of which routes may or may not require attributes.
-
Service layer
- Any service calls that may require resource scoping should be passed the scope from the controller.
- This scope should then be used to determine the correct database query to be used on the resource.
- Appropriate response will be returned to the controller.
-
New endpoints
-
/permissions- Authenticated endpoint
- Returns a list of permissions for the authenticated user
-
-
authzStore- A new store to be created. This will store the full list of user permissions obtained from the new
/permissionsendpoint, as well as front end navigation permission - Provides a computed getter function to check if a user has the requested permission
can: computed(() => (initiative: Initiative, resource: Resource, action: Action, group?: GroupName) =>
- A new store to be created. This will store the full list of user permissions obtained from the new
- Log in
- Upon a user logging in, an api call should be made to the new
/permissionsendpoint to obtain the appropriate front end permission set - This result will be stored in the new
authzStore
- Upon a user logging in, an api call should be made to the new
- Checking permissions
- Using the authzStore, you can check if a user has the necessary permission via
authzStore.can(initiative, resource, action, group?: GroupName)
- Using the authzStore, you can check if a user has the necessary permission via
- Updating permissions
- At this time we are not concerned about real time permission updates
- We could potentially query the
/permissionsendpoint on token refresh and store the new set in theauthzStore. Any changes should then propagate automatically via the computed function.
The developer role will not be included in the database permission table. Instead the front and back end authorization checks will check the users groups for the developer role. If present the action will automatically be allowed.
Table of Contents
-
Software Design
-
Developer Resources
-
Product Roadmap