Skip to content

Authorization

Iak edited this page Mar 10, 2023 · 1 revision

Authorization is the security process that determines a user's level of access to the application. In technology, we use authorization to give users or services permission to access some data or perform a particular action. It is vital to note the difference here between authentication and authorization. Authentication verifies the user's identity before allowing them access, and authorization determines what they can do once the system has granted them access.

Concept

In PROCEED we leverage RBAC (Role-based Access Control). According to that, it is possible to create roles and attach permissions for a specific resource to the role. Once a role is assigned to a user, the user implicitly inherits the role's permissions. A resource can be anything, but they are typically the assets of your application. In PROCEED, we currently have the following resources:

  • Process
  • Project
  • Template
  • Task
  • Machine
  • Execution
  • Role
  • User
  • Setting
  • EnvConfig
  • All

All represents all resources together! This is useful to grant full access to every resource in PROCEED.

Once a user wants to access one of the resources, he or she creates a request to the backend REST API (MS Server). The backend checks the user's permissions for that resource, by offloading the decision to an external service, which is called OPA (Open Policy Agent). OPA can grant or deny access to a resource and decides if the request will be fulfilled, or denied with a 403 FORBIDDEN error.

Open Policy Agent (OPA)

The Open Policy Agent (OPA, pronounced “oh-pa”) is an open source, general-purpose policy engine that unifies policy enforcement across the stack. OPA provides a high-level declarative language called Rego that lets you specify policy as code and simple APIs to offload policy decision-making from your software.

This is not a Rego documentation! Read this page to learn about the core concepts in OPA’s policy language (Rego).

OPA decouples policy decision-making from policy enforcement. When your software needs to make policy decisions, it queries OPA and supplies structured data (e.g., JSON) as input. OPA accepts arbitrary structured data as input. It generates policy decisions by evaluating the query input against policies and data.

opa-service.svg

Read this to learn more about policies, policy decoupling and OPA.

Permissions

In PROCEED you can create arbitrary resource-permission combinations. A resource-permission combination enforces specific permissions on a resource. Consider the following example:

"permissions": {
  "Role": 16,
  "User": 3,
  "Process": 49
}

The above permissions assigns a role with manage permissions for Roles, view & update permissions for User and view, manage & share permissions for Process. In PROCEED all permissions are represented as numbers. Behind every number, there is an action (verb). All numbers must come from a binary number sequence (0,1,2,4,8,16,...). The following values are currently available:

Constant Action (Verb) Value
PERMISSION_NONE none 0
PERMISSION_VIEW view 1
PERMISSION_UPDATE update 2
PERMISSION_CREATE create 4
PERMISSION_DELETE delete 8
PERMISSION_MANAGE manage 16
PERMISSION_SHARE share 32
PERMISSION_MANAGE_ROLES manage-roles 64
PERMISSION_MANAGE_GROUPS manage-groups 128
PERMISSION_MANAGE_PASSWORD manage-password 256
PERMISSION_ADMIN admin 9007199254740991

If multiple permissions are assigned to one resource, they are added. For example, if you want to assign view (1) + manage (16) permissions to a resource, it is calculated by view (1) + manage (16) = 17. It is always possible to reverse the number, so that a system knows 17 = manage and view permissions. This is important for frontend UI adjustments, as described in Frontend.

The reason for this binary row is, that it is possible to represent arbitrary resource-permission combinations with just a number and the resource name. According to that, OPA can execute some bitwise calculations for policy decisions. This is similar to the unix file permissions system. The following example shows, how it works:

# check if user has permission according to bitfields
# example: user has role x with permission { Role: 80 }; wants to delete = 8; role z

# calculation:
# user permission for roles in binary: 	  1010000 = 80
# necessary delete permissions in binary:    1000 = 8
# AND					  0000000 = 0 -> result: not allowed

admin is equivalent to admin rights on some resource. It is represented by the highest available number, which exists of only ones (11111...). This means that every AND operation with this number will never result into 0.

Available resource-permission Combinations

In PROCEED, it is currently possible to create the following resource-permission combinations to assign it to a role. The maximum value is the sum of the available permissions for a resource.

Resource Action (Verb) Value (max)
Process view, manage, share, admin 9007199254740991
Project view, manage, share, admin 9007199254740991
Template view, manage, share, admin 9007199254740991
Task view 1
Machine view, manage 17
Execution view 1
Role manage 16
Setting admin 9007199254740991
EnvConfig admin 9007199254740991
User manage, manage-roles 80
All admin 9007199254740991

