Roles and capabilities

Thanh Le & sam marshall, The Open University

13 February 2006

Introduction

This document attempts to describe a system for roles and capabilities within Moodle that would meet most reasonable needs for user management and permissions, in particular those identified during discussions at the Open University. It is based loosely on the document titled ‘Implementation plan for roles’, 16 November 2004, author unclear.

Terminology

  • By role, we mean an identifier of the user’s status, capabilities, etc. We intend roles to follow the English-language term so that it shouldn’t be necessary to use the software concept of ‘role’ for anything that isn’t a role. Most sites should need very few roles. ‘Teacher’, ‘student’, and ‘forum moderator’ are examples of roles.
  • Roles are assigned to users (or groups of users) within a certain context. A particular course, meta-course, or forum within a course would be an example of a context.
  • A capability is a permission to access some particular software feature. Capabilities are associated with roles.

Contents

This document includes:

  • The database tablesfor storing information related to roles and capabilities.
  • An example query to show how information can be obtained from the table structure.
  • A brief summary of how capabilities can be combined to determine whether a user can perform a particular action.

Note

We had to write this rather quickly and it may not be as simple to understand as people would like. If needed, we can help explain details or how certain cases should be handled. Probably we should work out use cases at some later date when there’s time…

Database tables

Arrows indicate foreign key references. Boldface entries ought to be NOT NULL while others should allow NULL, although of course this might or might not actually be implemented due to Moodle’s interesting code guidelines, in which case 0 or ‘’ counts same as NULL. (id) indicates an ID that is unnecessary but could be included to support get_record. Tables without IDs are those where we don’t see any likelihood of using get_record etc.

roles

Holds information about a particular role.

id / Auto-sequenced unique ID (roleid elsewhere)
name / User-friendly name of role (e.g. ‘Teacher’)
description / User-friendly full description of role (e.g. ‘One who teaches’)
priority / Optional priority. If included, indicates that capabilities attached to this role take a higher priority than those attached to any other role. (When a user has multiple roles with ‘priority’, they are sorted in numerical order; so priority 1 is higher than NULL/0, 2 is higher than 1, etc.)
Most roles would use priority NULL/0.
  • The table could later be extended to include more information if necessary, such as administrative details.
  • Roles donot need a computer-readable short name because particular roles should not be referenced in code. This system does not reserve any role names. (See later for an explanation of how legacy roles could be handled.)
  • The priority field is important where there are negative capabilities. For example, if you wish to ban somebody from posting in all forums, you can give them the ‘naughty boy’ role which removes the forum-post capability. This must take precedence over other roles (like ‘student’) which might allow them the capability.
  • Both name and description fields may include HTML. (As noted in the original proposal, this allows multilingual support using the <lang> tag. It may be necessary to add a pluralname field too.Alternatively, somebody could come up with a better idea about localising this data.)
  • We have not made it possible to define roles that can only be applied within a specific site. A small number of roles should be necessary; it should not be necessary to create roles for an individual site.
    However, it would be possible to add a courseid column which would have no effect other than (if non-null) to hide the display of the role in admin screens that relate to non-matching courses.
role_capabilities

Defines the capabilities that are granted by a particular role.

roleid / ID of role these capabilities are for
module / Short name of module, block, or other plugin that operates this capability, or NULL/‘’ if it is a system capability. Example: ‘forum’.
capability / Short name of capability, e.g. ‘post’.
allow / If 1, this is a positive setting that allows users with the given roleid to have this capability. If 0, this is a negative setting that removes the capability from holders of this role.
  • The default value for a capability is false (users do not have any capabilities unless they are assigned some).
  • There is a distinction between a capability not being defined for a role (implies that the role makes no change to the already built-up capability value) and being defined but with allow=0 (implies that the role rejects an existing capability). This is discussed in more detail later.
role_assignments

Assigns roles to individuals or groups.

