MDL-62663 core_user: Enforce viewalldetails capability when editing user
authorAndrew Hancox <andrewdchancox@googlemail.com>
Fri, 15 Jun 2018 12:09:33 +0000 (13:09 +0100)
committerAndrew Hancox <andrewdchancox@googlemail.com>
Tue, 10 Jul 2018 12:28:24 +0000 (13:28 +0100)
user/profile/lib.php
user/tests/behat/custom_profile_fields.feature [new file with mode: 0644]

index 29cb7b7..25b719d 100644 (file)
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-define ('PROFILE_VISIBLE_ALL',     '2'); // Only visible for users with moodle/user:update capability.
-define ('PROFILE_VISIBLE_PRIVATE', '1'); // Either we are viewing our own profile or we have moodle/user:update capability.
-define ('PROFILE_VISIBLE_NONE',    '0'); // Only visible for moodle/user:update capability.
+/**
+ * Visible to anyone who can view the user.
+ * Editable by the profile owner if they have the moodle/user:editownprofile capability
+ * or any user with the moodle/user:update capability.
+ */
+define('PROFILE_VISIBLE_ALL', '2');
+/**
+ * Visible to the profile owner or anyone with the moodle/user:viewalldetails capability.
+ * Editable by the profile owner if they have the moodle/user:editownprofile capability
+ * or any user with moodle/user:viewalldetails and moodle/user:update capabilities.
+ */
+define('PROFILE_VISIBLE_PRIVATE', '1');
+/**
+ * Only visible to users with the moodle/user:viewalldetails capability.
+ * Only editable by users with the moodle/user:viewalldetails and moodle/user:update capabilities.
+ */
+define('PROFILE_VISIBLE_NONE', '0');
 
 /**
  * Base class for the customisable profile fields.
@@ -131,15 +145,14 @@ class profile_field_base {
      * @return bool
      */
     public function edit_field($mform) {
-        if ($this->field->visible != PROFILE_VISIBLE_NONE
-          or has_capability('moodle/user:update', context_system::instance())) {
-
-            $this->edit_field_add($mform);
-            $this->edit_field_set_default($mform);
-            $this->edit_field_set_required($mform);
-            return true;
+        if (!$this->is_editable()) {
+            return false;
         }
-        return false;
+
+        $this->edit_field_add($mform);
+        $this->edit_field_set_default($mform);
+        $this->edit_field_set_required($mform);
+        return true;
     }
 
     /**
@@ -148,12 +161,12 @@ class profile_field_base {
      * @return bool
      */
     public function edit_after_data($mform) {
-        if ($this->field->visible != PROFILE_VISIBLE_NONE
-          or has_capability('moodle/user:update', context_system::instance())) {
-            $this->edit_field_set_locked($mform);
-            return true;
+        if (!$this->is_editable()) {
+            return false;
         }
-        return false;
+
+        $this->edit_field_set_locked($mform);
+        return true;
     }
 
     /**
@@ -429,6 +442,31 @@ class profile_field_base {
         }
     }
 
+    /**
+     * Check if the field data is editable for the current user
+     * This method should not generally be overwritten by child classes.
+     * @return bool
+     */
+    public function is_editable() {
+        global $USER;
+
+        if (!$this->is_visible()) {
+            return false;
+        }
+
+        $systemcontext = context_system::instance();
+
+        if ($this->userid == $USER->id && has_capability('moodle/user:editownprofile', $systemcontext)) {
+            return true;
+        }
+
+        if (has_capability('moodle/user:update', $systemcontext)) {
+            return true;
+        }
+
+        return false;
+    }
+
     /**
      * Check if the field data is considered empty
      * @internal This method should not generally be overwritten by child classes.
@@ -565,27 +603,25 @@ function profile_load_data($user) {
  * @param int $userid id of user whose profile is being edited.
  */
 function profile_definition($mform, $userid = 0) {
-    global $CFG, $DB;
-
-    // If user is "admin" fields are displayed regardless.
-    $update = has_capability('moodle/user:update', context_system::instance());
-
     $categories = profile_get_user_fields_with_data_by_category($userid);
     foreach ($categories as $categoryid => $fields) {
         // Check first if *any* fields will be displayed.
-        $display = false;
+        $fieldstodisplay = [];
+
         foreach ($fields as $formfield) {
-            if ($formfield->is_visible()) {
-                $display = true;
+            if ($formfield->is_editable()) {
+                $fieldstodisplay[] = $formfield;
             }
         }
 
+        if (empty($fieldstodisplay)) {
+            continue;
+        }
+
         // Display the header and the fields.
-        if ($display or $update) {
-            $mform->addElement('header', 'category_'.$categoryid, format_string($formfield->get_category_name()));
-            foreach ($fields as $formfield) {
-                $formfield->edit_field($mform);
-            }
+        $mform->addElement('header', 'category_'.$categoryid, format_string($fields[0]->get_category_name()));
+        foreach ($fieldstodisplay as $formfield) {
+            $formfield->edit_field($mform);
         }
     }
 }
diff --git a/user/tests/behat/custom_profile_fields.feature b/user/tests/behat/custom_profile_fields.feature
new file mode 100644 (file)
index 0000000..9359113
--- /dev/null
@@ -0,0 +1,188 @@
+@core @core_user
+Feature: Custom profile fields should be visible and editable by those with the correct permissions.
+
+  Background: Attempting to self-register as a new user with empty names
+    Given the following "users" exist:
+      | username            | firstname           | lastname | email                           |
+      | userwithinformation | userwithinformation | 1        | userwithinformation@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1        | 0        | 1         |
+    And the following "course enrolments" exist:
+      | user                | course | role    |
+      | userwithinformation | C1     | student |
+
+    And I log in as "admin"
+    And I navigate to "User profile fields" node in "Site administration > Users > Accounts"
+    And I set the field "datatype" to "Text input"
+    And I set the following fields to these values:
+      | Short name                    | notvisible_field |
+      | Name                          | notvisible_field |
+      | Who is this field visible to? | Not visible      |
+    And I click on "Save changes" "button"
+
+    And I set the field "datatype" to "Text input"
+    And I set the following fields to these values:
+      | Short name                    | uservisible_field |
+      | Name                          | uservisible_field |
+      | Who is this field visible to? | Visible to user   |
+    And I click on "Save changes" "button"
+
+    And I set the field "datatype" to "Text input"
+    And I set the following fields to these values:
+      | Short name                    | everyonevisible_field |
+      | Name                          | everyonevisible_field |
+      | Who is this field visible to? | Visible to everyone   |
+    And I click on "Save changes" "button"
+
+    And I navigate to "Browse list of users" node in "Site administration > Users > Accounts"
+    And I click on ".icon[title=Edit]" "css_element" in the "userwithinformation@example.com" "table_row"
+    And I expand all fieldsets
+    And I set the field "notvisible_field" to "notvisible_field_information"
+    And I set the field "uservisible_field" to "uservisible_field_information"
+    And I set the field "everyonevisible_field" to "everyonevisible_field_information"
+    And I click on "Update profile" "button"
+    And I log out
+
+  @javascript
+  Scenario: User with moodle/user:update but without moodle/user:viewalldetails can only update visible profile fields.
+    Given the following "roles" exist:
+      | name         | shortname   | description | archetype |
+      | Update Users | updateusers | updateusers |           |
+    And the following "permission overrides" exist:
+      | capability         | permission | role        | contextlevel | reference |
+      | moodle/user:update | Allow      | updateusers | System       |           |
+    And the following "users" exist:
+      | username         | firstname   | lastname | email                   |
+      | user_updateusers | updateusers | 1        | updateusers@example.com |
+    And the following "role assigns" exist:
+      | user             | role        | contextlevel | reference |
+      | user_updateusers | updateusers | System       |           |
+    And the following "course enrolments" exist:
+      | user             | course | role           |
+      | user_updateusers | C1     | editingteacher |
+    And I log in as "user_updateusers"
+    And I am on "Course 1" course homepage
+    And I navigate to course participants
+    And I follow "userwithinformation 1"
+
+    Then I should see "everyonevisible_field"
+    And I should see "everyonevisible_field_information"
+    And I should not see "uservisible_field"
+    And I should not see "uservisible_field_information"
+    And I should not see "notvisible_field"
+    And I should not see "notvisible_field_information"
+    And I follow "Edit profile"
+    And the following fields match these values:
+      | everyonevisible_field | everyonevisible_field_information |
+    And I should not see "uservisible_field"
+    And I should not see "notvisible_field"
+
+  @javascript
+  Scenario: User with moodle/user:viewalldetails but without moodle/user:update can view all profile fields.
+    Given the following "roles" exist:
+      | name             | shortname      | description    | archetype |
+      | View All Details | viewalldetails | viewalldetails |           |
+    And the following "permission overrides" exist:
+      | capability                 | permission | role           | contextlevel | reference |
+      | moodle/user:viewalldetails | Allow      | viewalldetails | System       |           |
+    And the following "users" exist:
+      | username            | firstname      | lastname | email                      |
+      | user_viewalldetails | viewalldetails | 1        | viewalldetails@example.com |
+    And the following "role assigns" exist:
+      | user                | role           | contextlevel | reference |
+      | user_viewalldetails | viewalldetails | System       |           |
+    And the following "course enrolments" exist:
+      | user                | course | role           |
+      | user_viewalldetails | C1     | editingteacher |
+    And I log in as "user_viewalldetails"
+    And I am on "Course 1" course homepage
+    And I navigate to course participants
+    And I follow "userwithinformation 1"
+
+    Then I should see "everyonevisible_field"
+    And I should see "everyonevisible_field_information"
+    And I should see "uservisible_field"
+    And I should see "uservisible_field_information"
+    And I should see "notvisible_field"
+    And I should see "notvisible_field_information"
+    And I should not see "Edit profile"
+
+  @javascript
+  Scenario: User with moodle/user:viewalldetails and moodle/user:update capabilities can view and edit all profile fields.
+    Given the following "roles" exist:
+      | name                              | shortname                    | description                  | archetype |
+      | View All Details and Update Users | viewalldetailsandupdateusers | viewalldetailsandupdateusers |           |
+    And the following "permission overrides" exist:
+      | capability                 | permission | role                         | contextlevel | reference |
+      | moodle/user:viewalldetails | Allow      | viewalldetailsandupdateusers | System       |           |
+      | moodle/user:update         | Allow      | viewalldetailsandupdateusers | System       |           |
+    And the following "users" exist:
+      | username                          | firstname                    | lastname | email                                    |
+      | user_viewalldetailsandupdateusers | viewalldetailsandupdateusers | 1        | viewalldetailsandupdateusers@example.com |
+    And the following "role assigns" exist:
+      | user                              | role                         | contextlevel | reference |
+      | user_viewalldetailsandupdateusers | viewalldetailsandupdateusers | System       |           |
+    And the following "course enrolments" exist:
+      | user                              | course | role           |
+      | user_viewalldetailsandupdateusers | C1     | editingteacher |
+    And I log in as "user_viewalldetailsandupdateusers"
+    And I am on "Course 1" course homepage
+    And I navigate to course participants
+    And I follow "userwithinformation 1"
+
+    Then I should see "everyonevisible_field"
+    And I should see "everyonevisible_field_information"
+    And I should see "uservisible_field"
+    And I should see "uservisible_field_information"
+    And I should see "notvisible_field"
+    And I should see "notvisible_field_information"
+    And I follow "Edit profile"
+    And the following fields match these values:
+      | everyonevisible_field | everyonevisible_field_information |
+      | uservisible_field     | uservisible_field_information     |
+      | notvisible_field      | notvisible_field_information      |
+
+  @javascript
+  Scenario: Users can view and edit custom profile fields except those marked as not visible.
+    Given I log in as "userwithinformation"
+    And I follow "Profile" in the user menu
+
+    Then I should see "everyonevisible_field"
+    And I should see "everyonevisible_field_information"
+    And I should see "uservisible_field"
+    And I should see "uservisible_field_information"
+    And I should not see "notvisible_field"
+    And I should not see "notvisible_field_information"
+
+    And I click on "Edit profile" "link" in the "region-main" "region"
+    Then the following fields match these values:
+      | everyonevisible_field | everyonevisible_field_information |
+      | uservisible_field     | uservisible_field_information     |
+    And I should not see "notvisible_field"
+    And I should not see "notvisible_field_information"
+
+  @javascript
+  Scenario: Users can view but not edit custom profile fields when denied the edit own profile capability.
+    Given the following "roles" exist:
+      | name                | shortname          | description        | archetype |
+      | Deny editownprofile | denyeditownprofile | denyeditownprofile |           |
+
+    And the following "permission overrides" exist:
+      | capability                 | permission | role               | contextlevel | reference |
+      | moodle/user:editownprofile | Prohibit   | denyeditownprofile | System       |           |
+    And the following "role assigns" exist:
+      | user                | role               | contextlevel | reference |
+      | userwithinformation | denyeditownprofile | System       |           |
+
+    And I log in as "userwithinformation"
+    And I follow "Profile" in the user menu
+
+    Then I should see "everyonevisible_field"
+    And I should see "everyonevisible_field_information"
+    And I should see "uservisible_field"
+    And I should see "uservisible_field_information"
+    And I should not see "notvisible_field"
+    And I should not see "notvisible_field_information"
+
+    And I should not see "Edit profile"
\ No newline at end of file