When PROCEED is deployed for the first time, the following roles and respective permissions are generated:

Role Process Project Template Task Machine Execution Role User Setting EnvConfig All
@admin x x x x x x x x x x admin
@everyone x x x x x x x x x x x
@guest x x x x x x x x x x x
@process_admin admin admin admin x x x x x x x x
@environment_admin x x x x x x x x admin  admin x

@admin

Once PROCEED is launched for the first time, an admin user is created with the @admin role, to have full access to everything in PROCEED. The admin user can now set up the entire Identity - and Access Management behavior of PROCEED in the IAM view by creating roles, and assigning users and permissions. The admin role is not removable.

@everyone

The @everyone role initially has no configuration. An admin user is able to change that. This role is intended for every authenticated PROCEED user and is not removable. Hence, every authenticated user inherits the permissions of the @everyone role.

@guest

The @guest role initially has no configuration. An admin user is able to change that. This role is intended for every unauthenticated PROCEED user and is not removable. Hence, every unauthenticated user inherits the permissions of the @everyone role.

Attribute-based Access Control

Next to RBAC, there is also ABAC (Attribute-based Access Control). The difference is, that ABAC uses arbitrary attributes to make policy decisions. ABAC somehow deviates from the "standardized" role management behaviour, because there can be arbitrary policies existing. PROCEED also makes use of additional ABAC rules, which are not possible to list here, but can be viewed in the repository.

Frontend

The frontend uses the 2 following libraries to adapt the frontend UI according to the users permissions. Please note that this may be manipulated by attackers. However, this won't be an issue because the backend is the final instance to protect resources against unauthorized access.

CASL is a library, that can be used in both the frontend and backend.

CASL operates on the abilities level, that is what a user can actually do in the application. An ability itself depends on the 4 parameters (last 3 are optional):

  1. User Action

Describes what user can actually do in the app. User action is a word (usually a verb) which depends on the business logic (e.g., prolong, read). Very often it will be a list of words from CRUD - create, read, update and delete.

  1. Subject

The subject or subject type which you want to check user action on. Usually this is a business (or domain) entity (e.g., Subscription, Article, User). The relation between subject and subject type is the same as relation between an object instance and its class.

  1. Fields

Can be used to restrict user action only to matched subject's fields (e.g., to allow moderator to update status field of an Article and disallow to update description or title)

  1. Conditions

Criteria which restricts user action only to matched subjects. This is useful when you need to give a permission on specific subjects (e.g., to allow user to manage own Article)

Define rules as .json

It is possible to define rules in a .json format, especially if rules are dynamic. In such cases, the preferred way is to use JSON objects. You can directly pass array of JSON rules into Ability constructor. Such rules are called raw rules:

import { Ability } from '@casl/ability';

export default new Ability([
  {
    action: 'read',
    subject: 'Post'
  },
  {
    action: 'delete',
    subject: 'Post',
    conditions: { published: true }
  }
])

If the above rules are passed to an ability constructor, it means that someone can read a Post and delete a specific Post, where published is true.

Usage

When a user is logging into the PROCEED MS, the MS Server reads the user's permissions from the database and passes them to the frontend in the following format:

{
  "permissions": {
    "Process": 16,
    "Project": 1
  }
}

The frontend transforms the above permissions into CASL rules as defined above. It subsequently passes them into the ability constructor to define the permissions for the active authenticated user, with the following function:

ability.update(unpackRules(rules));

It is now possible to hide or show components in the UI or to prevent unauthorized access to functions via the following shorthand function, that comes with the casl-vue library:

$can('view', 'Process')

This essentially asks, if someone can view an arbitrary Process and decides, if a user can perform the action based on the ability instance.

Syntax is $can(action, resource). Note that resource can also be a javascript object, so the $can function can additionally validate conditions based on the object. The resource is now not arbitrary anymore, but a specific one.

ATTENTION: When using objects as a resource, the type of the object has to match the Subject fields that are specified in the CASL Rules. According to that, it is recommended to map the objects to a readonly, non-enumerable and non-configurable __caslSubjectType__ property, with the following function for example.

import { subject } from '@casl/ability';

// maps all processes to __caslSubjectType__ Process
processes.map((object) => subject('Process', object));

This is the Dev Wiki

Clone this wiki locally