(id) / Unnecessary auto-sequenced ID that may be included so that Moodle datalib functions can work.
roleid / ID of role that is being assigned by this entry.
contextid / ID of context to which this assignment applies. (A context might be, for example, a course. See capability_contexts, later.)
userid / ID of user to whom this role is assigned. May be NULL only if groupid is set.
groupid / ID of group to whom this role is assigned. May be NULL only if userid is set.
timestart / Time from which this role applies. Before this time, users do not gain any of the capabilities associated with the role.
timeend / Time after which this role does not apply.
timemodified / Tracks the time this assignment was created or modified. For administrative use.
modifierid / If the role was granted by another Moodle user, their ID is listed here. May be NULL in cases where it was granted by system processes. For administrative use.
  • As per previous design, a user can have any number of roles. To determine a user’s roles, we use the user ID, the IDs for any groups they may belong to, and information about the context of the page they are trying to access. From userid and groupid we can obtain a list of possible roles from this table, which can then be filtered depending on that present context (see capability_contexts, later).
  • This is the only table that includes user IDs. A key aspect of this design is to minimise the storage of user-specific data where the data is actually shared between a collection of users that could be defined: in other words, we are looking to minimise the need for entries in this table.
  • We have allowed for the assignment of roles to groups. This is in anticipation of a groups system that doesn’t just mean tutor groups. When that is in place, administrative staff may want to configure groups of students together. For example, if certain students are assigned to be forum moderators, rather than assigning each student individually the ‘forum moderator’ role, admin staff will want to create a group, give it forum moderator role (and potentially others), and put all the students in it.
capability_contexts

Defines contexts within which capabilities might be applied. Essentially this relates to a particular request: which course are they trying to access, which module within that course, etc.

In general, a context entry includes only an id and one of the other fields. Each other field must be left null.

id / Auto-sequenced ID that defines context.
system / Set to 1 to mean ‘anywhere in Moodle’.
metacourseid / Set if the context is ‘all courses within a particular meta-course’.
courseid / Set if the context is an individual course.
moduleinstance / Set if the context is a single instance of a module, e.g. a particular forum.
userid / Set if the context is a user. This context would be used only in special cases when it’s necessary to determine capabilities with regard to individuals, such as a ‘parent’ relationship.
  • The distinction between courseid and metacourseid is that the context courseid only includes that specific course (even if it is a meta-course), while metacourseid includes all courses that are children of the meta-course. This is necessary so that roles can apply specifically to a meta-course but not its children.
  • Contexts are listed from least specific (system) to most specific (moduleinstance and userid). When obtaining capabilities, those that apply to a more specific context take priority.
capability_overrides

Allows capabilities to be overridden in a particular context for a particular role. For example, we might say that although the ‘Student’ role in general isn’t allowed to edit quizzes, there is a particular quiz that students can edit.

contextid / Context where this override applies.
roleid / Role ID to which this override applies. You must have this role already in order to obtain the given overrides.
module / As in role_capabilities.
capability
allow
priority / As in roles. This priority controls whether the override takes precedence over roles, or not. There are cases in which you would not want it to take precedence over a high-priority role.
timemodified / Tracks the time this assignment was created or modified. For administrative use.
modifierid / ID of user who set up the override. For administrative use.
  • This feature is very important because without it, you would need to create a separate role entry for each such case and assign each student again to that role entry, creating an extra table row per student and the corresponding duplication/management complexities!
role_granters

Controls which roles may grant (and revoke) other roles. Intended to allow, for example, tutors to grant ‘forum moderator’ privileges to students.

roleid / ID of role that has permission to grant other roles.
grantableroleid / Which roles they can grant.
  • It isn’t possible to implement this relationship simply using capabilities, because capabilities don’t have ‘data’ parameters (for which roles can be granted). In any case, keeping this part separate seems sensible.
  • A similar table is not necessary for capability overrides. These can be controlled by an add-capability-override capability, which would be available to ‘editing teachers’ or whoever is involved in configuring a course.
  • It might be possible to do away with this table and make it a standard capability, for example by using the special module field value !grant and putting the capability name as the role ID.

Accessing information

SQL query

Here is an example query that might run in response to a require_login call.

SELECT

r.id AS roleid,

rc.module,

rc.capability,

rc.allow,

r.priority, cc.system, cc.metacourseid, cc.courseid,

cc.moduleinstance, cc.userid

FROM

role_assignments AS ra

INNER JOIN capability_contexts AS cc ON ra.contextid=cc.id

INNER JOIN roles AS r ON ra.roleid=r.id

INNER JOIN role_capabilities AS rc ON ra.roleid=rc.roleid

WHERE

