|Version 109 (modified by 10 years ago) ( diff ),
Authentication, Authorization and Accounting
Table of Contents
- Simple Authorization
- Implementation of Access Control
- Data Access Tracking (Audit)
- Module Authorization
- See Also
Authentication is the act of establishing or confirming someone's identity.
Authorization is the concept of allowing access to resources only to those permitted to use them.
Accounting refers to the tracking of user actions - an audit trail.
AAA functions for S3 are implemented in the
modules/s3/s3aaa.py module. This module extends the web2py Auth class as AuthS3 (Authentication), and defines additional classes for role management, access control and audit.
Component Location Function AuthS3 modules/s3/s3aaa.py Authentication, Login S3Permission modules/s3/s3aaa.py Authorization of Access, ACLs S3Audit modules/s3/s3aaa.py Data access logging, audit trail S3RoleManager modules/s3/s3aaa.py RESTful method to manage roles and ACLs Admin controllers controllers/admin.py User Management, role management
Epydocs can be found here: http://pub.nursix.org/eden/s3
The AuthS3 class extends web2py's Auth class, which is documented here:
NB Whilst the Authentication functions of the Auth class are only slightly modified, the Authorisation & Audit functions are not used at all, so ignore those when working with Sahana.
- more coming...
- coming soon...
- coming soon...
HTTP Basic Authentication
It is possible to access privileged resources by providing the username/password within each request, rather than the usual method of having the login stored within the session.
This is done by using the
login_bare() method of AuthS3.
- more coming...
Roles are defined in the
auth_group table, which is defined by the
AuthS3 module (in
Each role has an ID, a unique name and an optional description.
Access permissions are granted to roles, while a user gets permissions by assigning roles to them. Role assignment is stored in the
auth_membership table, which is defined by the
AuthS3 class (in
At the start of every request, the IDs of all roles of the currently logged-in user are stored as list in
In cases where the user logs in during the request (e.g. by HTTP Basic Auth), a refresh of this list is also triggered by the
login_bare() method of
Roles can be managed in the
S3RoleManager graphical interface (Administration menu => User Management => Roles).
The following roles are pre-defined in S3 and cannot be changed:
ID Name Description 1 Administrator system administrator 2 Authenticated all authenticated users 3 Anonymous unauthenticated users 4 Editor data editor
The first registered user gets the Administrator role assigned.
Users with the Administrator role always have all permissions, and may access all pages in Eden. The Administrator may also manage Users, Roles and ACLs.
Users with the Editor role may access all data with all methods, except they can not manage Users, Roles or ACLs.
Every authenticated user gets automatically the Authenticated role assigned. This role assignment cannot be revoked.
Simple authorization means, that
- anonymous users have read-only permission
- authenticated users have full access
- the Administrator may additionally access the user management
This is the fallback model wherever there are no ACLs defined.
Access Control Lists (ACLs) are bit arrays with each bit representing a permission to access data with a particular method:
Bit Value Permission auth.permission.CREATE 0x01 may create new records auth.permission.READ 0x02 may read or list records auth.permission.UPDATE 0x04 may update existing records auth.permission.DELETE 0x08 may delete records
ACLs are combinations of these bits (by bitwise OR), e.g. an ACL with the value 0x06 defines permissions to read and update records, while no permission to add or to delete any records.
ACLs are stored per role and request destination in the
s3_permission table, which is defined by the
S3Permission class (in
ACLs can be managed by the
S3RoleManager graphical interface (Administration menu => User Management => Roles).
For every destination (controller/function/table) two ACLs can be defined to apply depending on whether a user owns the record or not:
- one ACL for users owning a record (Owner ACL =
- one ACL for any other user not owning the record (User ACL =
If a user owns a record, then the most permissive of the User ACL and the Owner ACL gets applied, otherwise only the User ACL applies.
OrgAuth are security policies which allow multiple organizations using the same instance of Sahana Eden to control who can access their data and with which permissions.
The OrgAuth policies are all based on the following base concepts:
A person entity is a type of records describing business entities which involve one or more individual persons. This can be, e.g., organisations, offices, teams, and of course persons.
A realm of a person entity is the set of all records controlled ("owned") by this entity (="their data"). Which entity gains control over a record can be defined per record type, and even as deployment options. The realm which a particular record belongs to is encoded as person entity ID (pe_id) in the
realm_entityfield in this record.
In an organizational structure, a person entity can be a sub-unit (organization unit, OU) of another person entity. E.g. an office can be a sub-unit of an organisation, or a person a sub-unit of a team.
In OrgAuth policies, a user can be assigned a role for a realm. That is, the permissions resulting from this role assignment are limited to the records within the realm - whilst they have no effect outside the realm.
OrgAuth policies 7 and 8 also implement a hierarchy of realms, where the realm of an entity includes the realms of all its OUs.
OrgAuth policy 8 additionally allows the delegation of access rights for a realm to other entities (rather than to particular users), thus facilitating controlled data sharing at the organization level.
A detailed description of the OrgAuth framework can be found here:
Tables can implement a record ownership by adding two meta fields:
Field name Type Description owned_by_user integer (reference auth_user) ID of the user who owns this record
(defaults to the ID of the user who created the record)
owned_by_group integer (reference auth_group) ID of the user group that owns the record
These meta fields are contained in
s3_ownerstamp() and also in
A user is considered the owner of a record if they are either the individual owner of the record (user ID ==
owned_by_user), or they are a member of the owner group (
In tables which do not define either of these meta-fields, ownership rules are not applied (
NOTE: you can have both an individual record owner and an owner role for the same record at the same time, where the individual owner doesn't need to have the owner role.
realm_entityfield associates the record with a realm (see #OrgAuth) - it has no relevance for the ownership of the record by an individual user. A user can own of records any realm.
NOTE: If a record has no owner, i.e. if both
owned_by_roleare None, then all authenticated users are considered the owner of this record (public record). As a consequence of that, any owner ACLs for the AUTHENTICATED-role would always include all records without owner - regardless of the realm they belong to.
Future versions could implement a deployment option to apply ownership strictly, i.e. to consider records without owner as not owned by any user (rather than as owned by all users).
For anonymous users we can make the session own the records, so that a user can edit records they've just created, or read their cached feature queries.
When using the framework, this happens automatically. For manual DAL calls, this can be set using:
(This has no effect if the owned_by_user field is set)
Ownership vs. Access Permission
NOTE: Ownership for a record and access permissions on this record are independent from each other!
The fact alone that a user owns a record doesn't give him any permissions on that record. He still needs to have a role assigned for which an ACL definition exists which gives him permissions on that table.
But that also means that the role which determines the user's ownership of a record, and the role which determines the user's effective access permissions for that record, do not have to be the same (in fact, the ownership-determining role doesn't need to have any permissions on the table at all): the effective permissions would still be applied as per the most permissive role the user is assigned to.
Have these things:
- A role OrgX Staff, which is routinely assigned to staff members of organisation X
- A role Boss, which is assigned to all organisation admins (independent of the organisation!)
- A role Clerk, which is assigned to all helpdesk officers (independent of the organisation!)
- A record Y in the table aaa_bbbbb, which has its
owned_by_rolefield set to OrgX Staff
- An ACL for the aaa_bbbbb table, which sets
(uacl=CREATE, oacl=ALL)for the Boss role
- An ACL for the aaa_bbbbb table, which sets
(uacl=NONE, oacl=READ)for the Clerk role
With this configuration, a user who has the OrgX Staff role, would own record Y. However, the fact that he owns the record doesn't give him any permission to access it.
(Note that there is intentionally no ACL defined on aaa_bbbbb for role OrgX Staff!)
If the user would have both, the OrgX Staff and the Boss roles, then he would own the record Y (as per
owned_by_role) and also be permitted to
delete this record (as per ACL for Boss), and additionally, he could add new records to aaa_bbbbb.
If instead the user would have the OrgX Staff and the Clerk roles, then he would also own the record Y (as per
owned_by_role), but just be permitted to
read that record (as per ACL for Clerk).
If the user would only be Boss, then he could only create new records in aaa_bbbbb, but could not access record Y (since that would require ownership of that record).
If the user would only be Clerk, then he could not see record Y at all. And he could not either create new records in aaa_bbbbb.
ACLs can be defined for controllers, and for particular functions inside controllers (Controller ACLs).
ACLs can additionally be defined for individual database tables (Table ACLs).
The system-wide permission model is configured as
security.policy in deployment_settings in either
models/000_config.py. This defaults to simple authorization (security.policy = 1).
To configure the system-wide policy to use ACLs, set
settings.security.policy = 3 # Apply Controller ACLs
settings.security.policy = 4 # Apply both Controller and Function ACLs
settings.security.policy = 5 # Apply Controller, Function and Table ACLs
To use controller ACLs, it must be specified for which controllers ACLs are to be used.
This can be done by setting the respective controller to
dvi = Storage( name_nice = T("Disaster Victim Identification"), description = T("Disaster Victim Identification"), restricted = True, # Apply controller ACLs for the dvi module module_type = 10, ),
False or undefined for a controller, then simple authorization is used for controller access.
NB: We could assume that if there is no ACL defined for a controller then this controller is unrestricted. However, this would require a second DB lookup per run to check whether the controller is restricted at all in case no specific ACL for the current user is found, and in order to keep this efficient, we introduced this deployment setting (which is also less confusing behavior).
The Controller ACL can be defined for all functions in a controller, and additionally for particular functions inside a controller, where the function-specific ACLs override the general controller ACL. That means, you can define a general ACL for the
pr controller, and a different one for the
The Controller ACLs are applied to all resources when accessed through this controller/function. If the Controller ACL does not give any permission for the current user (ACL value==auth.permissions.NONE==0x00), then the request is rejected as "Unauthorized". Controllers do not have to implement this check - this is done at a central place (in
Once the user has passed that controller permission check (must have at least
read permission), and tries to access to a particular table, then the controller checks for table-specific ACLs. This check is to be implemented by the particular controller using
s3_accessible_query (except controllers using S3CRUD only, which already contains it).
If there is no ACL defined for this table at all (i.e. for none of the users), then the table is considered unrestricted and only the controller ACLs apply.
If there exist ACLs for this table, but not for the current user, access is denied for the current user.
If there are specific ACLs defined for this table and the current user, then the most restrictive of the controller and table ACLs apply (i.e. you cannot allow on the table level what you forbid at the controller level, and vice versa).
Note: For consistency reasons, creating or deleting component records in a resource requires additional permission to update the main record, even though the main record is not changed by this operation, e.g. to add an address to a person record, you must also be permitted to update the person record.
Implementation of Access Control
Permission checking is always a two-step process:
- Check permission to access the controller/function
- Check permission to access the database table
The first step is done at a central point, in
00_utils.py before the models are loaded. If the ACLs, as defined for the current user, do not specify any permission for the target controller/function, then the request gets rejected before any models are loaded or the controller is entered.
The second step has to be implemented in the respective controller functions. This can be done in two ways:
- the controller uses s3_rest_controller() with S3CRUD, or,
- the controller uses auth.s3_has_permission() and/or auth.s3_accessible_query() to check permissions before exposing any data to the user
To check permissions to access a table (or a particular record) with a certain method, use the
authorised = auth.s3_has_permission("read", db.my_table) if authorised: # User may read in the db.my_table
authorised = auth.s3_has_permission("read", db.my_table, record_id=x) if authorised: # User may read record x in db.my_table
The access method can be one of these strings:
- "create": create new records in this table
- "read": read in this table (or this particular record, if specified)
- "update": update existing records in this table (or this particular record, if specified)
- "delete": delete records from this table (or this particular record, if specified)
Query for Accessible Records
You can build a query for all records in a table which are accessible for the current user with a certain method, by using
# Define your query: query = ... # Get accessible-query (e.g., all readable record in db.my_table): accessible = auth.s3_accessible_query("read", db.my_table) # Combine both parts: query = accessible & query # Perform the query rows = db(query).select(...)
Handling Insufficient Permissions
In case the user has insufficient permissions to access a table/record with the requested method, a well-defined response action must take place depending on the request format:
- in HTML format:
- already authenticated users should be informed about the insufficient permissions, and redirected to a (unrestricted) landing page
- unauthenticated users should be requested to login, and forwarded to a login page
- in all other formats:
- authenticated clients must receive an HTTP 403 (Forbidden) error code to cancel the request properly
- unauthenticated clients must receive an HTTP 401 (Authorization Required) error in order to trigger an authentication attempt
- the client must not be redirected in either of the cases (important!)
All this is covered by the
authorised = auth.shn_has_permission("delete", db.my_table) if not authorised: auth.permission.fail()
You can alter the destinations for redirection by setting:
auth.permission.homepagefor redirection when the user is logged-in, but has insufficient privileges (defaults to
auth.permission.loginpagefor redirection when the user is not logged-in (defaults to
Example: redirect to
my/index rather than to
default/index in case of insufficient privileges of an authenticated user:
authorised = auth.shn_has_permission("delete", db.my_table) if not authorised: auth.permission.homepage = URL(c="my", f="index") auth.permission.fail()
Data Access Tracking (Audit)
- see S3Audit
For the HMS Module it can be found here http://eden.sahanafoundation.org/wiki/HMS%20Module%20Authorization%20Access
- It is best to use table ACLs to grant access then controller ACLs to exclude roles from any pages.