Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More
Introduction to permissions¶
As soon as a database application is used by more than one user, we usually need to speak about permissions. For example, a site manager can see certain resources that a simple end user should not get to see. The application must check whether a given user has permission to see a given resource or to execute a given action. An application framework must provide a system for managing these permissions. Lino replaces Django’s user management and permission system (see Lino has its own user management if you wonder why).
Side note: Code snippets (lines starting with >>>
) in this document get
tested as part of our development workflow. The following
initialization snippet tells you which demo project is being used in
this document.
>>> from lino import startup
>>> startup('lino_book.projects.min9.settings')
>>> from lino.api.doctest import *
User roles¶
The basic unit for defining permissions in Lino are called user role.
- user role¶
A responsibility that can be given to a user of a Lino application, and which grants permission to access certain functionalities of the application.
Every user (even AnonymousUser
) has a user role assigned to them.
Every action on a data table has a set of required roles.
Lino comes with a few built-in user roles that are defined in
lino.core.roles
.
A user role can inherit from one or several other user roles.
For example, the
SiteAdmin
role inherits from SiteStaff
, Supervisor
and Explorer
.
Plugins may define their own user roles that are subclasses of these builtin roles.
A real-world application can define many user roles. For example here is an inheritance diagram of the roles used by Lino Così:
And if you think this diagram is complex, then don’t look at the following one (that of Lino Noi)…
Defining required roles¶
The application developer specifies which roles are required for a given resource.
For example, the users.Users
table
is visible only for users
who have the SiteAdmin
role:
>>> users.Users.required_roles
{<class 'lino.core.roles.SiteAdmin'>}
Rather than setting required_roles
directly, we recommend to use the
login_required()
function for specifying required roles, so the definition
of the Users
table is actually:
class Users(dd.Table):
...
required_roles = dd.login_required(SiteAdmin)
More precisely, “resource” is one of the following:
an actor (a subclass of
lino.core.actors.Actor
)an action (an instance of
lino.core.actions.Action
or a subclass thereof)a panel (an instance of
lino.core.layouts.Panel
)
These objects have a required_roles
attribute, which must be a
set()
of the user roles required for getting permission to access this
resource.
This set of user roles can be specified using the login_required
utility function. You can also specify it
manually. But it must stisfy some conditions described in
check_required_roles
.
See lino.modlib.users.UserType.has_required_role()
>>> from lino.core.roles import SiteUser, SiteAdmin
>>> user = SiteUser()
>>> admin = SiteAdmin()
>>> user.has_required_roles(rt.models.users.Users.required_roles)
False
>>> admin.has_required_roles(rt.models.users.Users.required_roles)
True
>>> robin = users.User.objects.get(username="robin")
>>> robin.has_required_roles({SiteAdmin})
True
User types¶
When creating a new user, the site manager needs to assign these roles to every user. But imagine they would have to fill out, for each user group, a multiple-choice combobox with all available roles from above examples! They would get crazy!
That’s why we have user types.
- user type¶
Each user type is basically not much more than a name and a storable value given to a selected user role.
Here is the default list of user types, defined in lino.core.user_types
:
>>> rt.show(users.UserTypes)
======= =========== ===============
value name text
------- ----------- ---------------
000 anonymous Anonymous
100 user User
900 admin Administrator
======= =========== ===============
Every user account has a user type, stored in the
user_type
field of the
users.User
model. (If that field is empty, the
user account is “inactive” and can’t be used for signing in).
>>> rt.show('users.AllUsers', column_names="username user_type")
========== =====================
Username User type
---------- ---------------------
robin 900 (Administrator)
rolf 900 (Administrator)
romain 900 (Administrator)
========== =====================
The application developer defines the user types for their application by
populating the UserTypes
choicelist.
User types are a meaningful subset of all available combinations of roles a way to classify end users in order to grant them different sets of permissions.
Actually a user type contains a bit more information than a user role. It has the following fields:
role
, the role given to users of this typetext
, a translatable namevalue
, a value for storing it in the databasereadonly
defines a user type which shows everything that a given user role can see, but unlike the original user role it cannot change any data.hidden_languages
may optionally specify a set of languages to not show to users of this type. This is used on sites with more than three or fourlanguages
.
The user types module¶
The roles_required
attribute is being
ignored when user_types_module
is empty.
- roles.py¶
- user_types.py¶
The roles.py
is used for both defining roles
A user_types.py
module is used for defining the user roles
that we want to make available in a given application. Every user
type is assigned to one and only one user role. But not every user
role is made available for selection in that list.
Relation between user roles and user types¶
There is a built-in virtual table that shows an overview of which roles are contained for each user type. This table can be helpful for documenting the permissions granted to each user type.
>>> rt.show(users.UserRoles)
======================== ===== ===== =====
Name 000 100 900
------------------------ ----- ----- -----
cal.GuestOperator ☑ ☑
comments.CommentsStaff ☑
comments.CommentsUser ☑ ☑
contacts.ContactsStaff ☑
contacts.ContactsUser ☑ ☑
excerpts.ExcerptsStaff ☑
excerpts.ExcerptsUser ☑ ☑
notes.NotesStaff ☑
notes.NotesUser ☑ ☑
office.OfficeStaff ☑
office.OfficeUser ☑ ☑
polls.PollsAdmin ☑
polls.PollsUser ☑ ☑
xl.SiteAdmin ☑
xl.SiteUser ☑
======================== ===== ===== =====
Accessing permissions from within your code¶
Just some examples…
The following two lines are equivalent. It’s a matter of taste and sometimes of context which one you choose:
>>> UserTypes = rt.models.users.UserTypes
>>> from lino.modlib.users.choicelists import UserTypes
>>> UserTypes.admin
<users.UserTypes.admin:900>
>>> UserTypes.admin.role
<lino_xl.lib.xl.user_types.SiteAdmin object at ...>
>>> UserTypes.admin.readonly
False
>>> UserTypes.admin.hidden_languages
>>> robin = users.User.objects.get(username='robin')
>>> robin.user_type
<users.UserTypes.admin:900>
>>> robin.user_type.role
<lino_xl.lib.xl.user_types.SiteAdmin object at ...>
Local customizations¶
You may have noted that UserTypes
is a choicelist, not a
database table. This is because it depends on the application and is
usually not locally modified.
Local site managers may nevertheless decide to change the set of available user types.
Permission debug messages¶
Sometimes you want to know why a given action is available (or not available) on an actor where you would not (or would) have expected it to be.
In this situation you can temporarily set the debug_permissions
attributes on both the Actor
and
the Action
to True.
This will cause Lino to log an info message for each invocation of a handler on this action.
Since you probably don’t want to have this feature accidentally
activated on a production server, Lino will raise an Exception if this
happens when DEBUG
is False.