Permissions

As soon as a database application is used by more than one user, we usually need to speak about permissions. For example, a system administrator can see certain resources which a simple 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.

See Lino has its own user management if you wonder why Lino replaces Django's user management and permission system. Yes, this is radical. Django's approach for managing permissions is one of the reasons why I wrote Lino. I believe that maintaining a few production sites with applications like Lino Welfare or Lino Voga would be a hell if I had implemented them using Django. Permission management is complex. Lino doesn't turn it into something simple, but it brings light into the dark...

User roles

A user role is a role of a user in our application. It is used as the basic unit for defining permissions.

  • Every user has a set of roles.

  • Every resource (table or action) has a set of required roles.

User roles are class objects which represent conditions for getting permission to access the functionalities of the application. Lino comes with a few built-in user roles which are defined in lino.core.roles. Every plugin may define its own user roles which must be subclasses of these builtin roles. And of course a role can inherit from one or several other roles.

A real-world application can define many user roles. For example here is an inheritance diagram of the roles used by Lino Noi:

Inheritance diagram of lino_noi.lib.noi.user_types

And if you think this diagram is complex, then don't look at the following one (that of Lino Welfare)...

Inheritance diagram of lino_welfare.modlib.welfare.user_types

As the application developer you must specify for each resource the role(s) that are required to use it.

User types

When creating a new user, the site administrator needs to assign these roles to every user.

But imagine they would get, 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. The application sdeveloper defines a meaningful subset of all available roles for her application. This is done by populating the UserTypes choicelist.

Each user type is basically not much more than a user-friendly 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
======= =========== ===============

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 type

  • text, a translatable name

  • value, a value for storing it in the database

  • readonly 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 (experimental), a set of languages to not show to users of this type. This is used on sites with more than three or four languages.

The user type of a user is stored in a field whose internal name is user_type. This is because at the beginnings of Lino we called them "user profiles". Now we prefer to call them user types. The web interface already calls them "types", but it will take some time to change all internal names from "profile" to "type".

>>> rt.show('users.Users', column_names="username user_type")
========== =====================
 Username   User type
---------- ---------------------
 rando      900 (Administrator)
 robin      900 (Administrator)
 romain     900 (Administrator)
========== =====================

Accessing permissions from within your code

Just some examples...

>>> UserTypes = rt.models.users.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 ...>

Defining required roles

The application programmer specifies which roles are required for a given resource.

Where "resource" is one of the following:

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()

For example, the list of all users (the users.AllUsers table) is visible only for users who have the SiteAdmin role:

>>> sixprint(rt.models.users.AllUsers.required_roles)
{<class 'lino.core.roles.SiteAdmin'>}
>>> from lino.core.roles import SiteUser, SiteAdmin
>>> user = SiteUser()
>>> admin = SiteAdmin()
>>> user.has_required_roles(rt.models.users.AllUsers.required_roles)
False
>>> admin.has_required_roles(rt.models.users.AllUsers.required_roles)
True

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 administrators may nevertheless decide to change the set of available user types.

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.

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.