(ra.userid=[[user ID]] OR ra.groupid IN [[group IDs]] AND

ra.timestart <= [[currenttime]] AND

ra.timeend >= [[currenttime]] AND

(cc.system=1 OR cc.metacourseid IN [[metacourse IDs]]

OR cc.courseid=[[courseid]] OR cc.moduleinstance=[[module instance]]

OR cc.userid=[[dependent user id]])

UNION ALL

SELECT

0,

co.module,

co.capability,

co.allow,

co.priority,0,0,0,0,0

FROM

role_assignments AS ra

INNER JOIN capability_contexts AS cc ON ra.contextid=cc.id

INNER JOIN roles AS r ON ra.roleid=r.id

INNER JOIN capability_overrides AS co

ON co.contextid=cc.id AND co.roleid=r.id

WHERE

[[ same where clause as above ]]

ORDER BY

priority DESC,userid DESC,moduleinstance DESC,

courseid DESC,metacourseid DESC,system DESC

To interpret the results of this query, the system takes the first value of any given capability and ignores all others. (In other words, if a negative value appears first, it doesn’t matter whether positive ones might appear later on.)

Note that the query (and hence require_login) needs to know:

  • Current user ID.
  • Group IDs that apply to current user (could be done with extension to query, but I thought we might already have them).
  • Course ID of user’s request.
  • Meta-courses that are parents of current course (could be done with extension to query).
  • Module instance of user’s request.
  • Only for cases when we’re asking for permissions with respect to a particular dependent user: dependent user ID

While you can leave these out if they don’t apply, support for negative capabilities makes it kind of important not to omit them when they do. For example, if you ask for permissions just for a course context, it might include the general ‘contribute’ permission. But permissions for a specific forum instance within the course might include an override that forbids it.

More work needs to be done to figure out exactly how require_login should change, and what consequences this will have for support of module code that hasn’t been altered. (E.g. it might just be that instance-specific permissions won’t work for those modules.)

While most of the above query (which it should be noted has not been checked for syntax errors) should be self-explanatory, the ORDER BY could be a bit confusing: essentially, we know that only one of those five fields after priority will be set, and we’d like the most specific (user) to come first. That’s what it is supposed to do.

Checking capabilities

Apart from require_login and methods to deal with modifying these tables as part of administration, the only new method needed is:

has_capability($module, $capability)

Implementing this method is quite simple:

  1. Look at the user data (previously set up by require_login) to see if the given capability is directly set (true or false). If so, return it.
  2. If the capability is not directly set, check first with the module, and then with the system, capability lists to see if there is a parent capability for that capability.
  3. If there is such a parent capability, return to step 1 to check it. Otherwise return false.

This capability tree is a key management feature that enables basic capabilities such as ‘course-edit’ to be defined, so that more specific capabilities like ‘quiz-add-question’ can be defined in terms of that one. If quiz-add-question isn’t defined, then it’ll look up quiz-edit, and then the generic course-edit.

  • This allows most management tasks to consider only a few simple capabilities, while leaving open the possibility for more specific control if needed.
  • Should new modules be installed, their capabilities do not need to be individually configured for all roles, because the generic ones they inherit from will probably be correct.

To calculate the capability tree, has_capability needs to access one file from the given module that defines the module’s own capabilities and parents, plus a core file.

Capability files

An example capability file for a module, capabilities.php, might look like this:

global $CAPABILITIES;

// Define the ‘post’ capability. If not specified individually,

// it takes the same value from the core capability ‘contribute’.

$CAPABILITIES[‘forum’][‘post’]=array(‘’,’contribute’);

// Define the ‘blat’ capability, which has no default. In this case

// you set to true instead of giving a parent.

$CAPABILITIES[‘forum’][‘blat’]=true;

We haven’t really defined it, that’s just a for-instance. So you can see that all you need to do is require_once that file (for the module you’re checking capabilities of), then you can look in the array, if necessary recurse to get the parent capability, etc.

It’s better to define capabilities in code because they are a property of the module code and not of the data (they shouldn’t change, for instance) and it’ll be a lot faster to use these structures in code.

Checking roles

Apart from administrative control (editing roles), it shouldn’t be necessary to check whether a person has a specific role. In all cases, capabilities should be checked rather than roles. (For example, where currently a ‘teacher’ is allowed to mark the work of students in their group, rather than checking ‘teacher’ we should be checking the mark_student_work capability.)

Displaying capabilities

When it is necessary to display capabilities (e.g. when editing a role), all capabilities [and the tree relationship] can be obtained by examining the capability file for each module, along with the core capability file. Names for each come from the module or core language file via a naming convention based on the capability name.

Legacy support

The legacy methods isteacher and isstudent etc. should be implemented in terms of capabilities, not roles. This allows multiple roles to all be ‘student’ or ‘teacher’ roles if necessary, rather than relying on dubious reserved names or special roles.

Where a capability makes sense as a generic capability, e.g. admin (general system administration capability), the capability should be a core capability defined as such. Capabilities that are only included for legacy reasons should have a prefix to indicate that: legacy_student, legacy_teacher. Eventually, as all modules gain support for the capability system, these legacy capabilities can be removed.