MDL-68974 admin: Prevent login as outside of the desired context
authorAndrew Nicols <andrew@nicols.co.uk>
Mon, 20 Jul 2020 04:28:35 +0000 (12:28 +0800)
committerJenkins <jenkins@worker08.test.in.moodle.com>
Tue, 8 Sep 2020 06:54:26 +0000 (08:54 +0200)
lib/accesslib.php

index ab6f09e..46652ef 100644 (file)
@@ -521,6 +521,16 @@ function has_capability($capability, context $context, $user = null, $doanything
         }
     }
 
         }
     }
 
+    if (!empty($USER->loginascontext)) {
+        // The current user is logged in as another user and can assume their identity at or below the `loginascontext`
+        // defined in the USER session.
+        // The user may not assume their identity at any other location.
+        if (!$USER->loginascontext->is_parent_of($context, true)) {
+            // The context being checked is not the specified context, or one of its children.
+            return false;
+        }
+    }
+
     // Find out if user is admin - it is not possible to override the doanything in any way
     // and it is not possible to switch to admin role either.
     if ($doanything) {
     // Find out if user is admin - it is not possible to override the doanything in any way
     // and it is not possible to switch to admin role either.
     if ($doanything) {
@@ -5590,6 +5600,30 @@ abstract class context extends stdClass implements IteratorAggregate {
         return $result;
     }
 
         return $result;
     }
 
+    /**
+     * Determine if the current context is a parent of the possible child.
+     *
+     * @param   context $possiblechild
+     * @param   bool $includeself Whether to check the current context
+     * @return  bool
+     */
+    public function is_parent_of(context $possiblechild, bool $includeself): bool {
+        // A simple substring check is used on the context path.
+        // The possible child's path is used as a haystack, with the current context as the needle.
+        // The path is prefixed with '+' to ensure that the parent always starts at the top.
+        // It is suffixed with '+' to ensure that parents are not included.
+        // The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
+        // The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
+        // The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
+        $haystacksuffix = $includeself ? '/+' : '+';
+
+        $strpos = strpos(
+            "+{$possiblechild->path}{$haystacksuffix}",
+            "+{$this->path}/"
+        );
+        return $strpos === 0;
+    }
+
     /**
      * Returns parent contexts of this context in reversed order, i.e. parent first,
      * then grand parent, etc.
     /**
      * Returns parent contexts of this context in reversed order, i.e. parent first,
      * then grand parent, etc.
@@ -5614,6 +5648,30 @@ abstract class context extends stdClass implements IteratorAggregate {
         return $result;
     }
 
         return $result;
     }
 
+    /**
+     * Determine if the current context is a child of the possible parent.
+     *
+     * @param   context $possibleparent
+     * @param   bool $includeself Whether to check the current context
+     * @return  bool
+     */
+    public function is_child_of(context $possibleparent, bool $includeself): bool {
+        // A simple substring check is used on the context path.
+        // The current context is used as a haystack, with the possible parent as the needle.
+        // The path is prefixed with '+' to ensure that the parent always starts at the top.
+        // It is suffixed with '+' to ensure that children are not included.
+        // The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
+        // The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
+        // The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
+        $haystacksuffix = $includeself ? '/+' : '+';
+
+        $strpos = strpos(
+            "+{$this->path}{$haystacksuffix}",
+            "+{$possibleparent->path}/"
+        );
+        return $strpos === 0;
+    }
+
     /**
      * Returns parent context ids of this context in reversed order, i.e. parent first,
      * then grand parent, etc.
     /**
      * Returns parent context ids of this context in reversed order, i.e. parent first,
      * then grand parent, etc.