Note: You are currently viewing documentation for Moodle 1.9. Up-to-date documentation for the latest stable version is available here: How permissions are calculated.

How permissions are calculated: Difference between revisions

From MoodleDocs
No edit summary
Line 1: Line 1:
{{Roles}}
{{Roles}}
One of the most frequently asked question is: ''What permissions does a user have in a given context?'' This article will document the main function in Moodle that is used to answer the question.  The function is ''has_capability()''. It can be found in ''lib/accesslib.php''. The function implements the rules for "summing" permissions from multiple roles and overrides. Given a user, a capability, and a context, the function returns ''true'' if the user is allowed to perform the action controlled by the capability in the given context, and ''false'' otherwise.
One of the most frequently asked question is: ''What permissions do I have in this context?'' This article will document the function that Moodle uses to answer this question.  The article is written for non-programmers as well as programmers, since it describes ''what'' the function does, not ''how''. The ''how'' is complicated. The ''what'' turns out to be relatively simple!  In case you want to look it up, the function is named ''has_capability()'' and can be found in ''lib/accesslib.php''.  


This article is written for non-programmers as well as programmers. It describes ''what'' the function does, not ''how''. The ''how'' is complicated. The ''what'' turns out to be surprisingly simple!
Given a user, a capability, and a context, the function returns ''true'' if the user is allowed to perform the action controlled by the capability in the given context, and ''false'' otherwise. In calculating the permission, the function considers all of the relevant permission data, which include role definitions, assignments, and overrides.


Notice that the function ''does not'' attempt to answer the Big Question posed in the first sentence of this article. Instead, it answers the little question "Can some user do ''CAP'' here?" where ''CAP'' is a specific capability. Moodle never calculates a user's complete set of permissions.  It would be very costly to do so, and also wasteful, since most of the permissions would never be tested.  Instead, Moodle calculates permissions lazily, i.e., on demand. Moodle does not cache calculated permissions, but recalculates them every time they need to be tested. This is why role assignments and overrides no longer have delayed effects, as they did in Moodle 1.7 and 1.8.
Notice that the function does not actually answer the big question posed in the first sentence of this article. Moodle never calculates a user's complete set of permissions.  It would be very costly to do so and also wasteful, since most of the permissions would never be tested.  Instead, Moodle calculates permissions ''as needed''. Moodle does not store the permissions that it calculates, but recalculates them every time it needs to test them. This is why role assignments and overrides no longer have delayed effects, as they did in Moodle 1.7 and 1.8.


You can generate tables like the ones in this article using [[The rolesdebug.php roles debugging script]].  This can be very helpful in debugging roles-related problems.
You can generate tables like the ones in this article using [[The rolesdebug.php roles debugging script]].  The tables are very helpful in debugging roles-related problems.


==The calculation==
==The calculation==

Revision as of 03:36, 27 April 2008


One of the most frequently asked question is: What permissions do I have in this context? This article will document the function that Moodle uses to answer this question. The article is written for non-programmers as well as programmers, since it describes what the function does, not how. The how is complicated. The what turns out to be relatively simple! In case you want to look it up, the function is named has_capability() and can be found in lib/accesslib.php.

Given a user, a capability, and a context, the function returns true if the user is allowed to perform the action controlled by the capability in the given context, and false otherwise. In calculating the permission, the function considers all of the relevant permission data, which include role definitions, assignments, and overrides.

Notice that the function does not actually answer the big question posed in the first sentence of this article. Moodle never calculates a user's complete set of permissions. It would be very costly to do so and also wasteful, since most of the permissions would never be tested. Instead, Moodle calculates permissions as needed. Moodle does not store the permissions that it calculates, but recalculates them every time it needs to test them. This is why role assignments and overrides no longer have delayed effects, as they did in Moodle 1.7 and 1.8.

You can generate tables like the ones in this article using The rolesdebug.php roles debugging script. The tables are very helpful in debugging roles-related problems.

The calculation

The function is called as follows:

   has_capability(CAP,CONTEXT,USER);

