Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Multitenancy was first introduced in Totara 13. Tenants introduce the ability to group and segregate users and content, and to delegate responsibilities for tenant management.

A diagram outlining the structure of a Totara site with multiple tenants.

In Totara 13 you can:

  • Enable tenants
  • Configure whether tenants are isolated or not
  • Create tenants
  • Create users within a tenant - making them a tenant member
  • Move system users into a tenant - making them a tenant member
  • Upload users, creating them within a tenant - making them a tenant member
  • Associate a system user with a tenant - making them a tenant participant (non-member)
  • Create content within a tenant category - courses and containers
  • Assign roles to user against a tenant - delegating responsibilities
  • Report on tenants, tenant members and tenant participants

The architecture extends the context system by specifically injecting a new context level between systems and users for users who are in a tenant, and by using existing category contexts in which all sub-content (courses, sub categories, activities, containers etc) is considered to belong to a tenant.
All components, plugins, and sub-plugins operating at the user context, or course category context and that correctly check capabilities will automatically work with the new multitenancy architecture.
Components, plugins and sub-plugins checking capabilities at the system context, or that do not use capabilities to control access will require modification in order to work with multitenancy.

This document aims to explain how that modification should be approached.

What multitenancy is not

Multitenancy is not the complete carving of the site into multiple sites within one database and on one code base.
It is an extension of the context system, and as such integration with multitenancy architecture is best achieved by utilising the context and capability system where possible.


How it works

Multitenancy is an advanced feature which must be enabled at the system level. Once enabled you can optionally enable the tenant isolation setting. Note that this setting doesn't appear until multitenancy has been enabled.

Once multitenancy has been enabled, a Tenants option will appear in the quick-access menu, through which tenants can be added.

For more information see the Using What is multitenancy? help documentation.

The structures that make up a tenant

Each tenant within system has the following:

  • A tenant record
  • A tenant context
  • A tenant category
  • A tenant audience
  • A tenant dashboard

The tenant record

A description of each individual tenant.

The tenant context

Added in Totara 13.0 the tenant context has a context level of 15. It can only have one parent, the system context.
It can only have one type of child context, user contexts.
Prior to Totara 13 the user context could only use the system context as a parent. Now, user contexts can use either the system context or a tenant context as the parent. A user whose context uses the system context is considered a "system user". A user whose context uses a tenant context as a parent is considered a "tenant member".
The purpose of the tenant context is to enable delegation of user management responsibilities over a tenants members.

The tenant category

The context structure for categories, courses and activities was not changed. In order to allow for delegation of tenant content responsibilities support was added for system managed categories, and when a new tenant is created a brand new top level category is created at the same time. The new category is marked as a system category belonging to the tenant and cannot be edited or moved in the same way that normal categories can.

The tenant category is always a top level category, and its context uses the system context as a parent. The existing category context is used.
When looking at a tenant category the administration block shows tenant management options as well.

Users (typically tenant members) can then be given roles against the tenant category allowing delegation of tenant content responsibilities.

The tenant audience

When a tenant is first created a new audience is also created. We have introduced the ability to have system managed audiences that cannot be edited in the same way that regular audiences can be. The tenant audience is a system managed audience, that is owned by the tenant.
All tenant members (users whose context uses the tenant context as a parent) are automatically added to this audience.
When adding tenant participant (non-member) through the product interfaces you are in fact adding a user to this audience.

The purpose of the audience is to allow easy selection and use of users associated with a tenant.

Fundamental architecture

Multitenancy was principally an extension of the context hierarchy structure within Totara.

When enabled, capability checks now take into account the tenant a user is a member of, and include a check against the tenant of the context the capability check is being made against. The explanation of behaviour below outlines how this additional check behaves.

By wiring it directly into the context and capability systems much of the Totara code automatically and inherently follows the behaviour rules brought in by multitenancy. This is then supplemented by the addition of the tenant category and audience, which make should make it easy for developers to update their code in places where permissions are not being used to control visibility or access by providing a path to restrict both users and content to a tenant. Primarily the tenant that the current user belongs to

Tenant members vs tenant participants

A user who belongs to a tenant is referred to as a tenant member. A user can only be a member of a single tenant. Tenant members cannot participate in any other tenant. In the context tree the users context will have the tenant context as its parent. Capabilities are therefore inherited from the tenant, and changes at the tenant level will have an effect on the user context if there are not overrides at the user context.

A user who is not a member of a tenant can be associated with a tenant as a participant. A user can be a tenant participant in multiple tenants. When a user is added as a tenant participant they will be added to the tenants audience. The user context uses the system context as its parent and therefore does not inherit from the tenant capability as tenant members do.

