Role-based Authorization
TL;DR - Row Level Security with Grant Permissions on User Roles
Declarative security enables you to Grant
row filters to user roles, providing row level security: users see only the rows to which they are authorized. Grants can access user properties, such as their organization. A common usage is to enforce multi-tenant access.
Define users and roles with a SQL database using an Admin app, or, supply a provider to attach to existing corporate security (AD, LDAP, etc).
Scope
Security consists of many aspects (http headers, cookie settings, etc.); here, we focus on the following key concepts:
-
Authentication: a login function that confirms a user has access, usually by posting credentials and obtaining a JWT token identifying the users' roles.
-
Authorization: controlling access to row/columns based on assigned roles.
-
Role: in security, users are assigned one or many roles. Roles are authorized for access to data, down to the row level.
Process Overview
The overall flow is shown below, where:
- Green - represents developer responsibilities
- Blue - System processing
Developers Configure Security
Developers are responsible for providing (or using system defaults).
Authentication-Provider
This class, given a user/password, returns the list of authorized roles (on None). It is invoked by the system when client apps log in.
Developers must:
* Provide this class
* Identify the class in `conf/config.py`
Authentication Data
Developers must determine the data required to authenticate users. This can be a SQL Database, LDAP, AD, etc. It is separate from user databases so it can be shared between systems. The Authentication-Provider uses it to authenticate a user/password, and return their roles.
declare_security
Add code to the pre-created (empty) Python module that defines table/role filters. The system merges these into each retrieval. These declarations are processed on system startup as described below.
System Processing
System processing is summarized below.
Startup: declare_security
When you start the server, the system (api_logic_server_run.py
) imports declare_security
. This:
-
Imports
from security.system.security_manager import Grant, Security
, which sets up SQLAlchemy listeners for all database access calls -
Creates
Grant
objects, internally maintained for subsequent use on API calls (SQLAlchemy read events).
Login: Call Auth-Provider
When users log in, the app POST
s their id/password to the system, which invokes the Authentication-Provider to autthenticate and return a set of roles. These are tokenized and returned to the client, and passed in the header of subsequent requests.
API: Security Manager
This provides:
-
The
Grant
function, to save the filters for each table/role -
Filtering, by registering for and processing the SQLAlchemy
receive_do_orm_execute
event to enforce filters.
Server: User State
The server provides the functions for login (using the Authentication-Provider). This returns the JWT which users supply in the header of subsequent requests.
As the server processes requests, it validates JWT presence, and provides current_user_from_JWT()
to return this data for the Security Manager.
Use Cases
Data Security
Security enables you to hide certain rows from designated roles, such as a list of HR actions.
Multi-Tenant
Some systems require the data to be split between multiple customers. One approach here is to 'stamp' each row with a client_id, associate client_id with each customers, and then add the client_id to each search. The sample illustrates how this can be achieved with authorization:
Grant( on_entity = models.Category,
to_role = Roles.tenant,
filter = models.Category.Client_id == Security.current_user().client_id) # User table attributes
Appendix: Resources
The Security Manager and sqlite Authentication-Provider are built into created projects from the system's prototype project -- see the security
directory.