Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

namespace core\session;

use core\context\course as context_course;
use core\context\system as context_system;

/**
 * Unit tests for loginas_helper class.
 *
 * @package core
 * @author Jason den Dulk <jasondendulk@catalyst-au.net>
 * @covers \core\session\loginas_helper
 * @copyright 2025 Catalyst IT
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
final class loginas_helper_test extends \advanced_testcase {
    /**
     * Tests various users wanting to login as other users of the same role.
     */
    public function test_loginas_same_role(): void {
        global $CFG, $DB;

        $this->resetAfterTest();

        $managerrole = $DB->get_field('role', 'id', ['shortname' => 'manager']);

        $user1 = $this->getDataGenerator()->create_user();
        $user2 = $this->getDataGenerator()->create_user();

        // By default, users cannot login as other users.
        $this->assertNull(loginas_helper::get_context_user_can_login_as($user1, $user2));
        $this->assertNull(loginas_helper::get_context_user_can_login_as($user2, $user1));

        // Admins can login as other admins.
        $originalsiteadmins = $CFG->siteadmins;
        $CFG->siteadmins .= ',' . $user1->id . ',' . $user2->id;
        $systemcontext = context_system::instance();
        $this->assertEquals($systemcontext, loginas_helper::get_context_user_can_login_as($user1, $user2));
        $this->assertEquals($systemcontext, loginas_helper::get_context_user_can_login_as($user2, $user1));

        // Managers can login as other managers.
        $user1 = $this->getDataGenerator()->create_user();
        $user2 = $this->getDataGenerator()->create_user();
        role_assign($managerrole, $user1->id, $systemcontext->id);
        role_assign($managerrole, $user2->id, $systemcontext->id);

        $this->assertEquals($systemcontext, loginas_helper::get_context_user_can_login_as($user1, $user2));
        $this->assertEquals($systemcontext, loginas_helper::get_context_user_can_login_as($user2, $user1));

        // Course managers can login as other course managers, but only in the course context.
        $user1 = $this->getDataGenerator()->create_user();
        $user2 = $this->getDataGenerator()->create_user();

        $course = $this->getDataGenerator()->create_course();
        $coursecontext = context_course::instance($course->id);
        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'manager');
        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'manager');

        $this->assertNull(loginas_helper::get_context_user_can_login_as($user1, $user2));
        $this->assertNull(loginas_helper::get_context_user_can_login_as($user2, $user1));

        $this->assertEquals($coursecontext, loginas_helper::get_context_user_can_login_as($user1, $user2, $course));
        $this->assertEquals($coursecontext, loginas_helper::get_context_user_can_login_as($user2, $user1, $course));

        // Students cannot login as another student.
        $user1 = $this->getDataGenerator()->create_user();
        $user2 = $this->getDataGenerator()->create_user();

        $course = $this->getDataGenerator()->create_course();
        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');

        $this->assertNull(loginas_helper::get_context_user_can_login_as($user1, $user2));
        $this->assertNull(loginas_helper::get_context_user_can_login_as($user2, $user1));

        $this->assertNull(loginas_helper::get_context_user_can_login_as($user1, $user2, $course));
        $this->assertNull(loginas_helper::get_context_user_can_login_as($user2, $user1, $course));
    }

    /**
     * Tests trying to login as a deleted user.
     */
    public function test_loginas_deleted_user(): void {
        global $USER;

        $this->resetAfterTest();
        $this->setAdminUser();

        // Sanity check that ordinary login as works.
        $user = $this->getDataGenerator()->create_user();
        $this->assertEquals(context_system::instance(), loginas_helper::get_context_user_can_login_as($USER, $user));

        // Cannot login as a user that has been deleted.
        $user = $this->getDataGenerator()->create_user(['deleted' => true]);
        $this->assertNull(loginas_helper::get_context_user_can_login_as($USER, $user));
    }

    /**
     * Tests various users wanting to login as other users of differing roles.
     */
    public function test_loginas_different_roles(): void {
        global $CFG, $DB;

        $this->resetAfterTest();

        $systemcontext = context_system::instance();
        $managerrole = $DB->get_field('role', 'id', ['shortname' => 'manager']);
        $originalsiteadmins = $CFG->siteadmins;

        $users = [];
        for ($i = 0; $i < 11; ++$i) {
            $users[] = $this->getDataGenerator()->create_user();
        }

        $courses = [
            $this->getDataGenerator()->create_course(),
            $this->getDataGenerator()->create_course(),
        ];
        $coursecontexts = [
            context_course::instance($courses[0]->id),
            context_course::instance($courses[1]->id),
        ];

        // User 0 is an admin.
        $CFG->siteadmins .= ',' . $users[0]->id;

        // User 1 is a manager.
        role_assign($managerrole, $users[1]->id, $systemcontext->id);

        // User 2 is a manager and an admin.
        $CFG->siteadmins .= ',' . $users[2]->id;
        role_assign($managerrole, $users[2]->id, $systemcontext->id);

        // User 3 is a course manager for course 0.
        $this->getDataGenerator()->enrol_user($users[3]->id, $courses[0]->id, 'manager');

        // User 4 is a course manager for course 0 and a site manager.
        $this->getDataGenerator()->enrol_user($users[4]->id, $courses[0]->id, 'manager');
        role_assign($managerrole, $users[4]->id, $systemcontext->id);

        // User 5 is a course manager for course 0 and an admin.
        $this->getDataGenerator()->enrol_user($users[5]->id, $courses[0]->id, 'manager');
        $CFG->siteadmins .= ',' . $users[5]->id;

        // User 6 is a student for course 0.
        $this->getDataGenerator()->enrol_user($users[6]->id, $courses[0]->id, 'student');

        // User 7 is a student for course 0 and an admin.
        $this->getDataGenerator()->enrol_user($users[7]->id, $courses[0]->id, 'student');
        $CFG->siteadmins .= ',' . $users[7]->id;

        // User 8 is a course manager for course 1.
        $this->getDataGenerator()->enrol_user($users[8]->id, $courses[1]->id, 'manager');

        // User 9 is a student for course 1.
        $this->getDataGenerator()->enrol_user($users[9]->id, $courses[1]->id, 'student');

        // User 10 is a user without courses or roles.

        // This matrix defines loginas expectations. 'S' = system context. # = course context. 'X' = nothing.
        $matrix = [
            // ... 0    1    2    3    4    5    6    7    8    9    10.
            0 => ['X', 'S', 'S', 'S', 'S', 'S', 'S', 'S', 'S', 'S', 'S'],
            1 => ['X', 'X', 'X', 'S', 'S', 'X', 'S', 'X', 'S', 'S', 'S'],
            2 => ['S', 'S', 'X', 'S', 'S', 'S', 'S', 'S', 'S', 'S', 'S'],
            3 => ['X', 'X', 'X', 'X', 'X', 'X', '0', 'X', 'X', 'X', 'X'],
            4 => ['X', 'S', 'X', 'S', 'X', 'X', 'S', 'X', 'S', 'S', 'S'],
            5 => ['S', 'S', 'S', 'S', 'S', 'X', 'S', 'S', 'S', 'S', 'S'],
            6 => ['X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'],
            7 => ['S', 'S', 'S', 'S', 'S', 'S', 'S', 'X', 'S', 'S', 'S'],
            8 => ['X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', '1', 'X'],
            9 => ['X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'],
            10 => ['X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'],
        ];

        // Now test each user against each other user, and compare the results to the expectation matrix.
        foreach ($users as $uid => $user) {
            foreach ($users as $oid => $other) {
                $resultsnocourse = loginas_helper::get_context_user_can_login_as($user, $other);
                $resultscourse0 = loginas_helper::get_context_user_can_login_as($user, $other, $courses[0]);
                $resultscourse1 = loginas_helper::get_context_user_can_login_as($user, $other, $courses[1]);

                switch ($matrix[$uid][$oid]) {
                    case 'X':
                        // No loginas is possible.
                        $this->assertNull($resultsnocourse);
                        $this->assertNull($resultscourse0);
                        $this->assertNull($resultscourse1);
                        break;
                    case 'S':
                        // Loginas can happen at the site level.
                        $this->assertEquals($systemcontext, $resultsnocourse);
                        $this->assertEquals($systemcontext, $resultscourse0);
                        $this->assertEquals($systemcontext, $resultscourse1);
                        break;
                    case '0':
                        // Loginas can only happen within the context of course 0.
                        $this->assertNull($resultsnocourse);
                        $this->assertEquals($coursecontexts[0], $resultscourse0);
                        $this->assertNull($resultscourse1);
                        break;
                    case '1':
                        // Loginas can only happen within the context of course 1.
                        $this->assertNull($resultsnocourse);
                        $this->assertNull($resultscourse0);
                        $this->assertEquals($coursecontexts[1], $resultscourse1);
                        break;
                }
            }
        }
    }

    /**
     * Providor function for test_loginas_groups().
     *
     * @return array[]
     */
    public static function loginas_groups_providor(): array {
        return [
            'Separate groups' => [
                'groupmode' => SEPARATEGROUPS,
                'accessallgroups' => true,
                'canloginassamegroup' => true,
                'canloginasdifferentgroup' => true,
            ],
            'Separate groups, no access' => [
                'groupmode' => SEPARATEGROUPS,
                'accessallgroups' => false,
                'canloginassamegroup' => true,
                'canloginasdifferentgroup' => false,
            ],
            'Visible groups' => [
                'groupmode' => VISIBLEGROUPS,
                'accessallgroups' => true,
                'canloginassamegroup' => true,
                'canloginasdifferentgroup' => true,
            ],
            'Visible groups, no access' => [
                'groupmode' => VISIBLEGROUPS,
                'accessallgroups' => false,
                'canloginassamegroup' => true,
                'canloginasdifferentgroup' => true,
            ],
        ];
    }

    /**
     * Tests users wanting to login as other users of different groups.
     *
     * @param int $groupmode
     * @param bool $accessallgroups
     * @param bool $canloginassamegroup
     * @param bool $canloginasdifferentgroup
     *
     * @dataProvider loginas_groups_providor
     */
    public function test_loginas_groups(
        int $groupmode,
        bool $accessallgroups,
        bool $canloginassamegroup,
        bool $canloginasdifferentgroup
    ): void {
        global $DB;

        $this->resetAfterTest();

        // Set up manager and students.
        $manager = $this->getDataGenerator()->create_user();
        $student1 = $this->getDataGenerator()->create_user();
        $student2 = $this->getDataGenerator()->create_user();

        $course = $this->getDataGenerator()->create_course(['groupmode' => $groupmode]);
        $coursecontext = context_course::instance($course->id);

        // Add or remove accessallgroups permission.
        $managerroleid = $DB->get_field('role', 'id', ['shortname' => 'manager'], MUST_EXIST);
        $permission = $accessallgroups ? CAP_ALLOW : CAP_PREVENT;
        assign_capability('moodle/site:accessallgroups', $permission, $managerroleid, $coursecontext, true);

        $this->getDataGenerator()->enrol_user($manager->id, $course->id, 'manager');
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student');

        // Manager and student 1 are in the same group. Student 2 is in a different group.
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);

        $this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $manager->id]);
        $this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $student1->id]);
        $this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $student2->id]);

        // Manager wants to login as a student in the same group.
        $this->assertEquals(
            $canloginassamegroup,
            (bool) loginas_helper::get_context_user_can_login_as($manager, $student1, $course)
        );
        // Manager wants to login as a student in a different group.
        $this->assertEquals(
            $canloginasdifferentgroup,
            (bool) loginas_helper::get_context_user_can_login_as($manager, $student2, $course)
        );
    }
}