When undertaking actions for a tenant or its content through which you see or select users the list of users that you see will be restricted to just tenant members and tenant participants.
This includes actions such as delegating responsibility within the tenant, associating managers and appraisers with a tenant members job, or enrolling in a course within the tenant.


Roles provided

Developing for multitenancy

Two new roles are installed by default on a site in order to support the delegation of responsibility when using multitenancy.

Tenant user manager

This role can manager users within a tenant, they have no rights over the content.
The role is assigned at the tenant context level.

Tenant domain manager

This role can manage content within a tenant, they have no rights over the users.
The role is assigned at the category level (directly to the tenant category)

Basic behaviour when isolation mode is disabled (default)

Isolation mode is off by default. It is an advanced setting.

When turned off:

  • Tenant members are able to see and interact with:
    • Content within their tenant
    • Content not within any tenant
    • Other users who are also members of their tenant.
    • Other users who are also participants of their tenant
  • A member of "Tenant A" will not be able to see content in "Tenant B" or find users in "Tenant B".
  • Authenticated users who are not a member of any tenant will be able to see and interact with:
    • Content in the system regardless of whether it is tenant content or not (normal visibility + access control is of course respected)
    • Users in the system regardless of whether they are a member of a tenant or not (also respected existing visibility and access control)
  • The guest user will be able to see and interact with system content and system users only. They cannot access tenants, or tenant content.

For more information see the Using multitenancy help page.

Basic behaviour when isolation mode is enabled

Isolation mode is off by default. It is an advanced setting.

Note

Note that tenant isolation is an experimental feature.

When turned on:

  • Tenant members are able to see and interact with:
    • Content within their tenant
    • Other users who are also members of their tenant
    • Other users who are also participants of their tenant
  • A member of "Tenant A" will not be able to see content in "Tenant B" or find users in "Tenant B".
  • Authenticated users who are not a member of any tenant will be able to see and interact with:
    • Content in the system regardless of whether it is tenant content or not (normal visibility + access control is of course respected)
    • Users in the system who are not a member of a tenant (also respected existing visibility and access control)
  • The guest user will be able to see and interact with system content and system users only. They cannot access tenants, or tenant content.

The main distinction is that with isolation on a tenant member cannot see or experience content or users outside of their own tenant.

For more information see the Using multitenancy help pages.

Behaviour when a tenant member is assigned to content outside their tenant

The following statements outline default expectations:

  • If the content belongs to another tenant then regardless of any assignment, the user will not be able to access the content. All calls to has_capability will return false due to the content and user being in different tenants.
  • If the content does not belong to a tenant (it is system content) and if isolation mode is turned off (default) the user will be able to access it, providing their assignment gives them access.
  • If the content does not belong to a tenant (it is system content) and if isolation mode is turned on then the user will not be able to access it. All calls to has capability will return false as the content is not in the same tenant as the user.

This is the default behaviour, and is driven by restrictions employed by has_capability().
In reality each area is free to break from the default behaviour and do their own thing, however they will not be able to use the capability system.

Behaviour when a system user is moved into a tenant

System users can be moved into a tenant. When this happens the users content changes its parent from the system context to the tenant context. This effectively puts the user into the tenant.
Any content the user has previously created, whether it is personal content  (forum posts, comments) or site content (courses, activities) is left exactly where it was.
The same is true for any assignments or data recorded against the user. It remains as is.

If content was created in a tenant that the user could access, but that is not the tenant that they became a member of, then they will loose access to that content.
Any assignments the user has remain, however if the assignment is to content that they cannot access due to now being a member of the tenant they will loose access to that content the assignment is against, regardless of any intention of the assignment.

When a user is moved into a tenant their participation in all other tenants is terminated (they are removed from all other tenant audiences).

Behaviour when a tenant member is moved from one tenant to another

A user can be moved from one tenant to another by an administrator via editing the user. When they are moved they cease to be a member of the original tenant, and become a member of the new tenant.
They still cannot participate in any other tenants.
Any data they have created whilst a member of the original tenant will remain, as will any assignments between them and content in the original tenant. However they will lose access to all of this data, and the assignments will not grant them any access.

Behaviour when moving a tenant course from one tenant to another

A course can be moved from one tenant to another. Or out of a tenant into the system category.
This can be done by editing the course and changing its category.

When a course is moved from a tenant all of its content and data remains exactly as is. This may mean that users who are assigned and could previously access the course remain assigned but can no longer access the course.

A special note about site administrators

Site administration is not a role. It is a special elevation that should be given to as few people as possible. It bypasses all permission checks and restrictions, including those introduced by the multitenancy. This enables the site administrator to view information no other user would be able to view, and to perform actions that would otherwise be impossible.
We continue to strongly recommend that site administration accounts are only ever used to administer the site, such that they are used for configuration only, never for content creation, or modification.

