Note: You are currently viewing documentation for Moodle 2.0. 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
m (Typo)
No edit summary
Line 8: Line 8:
The function is called as follows:
The function is called as follows:


     has_capability(CAPABILITY,CONTEXT,USER);
     has_capability(CAP,CONTEXT,USER);


The function returns a true/false result
The function returns a true/false result
Line 15: Line 15:
* ''false'' means the user should be prevented from perfoming the action
* ''false'' means the user should be prevented from perfoming the action


The 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.
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.


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


       System
       System
Line 30: Line 30:




The quiz module will call ''has_capability'' to see if the user should be allowed to perform this action.
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:
The function considers the following permissions data:
Line 40: Line 40:
Note that there may be multiple assignments and/or overrides in any single context.   
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 data, as well as the location of the data in the context chain.
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.


The function ignores any data that isn't used in the computation. It ignores
''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


* roles that aren't assigned to the user
    N - Not set
* capabilities other than one we're testing
    A - Allow
* permissions that are Not set. 
    P - Prevent
    X - Prohibit


After ignoring the above data, we are left with a very simple "data slice" consisting of just those permissions for the capability we're testing, in which all values are either Allow (A), Prevent (P), or Prohibit (X). This data can be represented in a neat tabular form, resembling an addition problem. For our quiz example, suppose we have
We use the following notation: for a role ''R1'' with permission ''P'', we write ''R1(P)''.


    P  A  P
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:
          A
    P  A  X
    A    P
      P  A
    --------
      ?   


The top row represents the permissions in the role definitions, one permission from each role. In the example, you can see that the user is currently assigned three roles. The remaining rows represent the overrides. In the example, you can see that the user has


* one override in category A,
    0    System      <---- define R1(P), R2(N), R3(N), R4(A)
* three overrides in the category B
            |              assign R1
* two overrides the course
            |
* two overrides in the quiz itself. 
    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


When representing a complex example like this, it is convenient to line up the override permissions under the role that's being overridden, but it is not necessary to do so because we don't "add" permissions in columns the way we add numbers.


We will "add" the permissions in some sense and write the result below the horizontal line. The result, which we call the ''computed permission'', will be either ''A'', ''P'', or ''X''. The function will return ''true'' or ''false'' based on the computed permission.
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.


Before we start "adding," check the data to see if it contains a prohibit ''X'' anywhere. If it does, we're done. The computed permission is ''X''. For our example...
We will now set up a table to hold the permission data. The table has
*one column for each role assignment, listed in order of increasing assignment depth. Columns      are labeled with the role names.  
*one row for each context level, listed in order of increasing context depth. Rows are labeled with the context level.


    P A P
In the quiz example, ''R1'' is assigned in the System context, so it becomes the first column. ''R2'' and ''R3'' are assigned in the second highest context, so they become the next two columns (it doesn't matter which one goes first). ''R4'' and ''R1'' are assigned in the lowest context, so we list them last (again, it doesn't matter which one goes first). Here is our table with the rows and columns labeled:
          A
    P A  X
    A    P
      P  A
    --------
      X    <----- computed permission


        R1  R2  R3  R4  R1
      +------------------------
    0 |
    1 |
    2 |
    3 |
    4 |


If there is no ''X'', the addition algorithm must be used. Unlike normal addition, the addition algorithm


* starts on the bottom line and works its way up
We add vertical lines to group role assignments made in the same context.
* considers one whole line at a time (rather than working in columns)
* may stop with a result before reaching the top line.


Here is the algorithm:
        R1  R2  R3  R4  R1
      +-----+---------+--------
    0 |    |        |
    1 |    |        |
    2 |    |        |
    3 |    |        |
    4 |    |        |


