Overview
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 declarations reference not only roles, but also user properties (e.g., their organization). A common usage is to enforce multi-tenant access.
Define authentication data (auth data) - users and roles - with:
- a SQL database using an Admin app,
- Keycloak, or,
- supply a provider to attach to existing corporate security (AD, LDAP, etc).
Key Concepts
Security consists of many aspects (http headers, cookie settings, etc.); here, we focus on the following key concepts.
Authentication - system access
A login function that confirms a user has access, usually by posting credentials and obtaining a JWT token identifying the users' roles.
Authorization - data access
Controls access to row/columns based on assigned roles.
Users
Authorized users have a list of roles, and optionally a set of attributes.
User Roles
Users are assigned one or many roles (e.g, sales
). Rather than dealing with thousands of users, security adminstrators focus on authorization Roles to access data.
User Attributes
Each user may also have a set of site-specific attributes, such as their region
, or their (multi-tenant) client_id
.
Auth-Providers
Organizations will utilize a wide variety of techniques to maintain authorization data: databases, keycloak, LDAP, AD etc. This means an open "interface" approach is required.
Authentication Providers are called by the system during login. They are passed the id/password, and return a user row and list of roles. They hide how the user/role information is actually stored.
The system provides default providers for sql and keycloak. In addition, you can create your own provider to interface with your authentication system (LDAP, AD etc)
Multiple systems will share the same authentication data, so, even if you are using sql, this "auth" database/schema will be separate from each application database/schema.
Pre-supplied Auth Providers
It is common to use the system-supplied auth providers for keycloak and sql:
Custom Auth Providers
If you define your own auth provider, you must ensure it can be called by the system. To ensure that Authentication-Providers implement the expected api, you should inherit from this class.
Grant Role Filters
Security Administrators declaring Grant filters, which filter retrieval based on roles and user properties. This provides authorization down to the row level. For example, we might want to filter "small" customers so the sales team can focus on high revenue accounts:
Grant( on_entity = models.Customer,
to_role = Roles.sales,
filter = lambda : models.Customer.CreditLimit > 300)
Global Filters
Global filters apply to all roles. For example, you might enforce multi-tenant access with:
GlobalFilter( global_filter_attribute_name = "Client_id",
roles_not_filtered = ["sa"],
filter = '{entity_class}.Client_id == Security.current_user().client_id')
Process Overview
The overall flow is described below, identifying:
- What Developers must do
- What the system does
Developers Configure Security
Developers are responsible for providing (or using system defaults) the following:
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 (or use the system-supplied providers for
sql
andkeycloak
) -
Identify this as the
--provider-type
in theadd-auth
command
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 security/declare_security.py
to define table/role filters. The system merges these filters 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
security/system/authorization.py
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.