Developing for multitenancy

When developing for multitenancy your very first consideration is the context level at When developing for multitenancy your very first consideration is the context level at which you functionality is implemented. It [multitenancy] was implemented in such a way that for those who are creating, or updating functionality that woks at the category, course, activity, and user levels access control and visibility via capabilities will work out of the box.
Where your functionality is implemented at a context level with lineage back to category, or user context then the following is true:

  • Providing your functionality has properly hooked into the navigation structure then the navigation blocks, navbar, site administration areas and other features that consume the navigation structure for a site will automatically adjust as designed when required.
  • Core API's for working with existing structures such as users, courses, and activities have already been updated to work with multitenancy - custom code already using there API's will not require updating in these areas.

If however your functionality has been implemented at the system context level, or has extended beyond these conveniences through the likes of report build reports, custom selection API's for users and content then work will be required to make it compatible with multitenancy, if desired.

For those using the system context level the first consideration should be whether there is a more appropriate context level that could be used, such as a course, category, or activity context. If so then making this change will be beneficial in a number of ways, including automatic adoption of permission and access control checks.

Regardless, the following sections try to outline some of the commonly encountered challenges a developer will face, and attempt to provide useful code snippets to aid you in resolving this quickly.


Getting a tenant

A tenant record can be fetched easily using the following method.

Code Block
languagephp
$tenant = \core\record\tenant::fetch($id);

The tenant record is rarely of interest outside of tenant management functionality. It is used within this document more for illustration purposes. In nearly all situations you will be interested in the tenantid only.


Getting a

users

user's tenant

There are a couple of ways to get a users user's tenant, depending upon which information you have readily accessible to you.

The user table has a tenantid column that records the ID of the tenant the user is a member of. If the user is not a member of a tenant the value will be null.

Code Block
languagephp
$USER->tenantid

The context table also has a tenantid field that gets set for all contexts within the a tenant. The user context therefore can be used to get the users tenant also.

Code Block
languagephp
\context_user::instance($USER->id)->tenantid

The following function would get the tenant a user belongs to, or null if they are not a tenant member:

Code Block
languagephp
function core_user_get_tenant_by_userid(int $userid): ?\core\record\tenant {
    global $DB;
    $tenantid = $DB->get_field('user', 'tenantid', ['id' => $userid], MUST_EXIST);
    if (empty($tenantid)) {
        return null;
    }
    return \core\record\tenant::fetch($tenantid);
}


Getting the tenants a member is a participant of

The following snippet of SQL illustrates how to get all of the tenants a member is a participant of.

Code Block
languagesql
SELECT t.id
  FROM {tenant} t
  JOIN {cohort_members} cm ON cm.cohortid = t.cohortid
 WHERE cm.userid = :userid


Getting the tenant a context belongs to

The context table also has a tenantid field that gets set for all contexts within the a tenant. The context therefore can be used to get the tenant easily.

Code Block
languagephp
function core_user_get_tenant_by_context(\context $context): ?\core\record\tenant {
    if (empty($context->tenantid)) {
        return null;
    }
    return \core\record\tenant::fetch($context->tenantid);
}

As above, its unlikely the tenant record is going to be of much use. This example just illustrates logic around the content→tenantid field.


Restricting a list of users, courses, or contexts to a tenant by context join in SQL

Restricting a list of users by tenant is extremely simple as the user table has a tenantid column.

Code Block
languagesql
titleRestricting users
SELECT u.id
  FROM {user} u
 WHERE u.tenantid = :tenantid

For courses and other things in the context hierarchy joining to the context table and restricting based upon that is the best path

Code Block
languagesql
titleRestricting based upon context
SELECT c.id
  FROM {course} c
  JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = 50
 WHERE ctx.tenantid = :tenantid


Adding restrictions to a tenant where a capability check is made at the system context

Because the system context is being used capability checks for a user will fail when isolation mode is enabled regardless of whether the user has been granted the capability or not. It is rejected simply because isolation mode is on, the content exists at the system context, and the user is a tenant member.
If you require access control checks that differ from this behaviour then your only option is to implement your own check here, using the login that you wish to implement.
If you must use the capability system then your only option is to move the content into a context level that will parent (either directly or indirectly) the tenant context, or the tenant category context.


Tenant API

There is currently no publicly accessible tenant API.
Internally \totara_tenant\local\util serves as the tenant API for the totara_tenant plugin that manages tenants.
In the future we are very likely to build out GraphQL API's that expose multitenancy API.