where

  • CAP is the capability being tested (e.g., mod/quiz:attempt)
  • CONTEXT can be System context or some context nested arbitrarily deeply within System. Internally, the function uses Unix-style paths to represent contexts (e.g., /1/3/4/150), but such details do not concern us here
  • USER is the user.

The function returns a true/false result

  • true means the user should be allowed to perform the action
  • false means the user should be prevented from perfoming the action

Data used by the function

Suppose USER is about to attempt a quiz (CAP = mod/quiz:attempt) in a Module context nested four levels deep within System.

     System
        |
    Category A
        |					      
   Subcategory B
        |
      Course
        |
      Quiz  <--- the user is here


The quiz module will call has_capability() to see if the user should be allowed to perform this action.

The function considers the following permissions data:

  • Role definitions (these are in the System context)
  • Role assignments which have been made in any of the five contexts
  • Role overrides which may occur in any of the contexts except System (there is no concept of override in System).

Note that there may be multiple assignments and/or overrides in any single context.

Whether or not the user can attempt the quiz is a function of the permissions data, as well as the location of the data in the context chain.

has_capability() considers all roles and overrides that impact USER in CONTEXT, but it ignores capabilities other than CAP. This simplification lets us view each role or override as a single permission having one of the following values

   N - Not set
   A - Allow
   P - Prevent
   X - Prohibit

We use the following notation: for a role R1 with permission P, we write R1(P).

An example

In the quiz example, suppose that the user has four roles (R1, R2, R3, and R4) and each of the roles has been assigned and overridden as follows:


   0     System      <---- define R1(A), R2(N), R3(N), R4(P)
           |               assign R1
           |
   1   Category A    <-------------- overide R1(N) and R4(N)
           |                          
           |                          
   2  Subcategory B  <---- assign R2 and R3
           |
           |
   3     Course      <-------------- override R2(X) and R3(A)
           |
           |
   4     Quiz        <---- assign R4 and R1