#Start on the bottom line.
The table is now ready to have the permission data addedIn the first row, put the permission from the role definitions.
#If there is only one permission on the line, take its value as the computed permission and STOP
#If there are two more permissions on the line, add them using the following numerical equivalents: A = +1, P = -1
##If the sum is positive, the computed permission is ''A''. STOP 
##If the sum is negative, the computed permission is ''P''. STOP  
##If the sum is zero, the calculation is inconclusive. If this this is already the top line, the computed permission is ''P''; STOP.  Otherwise, move to the line above and go to step 2.


Notice that the algorithm stops when either (1) a conclusive result is obtained, or (2) the top line is reached without obtaining a conclusive result.
        R1  R2  R3  R4  R1
      +-----+---------+--------
    0 |  P  | N    N  | A    P
    1 |    |        |
    2 |    |        |
    3 |    |        |
    4 |    |        |


Let's take our original example, replacing the ''X'' by ''P'' to make it more interesting:
The remaining data come from overrides.  We place the value from each override in
*the column corresponding to the role being overridden
*the row corresponding the level at which the override is made.


    P  A  P
To illustrate, let's add just one of the overrides:
          A
    P  A  P
    A    P
      P  A
    --------
      ? 


Now carry out the steps of the algorithm
        R1  R2  R3  R4  R1
      +-----+---------+--------
    0 |  P  | N    N  | A    P
    1 |    |        |
    2 |    |        |
    3 |    | X      |
    4 |    |        |


* There are two permissions on line 5
The X represents the override to role R2 made in the course context. Let's add the rest of the overrides:
* Since ''P + A = 0'', the result is inconclusive and we must look at line 4.
* There are two permissions on line 4.
* Since ''A + P = 0'', the result is inconclusive and we must look at line 3.
* There are three permissions on line 3.
* Since ''A + P + P = -1'' we have a conclusive result! The computed permission is ''P''.


     P  A P
        R1  R2  R3  R4  R1
          A
      +-----+---------+--------
     P A P
     0 |  | N    N  | A   P
     A     P
     1 | N |        | N    N
      P A
     2 |     |        |
     --------
    3 |    | X    A |
      P      <----- computed permission
     4 |    |        |


If the computed permission had been ''A'', the function would have returned ''true'' without further ado, allowing the user to perform the action.


However if the computed permission is ''P'' or ''X'', the function does not immediately return ''false''. Rather, it tests if the user has ''moodle/site:doanything = Allow'' (since this permission trumps all others). The function does this by calling itself:
The table is now fully populated, and we are ready to calculate the permission.  The algorithm is simple.  It follows a path through the table indicated by nodes and arrows in the diagram
and stops as soon as it has a conclusive result.
 
# If there is an X anywhere in the table, STOP.  The calculated permission is ''X''
# Go to the START node
# If there is no next node, STOP.  The calculated permission is ''P''
# Otherwise, follow the arrow to the next node
#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.
 
If we execute the algorithm with the quiz data, we stop at step 1 with a calculated permission of X.  That was too easy, so let's change the X to a P and see what happens:
 
        R1  R2  R3  R4  R1
      +-----+---------+--------
    0 |  P  | A    P  | A    P
    1 |  N  |        | N    N
    2 |    |        |
    3 |    | P    A  |
    4 |    |        |
 
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.
 
However if the calculated permission is ''P'' or ''X'', ''has_capability()'' does not immediately return ''false''. Rather, it tests 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." 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) that are 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.  The algorithm searches upward in each column 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. 
 
==A practical example==
The Predefined roles (Student, Teacher, etc.) are ''complementary'' in the sense that their permissions just add and never conflict.  As long as you're using Moodle in a traditional way, relying only on the Predefined roles and not doing any overrides, then no matter how many roles you have in a given context, you'll never get an A-P conflict.
 
Consider the following scenario: 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.  She 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.
 
        AuthUser  Creator  Teacher
      +----------------------------
    0 |    N        N        A 
    1 |               
    2 |               
    3 |                       
    4 |               
 
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
 
        AuthUser  Creator  Teacher
      +----------------------------
    0 |    N        N        A
    1 |               
    2 |               
    3 |                       
    4 |                        P               
 
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/quiz:attempt = Prevent'' in the Categoy B context:
 
        AuthUser  Creator  Teacher
      +----------------------------
    0 |    N        N        A
    1 |               
    2 |              P
    3 |                       
    4 |                                       
 
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.


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


==See also==
==See also==

Revision as of 17:03, 24 February 2008


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.

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!

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 computes 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 computes permissions lazily, i.e., on demand. Moodle does not cache computed permissions, but recomputes 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.

The function is called as follows:

   has_capability(CAP,CONTEXT,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

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.

Suppose a 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).

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(P), R2(N), R3(N), R4(A)
           |               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.

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

  • one column for each role assignment, listed in order of increasing assignment depth. Columns are labeled with the role names.
  • one row for each context level, listed in order of increasing context depth. Rows are labeled with the context level.

In the quiz example, R1 is assigned in the System context, so it becomes the first column. R2 and R3 are assigned in the second highest context, so they become the next two columns (it doesn't matter which one goes first). R4 and R1 are assigned in the lowest context, so we list them last (again, it doesn't matter which one goes first). Here is our table with the rows and columns labeled:

        R1   R2   R3   R4   R1
     +------------------------
   0 |
   1 |
   2 |
   3 |
   4 |


We add vertical lines to group role assignments made in the same context.

        R1   R2   R3   R4   R1
     +-----+---------+--------
   0 |     |         |
   1 |     |         |
   2 |     |         |
   3 |     |         |
   4 |     |         |

The table is now ready to have the permission data added. In the first row, put the permission from the role definitions.

        R1   R2   R3   R4   R1
     +-----+---------+--------
   0 |  P  | N    N  | A    P
   1 |     |         |
   2 |     |         |
   3 |     |         |
   4 |     |         |

The remaining data come from overrides. We place the value from each override in

  • the column corresponding to the role being overridden
  • the row corresponding the level at which the override is made.

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

        R1   R2   R3   R4   R1
     +-----+---------+--------
   0 |  P  | N    N  | A    P
   1 |     |         |
   2 |     |         |
   3 |     | X       |
   4 |     |         |

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

        R1   R2   R3   R4   R1
     +-----+---------+--------
   0 |  P  | N    N  | A    P
   1 |  N  |         | N    N
   2 |     |         |
   3 |     | X    A  |
   4 |     |         |


The table is now fully populated, and we are ready to calculate the permission. The algorithm is simple. It follows a path through the table indicated by nodes and arrows in the diagram and stops as soon as it has a conclusive result.

  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
    1. If the sum is positive, STOP. The calculated permission is A
    2. If the sum is negative, STOP. The calculated permission is P
    3. 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.

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

        R1   R2   R3   R4   R1
     +-----+---------+--------
   0 |  P  | A    P  | A    P
   1 |  N  |         | N    N
   2 |     |         |
   3 |     | P    A  |
   4 |     |         |

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.

However if the calculated permission is P or X, has_capability() does not immediately return false. Rather, it tests 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." 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) that are 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. The algorithm searches upward in each column 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.

A practical example

The Predefined roles (Student, Teacher, etc.) are complementary in the sense that their permissions just add and never conflict. As long as you're using Moodle in a traditional way, relying only on the Predefined roles and not doing any overrides, then no matter how many roles you have in a given context, you'll never get an A-P conflict.

Consider the following scenario: 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. She 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.

        AuthUser  Creator  Teacher
     +----------------------------
   0 |     N        N         A   
   1 |                
   2 |                
   3 |                         
   4 |                

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

        AuthUser  Creator  Teacher
     +----------------------------
   0 |     N        N         A
   1 |                
   2 |                
   3 |                         
   4 |                        P                

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/quiz:attempt = Prevent in the Categoy B context:

        AuthUser  Creator  Teacher
     +----------------------------
   0 |     N        N         A
   1 |                
   2 |              P 
   3 |                         
   4 |                                         

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