Note that R1 has been assigned in two different contexts. This is an unusual practice (it is probably someone's error), but since it is legal, we have to consider the possibility that it can happen. In fact this entire example has been contrived to explore the edge cases of the algorithm. In practice, the calculation of permissions is trivial and the function calculates the result you would expect based on common sense and simple mental analysis (for further discussion of this, see A note about the algorithm near the end of this article.

Representing the data in tabular form

We will set up a table to hold the permission data. The table has

  • a column for each context, ordered from highest level (System) to lowest (the context where we want to compute the permission).
  • a row for each context, also listed from highest to lowest.

Empty table

The role definitions go in the first row. Put each definition in the column(s) coresponding to the context(s) in which the role is assigned:

Table with assignments added

The overrides go in the remaining rows. Each override goes

  • in the same column as the role being overridden
  • in the row corresponding the context in which the override is made.

To illustrate, let's add just one of the overrides:

Table with just one override

R2(X) represents the override to role R2 made in the course context. Let's add the rest of the overrides:

Table with remaining overrides shown

The table is now fully populated, and we are ready to calculate the permission.

The algorithm

The algorithm follows a path through the table indicated by nodes and arrows in the diagram below and stops as soon as it has a conclusive result.

Algorithm path through permissions data

  1. If there is an X anywhere in the table, STOP. The calculated permission is X
  2. Go to the START node
  3. If there is no next node, STOP. The calculated permission is P
  4. Otherwise, follow the arrow to the next node
  5. Add the permissions in the node using the numerical equivalents: N = 0, A = +1, P = -1
    • If the sum is positive, STOP. The calculated permission is A
    • If the sum is negative, STOP. The calculated permission is P
    • If the sum is zero, GO TO step 3

Notice that the algorithm stops when either (1) a conclusive result is obtained, or (2) the algorithm reaches the last node without a conclusive result.

Applying the algorithm to our example

If we execute the algorithm with the quiz data, we stop at step 1 with a calculated permission of X. That's not very interesting, so let's change the X to a P and see what happens:

Change X to P in permission table

Run the algorithm:

  • There is No X in the table
  • Go to the START node
  • Go to the next node
  • N + N = 0
  • the sum is zero; go to the next node
  • A + P = 0
  • the sum is zero; go to the next node
  • P + A = 0
  • the sum is zero; go to the next node
  • A + P = 0
  • the sum is zero; go to the next node
  • N = 0
  • the sum is zero; go to the next node
  • A = +1
  • the sum is positive; STOP. The calculated permission is A

If the calculated permission is A, has_capability() returns true, and the user is allowed to perform the action.

Had the calculated permission been P or X, has_capability() would not immediately return false. Rather, it would test if the user has moodle/site:doanything = Allow (since this permission trumps all others). The function does this by calling itself:

   final result = has_capability(moodle/site:doanything,CONTEXT, USER);

A note about the algorithm

Earlier, we said that the algorithm "calculates the result you would expect." But what do we expect? Intuition tells us that permissions closer to the user should carry more weight than more distant permissions. That's why we set up the table the way we did and why the algorithm walks the table in the order that it does. The last column of the table represents the role assignment(s) closest to the user. The second-to-last column represents the role assignent(s) that are next in terms of distance from the user, and so-on. That's why the algorithm considers the columns from right-to-left. Within a column, the algorithm searches upward in order to give higher weight to overrides that are closer to the user. Once again, this matches our intuition about overrides. If there are no overrides in a column (or the overrides are all Not set), the permission in the role definition is used.

Getting the result you expect

The calculation is more likely to give "the result you would expect" if you keep your permissions as simple as possible. Therefore, we recommend the following 'rules' for defining and overriding roles. If you understand the description of the calculation above, you should understand why they make sense.

When defining roles

... use Allow (A) for things you want the role to be able to do, and use Not set (N) for things you don't care about (which should include most things).

When overriding permissions

... leave the permission for every capability as Inherit (N) apart from the few you want to change. For those, use Allow (A) for things you want to allow, and Prevent (P) for things you want to prevent.

Only use prohibit in special roles

Prohibit exists to cover extreme scenarios such as the following: Suppose you have a naughty student who is posting inappropriate content in your Moodle site. You need to be able to remove their ability to contribute to public discussions until they have promised not to do it again, but during that time, you cannot completely block them from the resources, quizzes, etc. because they must keep up with their studying. Therefore, you need a way to block their access to specific capabilities e.g. mod/forum:replypost in a way that cannot be overridden. You can do this by creating a special 'Naughty student' role defined with the required permission(s) set to prohibit. Then when you are having problems with a user, you can temporarily assign them this role, either at site level or at course level.

A practical example

Suppose that a user, who is assigned the role of Course creator in category B, creates a course (becoming Teacher in the course) and then creates a Lesson within the course. The user is about to edit the lesson (editing lessons is controlled by capability mod/lesson:edit). Here is the permission data:


   0     System      <---- define Auth user(N), Creator(N), Teacher(A)
           |               assign Auth user
           |
   1   Category A
           |                          
           |                          
   2  Subcategory B  <---- assign Creator
           |
           |
   3     Course      <---- assign Teacher
           |
           |
   4    Lesson       <---- user attempts to edit lesson

We set up the table and populate it with permission data.

A practical example

Since there are no overrides, the table only has data in the first row. The algorithm quickly calculates a permission of A and returns true. The user is allowed to edit the Lesson!

Now suppose Teacher is overridden in the Lesson context with mod/lesson:edit = P. Then the table changes

A practical example with first override

and clearly the user is no longer allowed to edit the Lesson.

But suppose the administrator (or whoever is making the overrides) decided instead to override the Creator role, setting mod/lesson:edit = Prevent in the Categoy B context:

A practical example with second override

Much to everyone's surprise, the user is still allowed to edit the Lesson! If you understood this article, you shouldn't be surprised, and you should be able to explain what happened.

See also

There is a script that you can use to create tables like the ones in this article. It is very useful for debugging roles-related problems. See The rolesdebug.php roles debugging script.