diff --git a/admin/tool/mfa/.github/workflows/ci.yml b/admin/tool/mfa/.github/workflows/ci.yml deleted file mode 100644 index 4d92066f98f..00000000000 --- a/admin/tool/mfa/.github/workflows/ci.yml +++ /dev/null @@ -1,15 +0,0 @@ -# .github/workflows/ci.yml -name: ci - -on: [push, pull_request] - -jobs: - test: - uses: catalyst/catalyst-moodle-workflows/.github/workflows/ci.yml@main - secrets: - # Required if you plan to publish (uncomment the below) - moodle_org_token: ${{ secrets.MOODLE_ORG_TOKEN }} - with: - #Grunt fails due to CSS styling needing an !important. - disable_grunt: true - release_branches: master diff --git a/admin/tool/mfa/README.md b/admin/tool/mfa/README.md deleted file mode 100644 index cf9e573db6d..00000000000 --- a/admin/tool/mfa/README.md +++ /dev/null @@ -1,12 +0,0 @@ -#NOTE: This master branch has been deprecated. Please see the table below for the correct supported branches. - -## Branches - -| Version | Branch | Patches | -|-----------------|--------------|----------------------| -| Moodle 4.0 - 4.2| MOODLE_400_STABLE | None | -| Moodle 3.8 -3.9 | MOODLE_35_STABLE | None | -| Moodle 3.7 | MOODLE_35_STABLE | MDL-66340 | -| Moodle 3.5-3.6 | MOODLE_35_STABLE | MDL-66340, MDL-60470 | -| Totara 12-15 | MOODLE_35_STABLE | MDL-66340, MDL-60470 | - diff --git a/admin/tool/mfa/classes/event/user_deleted_factor.php b/admin/tool/mfa/classes/event/user_deleted_factor.php index 5fe5eda3ebf..5628b42e254 100644 --- a/admin/tool/mfa/classes/event/user_deleted_factor.php +++ b/admin/tool/mfa/classes/event/user_deleted_factor.php @@ -16,6 +16,8 @@ namespace tool_mfa\event; +use stdClass; + /** * Event for when user factor is deleted. * @@ -37,11 +39,11 @@ class user_deleted_factor extends \core\event\base { * @param stdClass $deleteuser the user who performed the factor delete. * @param string $factorname deleted factor * - * @return user_factor_deleted the user_factor_deleted event + * @return \core\event\base the user_factor_deleted event * * @throws \coding_exception */ - public static function user_deleted_factor_event($user, $deleteuser, $factorname) { + public static function user_deleted_factor_event(stdClass $user, $deleteuser, $factorname): \core\event\base { $data = [ 'relateduserid' => $user->id, @@ -61,7 +63,7 @@ class user_deleted_factor extends \core\event\base { * * @return void */ - protected function init() { + protected function init(): void { $this->data['crud'] = 'd'; $this->data['edulevel'] = self::LEVEL_OTHER; } @@ -71,7 +73,7 @@ class user_deleted_factor extends \core\event\base { * * @return string */ - public function get_description() { + public function get_description(): string { // The log message changed from logging the deleter user object to the ID. This must be kept for backwards compat // With old log events. if (is_object($this->other['delete'])) { @@ -89,7 +91,7 @@ class user_deleted_factor extends \core\event\base { * @return string * @throws \coding_exception */ - public static function get_name() { + public static function get_name(): string { return get_string('event:userdeletedfactor', 'tool_mfa'); } } diff --git a/admin/tool/mfa/classes/event/user_failed_mfa.php b/admin/tool/mfa/classes/event/user_failed_mfa.php index 447f9be0e48..6c4e763a5ad 100644 --- a/admin/tool/mfa/classes/event/user_failed_mfa.php +++ b/admin/tool/mfa/classes/event/user_failed_mfa.php @@ -16,6 +16,8 @@ namespace tool_mfa\event; +use stdClass; + /** * Event for when user successfully passed all MFA factor checks. * @@ -33,13 +35,13 @@ class user_failed_mfa extends \core\event\base { /** * Create instance of event. * - * @param int $user the User object of the User who failed MFA authentication. + * @param stdClass $user the User object of the User who failed MFA authentication. * * @return user_failed_mfa the user_passed_mfa event * * @throws \coding_exception */ - public static function user_failed_mfa_event($user) { + public static function user_failed_mfa_event(stdClass $user): user_failed_mfa { // Build debug info string. $factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types(); $debug = ''; @@ -71,7 +73,7 @@ class user_failed_mfa extends \core\event\base { * * @return void */ - protected function init() { + protected function init(): void { $this->data['crud'] = 'r'; $this->data['edulevel'] = self::LEVEL_OTHER; } @@ -81,7 +83,7 @@ class user_failed_mfa extends \core\event\base { * * @return string */ - public function get_description() { + public function get_description(): string { return "The user with id '{$this->other['userid']}' failed authenticating with MFA.
Information: {$this->other['failurereason']}{$this->other['debug']}"; } @@ -92,7 +94,7 @@ class user_failed_mfa extends \core\event\base { * @return string * @throws \coding_exception */ - public static function get_name() { + public static function get_name(): string { return get_string('event:userfailedmfa', 'tool_mfa'); } } diff --git a/admin/tool/mfa/classes/event/user_passed_mfa.php b/admin/tool/mfa/classes/event/user_passed_mfa.php index 13b25254649..6051c7651cd 100644 --- a/admin/tool/mfa/classes/event/user_passed_mfa.php +++ b/admin/tool/mfa/classes/event/user_passed_mfa.php @@ -16,6 +16,8 @@ namespace tool_mfa\event; +use stdClass; + /** * Event for when user successfully passed all MFA factor checks. * @@ -33,13 +35,13 @@ class user_passed_mfa extends \core\event\base { /** * Create instance of event. * - * @param int $user the User object of the User who passed all MFA factor checks. + * @param stdClass $user the User object of the User who passed all MFA factor checks. * * @return user_passed_mfa the user_passed_mfa event * * @throws \coding_exception */ - public static function user_passed_mfa_event($user) { + public static function user_passed_mfa_event(stdClass $user): user_passed_mfa { // Build debug info string. $factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types(); @@ -65,7 +67,7 @@ class user_passed_mfa extends \core\event\base { * * @return void */ - protected function init() { + protected function init(): void { $this->data['crud'] = 'r'; $this->data['edulevel'] = self::LEVEL_OTHER; } @@ -75,7 +77,7 @@ class user_passed_mfa extends \core\event\base { * * @return string */ - public function get_description() { + public function get_description(): string { return "The user with id '{$this->other['userid']}' successfully passed MFA.
Information: {$this->other['debug']}"; } @@ -85,7 +87,7 @@ class user_passed_mfa extends \core\event\base { * @return string * @throws \coding_exception */ - public static function get_name() { + public static function get_name(): string { return get_string('event:userpassedmfa', 'tool_mfa'); } } diff --git a/admin/tool/mfa/classes/event/user_revoked_factor.php b/admin/tool/mfa/classes/event/user_revoked_factor.php index f692446650f..1fe81bb28db 100644 --- a/admin/tool/mfa/classes/event/user_revoked_factor.php +++ b/admin/tool/mfa/classes/event/user_revoked_factor.php @@ -16,6 +16,8 @@ namespace tool_mfa\event; +use stdClass; + /** * Event for when user successfully revoked MFA Factor. * @@ -33,14 +35,14 @@ class user_revoked_factor extends \core\event\base { /** * Create instance of event. * - * @param int $user the User object of the User who has revoked new factor + * @param stdClass $user the User object of the User who has revoked new factor * @param string $factorname revoked factor * - * @return user_passed_mfa the user_passed_mfa event + * @return self the related event * * @throws \coding_exception */ - public static function user_revoked_factor_event($user, $factorname) { + public static function user_revoked_factor_event(stdClass $user, $factorname): self { $data = [ 'relateduserid' => null, @@ -59,7 +61,7 @@ class user_revoked_factor extends \core\event\base { * * @return void */ - protected function init() { + protected function init(): void { $this->data['crud'] = 'd'; $this->data['edulevel'] = self::LEVEL_OTHER; } @@ -69,7 +71,7 @@ class user_revoked_factor extends \core\event\base { * * @return string */ - public function get_description() { + public function get_description(): string { return "The user with id '{$this->other['userid']}' successfully revoked {$this->other['factorname']}"; } @@ -79,7 +81,7 @@ class user_revoked_factor extends \core\event\base { * @return string * @throws \coding_exception */ - public static function get_name() { + public static function get_name(): string { return get_string('event:userrevokedfactor', 'tool_mfa'); } } diff --git a/admin/tool/mfa/classes/event/user_setup_factor.php b/admin/tool/mfa/classes/event/user_setup_factor.php index 77f9395a06d..fcd2b81fc6e 100644 --- a/admin/tool/mfa/classes/event/user_setup_factor.php +++ b/admin/tool/mfa/classes/event/user_setup_factor.php @@ -16,6 +16,8 @@ namespace tool_mfa\event; +use stdClass; + /** * Event for when user successfully setup new MFA Factor. * @@ -33,14 +35,14 @@ class user_setup_factor extends \core\event\base { /** * Create instance of event. * - * @param object $user the User object of the User who has setup new factor + * @param stdClass $user the User object of the User who has setup new factor * @param string $factorname setup factor * - * @return user_passed_mfa the user_passed_mfa event + * @return self the related event * * @throws \coding_exception */ - public static function user_setup_factor_event($user, $factorname) { + public static function user_setup_factor_event(stdClass $user, $factorname): self { $data = [ 'relateduserid' => null, @@ -59,7 +61,7 @@ class user_setup_factor extends \core\event\base { * * @return void */ - protected function init() { + protected function init(): void { $this->data['crud'] = 'c'; $this->data['edulevel'] = self::LEVEL_OTHER; } @@ -69,7 +71,7 @@ class user_setup_factor extends \core\event\base { * * @return string */ - public function get_description() { + public function get_description(): string { return "The user with id '{$this->other['userid']}' successfully setup {$this->other['factorname']}"; } @@ -79,7 +81,7 @@ class user_setup_factor extends \core\event\base { * @return string * @throws \coding_exception */ - public static function get_name() { + public static function get_name(): string { return get_string('event:usersetupfactor', 'tool_mfa'); } } diff --git a/admin/tool/mfa/classes/local/admin_setting_managemfa.php b/admin/tool/mfa/classes/local/admin_setting_managemfa.php index 6512cd851ca..2d734082900 100644 --- a/admin/tool/mfa/classes/local/admin_setting_managemfa.php +++ b/admin/tool/mfa/classes/local/admin_setting_managemfa.php @@ -43,9 +43,9 @@ class admin_setting_managemfa extends \admin_setting { /** * Always returns true * - * @return true + * @return bool */ - public function get_setting() { + public function get_setting(): bool { return true; } @@ -55,7 +55,7 @@ class admin_setting_managemfa extends \admin_setting { * @param mixed $data * @return string Always returns '' */ - public function write_setting($data) { + public function write_setting($data): string { return ''; } @@ -69,7 +69,7 @@ class admin_setting_managemfa extends \admin_setting { * @throws \coding_exception * @throws \moodle_exception */ - public function output_html($data, $query='') { + public function output_html($data, $query=''): string { global $OUTPUT; $return = $OUTPUT->box_start('generalbox'); diff --git a/admin/tool/mfa/classes/local/factor/fallback.php b/admin/tool/mfa/classes/local/factor/fallback.php index e196b2cdde7..3c3999a17c4 100644 --- a/admin/tool/mfa/classes/local/factor/fallback.php +++ b/admin/tool/mfa/classes/local/factor/fallback.php @@ -36,21 +36,21 @@ class fallback extends object_factor_base { /** * {@inheritDoc} */ - public function get_display_name() { + public function get_display_name(): string { return get_string('fallback', 'tool_mfa'); } /** * {@inheritDoc} */ - public function get_info() { + public function get_info(): string { return get_string('fallback_info', 'tool_mfa'); } /** * {@inheritDoc} */ - public function get_state() { + public function get_state(): string { return \tool_mfa\plugininfo\factor::STATE_FAIL; } @@ -58,10 +58,10 @@ class fallback extends object_factor_base { * Sets the state of the factor check into the session. * Returns whether storing the var was successful. * - * @param mixed $state + * @param string $state * @return bool */ - public function set_state($state) { + public function set_state(string $state): bool { return false; } } diff --git a/admin/tool/mfa/classes/local/factor/object_factor.php b/admin/tool/mfa/classes/local/factor/object_factor.php index f2ef05a5852..403ab1ab495 100644 --- a/admin/tool/mfa/classes/local/factor/object_factor.php +++ b/admin/tool/mfa/classes/local/factor/object_factor.php @@ -25,6 +25,8 @@ namespace tool_mfa\local\factor; +use stdClass; + interface object_factor { /** @@ -33,7 +35,7 @@ interface object_factor { * @return bool * @throws \dml_exception */ - public function is_enabled(); + public function is_enabled(): bool; /** * Returns configured factor weight. @@ -41,7 +43,7 @@ interface object_factor { * @return int * @throws \dml_exception */ - public function get_weight(); + public function get_weight(): int; /** * Returns factor name from language string. @@ -49,7 +51,7 @@ interface object_factor { * @return string * @throws \coding_exception */ - public function get_display_name(); + public function get_display_name(): string; /** * Returns factor info from language string. @@ -57,7 +59,7 @@ interface object_factor { * @return string * @throws \coding_exception */ - public function get_info(); + public function get_info(): string; /** * Defines setup_factor form definition page for particular factor. @@ -66,7 +68,7 @@ interface object_factor { * @return object $mform * @throws \coding_exception */ - public function setup_factor_form_definition($mform); + public function setup_factor_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm; /** * Defines setup_factor form definition page after form data has been set. @@ -75,7 +77,7 @@ interface object_factor { * @return object $mform * @throws \coding_exception */ - public function setup_factor_form_definition_after_data($mform); + public function setup_factor_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm; /** * Implements setup_factor form validation for particular factor. @@ -84,7 +86,7 @@ interface object_factor { * @param array $data * @return array */ - public function setup_factor_form_validation($data); + public function setup_factor_form_validation(array $data): array; /** * Defines login form definition page for particular factor. @@ -93,7 +95,7 @@ interface object_factor { * @return object $mform * @throws \coding_exception */ - public function login_form_definition($mform); + public function login_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm; /** * Defines login form definition page after form data has been set. @@ -102,7 +104,7 @@ interface object_factor { * @return object $mform * @throws \coding_exception */ - public function login_form_definition_after_data($mform); + public function login_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm; /** * Implements login form validation for particular factor. @@ -111,16 +113,16 @@ interface object_factor { * @param array $data * @return array */ - public function login_form_validation($data); + public function login_form_validation(array $data): array; /** * Setups given factor and adds it to user's active factors list. * Returns true if factor has been successfully added, otherwise false. * - * @param array $data - * @return stdClass the factor record, or null. + * @param stdClass $data + * @return stdClass|null the factor record, or null. */ - public function setup_user_factor($data); + public function setup_user_factor(stdClass $data): stdClass|null; /** * Returns an array of all user factors of given type (both active and revoked). @@ -128,7 +130,7 @@ interface object_factor { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user); + public function get_all_user_factors(stdClass $user): array; /** * Returns an array of active user factor records. @@ -137,7 +139,7 @@ interface object_factor { * @param stdClass $user the user to check against. * @return array */ - public function get_active_user_factors($user); + public function get_active_user_factors(stdClass $user): array; /** * Returns true if factor class has factor records that might be revoked. @@ -145,7 +147,7 @@ interface object_factor { * * @return bool */ - public function has_revoke(); + public function has_revoke(): bool; /** * Marks factor record as revoked. @@ -154,97 +156,98 @@ interface object_factor { * @param int $factorid * @return bool */ - public function revoke_user_factor($factorid); + public function revoke_user_factor(?int $factorid = null): bool; /** * When validation code is correct - update lastverified field for given factor. * If factor id is not provided, update all factor entries for user. * * @param int $factorid - * @return bool + * @return bool|\dml_exception */ - public function update_lastverified($factorid); + public function update_lastverified(?int $factorid = null): bool|\dml_exception; /** * Gets lastverified timestamp. * * @param int $factorid - * @return int + * @return int|bool */ - public function get_lastverified($factorid); + public function get_lastverified(int $factorid): int|bool; /** * Returns true if factor needs to be setup by user and has setup_form. * * @return bool */ - public function has_setup(); + public function has_setup(): bool; /** * If has_setup returns true, decides if the setup buttons should be shown on the preferences page. * * @return bool */ - public function show_setup_buttons(); + public function show_setup_buttons(): bool; /** * Returns true if factor requires user input for success or failure during login. * * @return bool */ - public function has_input(); + public function has_input(): bool; /** * Returns the state of the factor check * - * @return mixed + * @return string */ - public function get_state(); + public function get_state(): string; /** * Sets the state of the factor check into the session. * Returns whether storing the var was successful. * - * @param mixed $state + * @param string $state * @return bool */ - public function set_state($state); + public function set_state(string $state): bool; /** * Fires any additional actions required by the factor once the user reaches the pass state. * * @return void */ - public function post_pass_state(); + public function post_pass_state(): void; /** * Retrieves label for a factorid. * * @param int $factorid - * @return string + * @return string|\dml_exception */ - public function get_label($factorid); + public function get_label(int $factorid): string|\dml_exception; /** * Returns a list of urls to not redirect from. * * @return array */ - public function get_no_redirect_urls(); + public function get_no_redirect_urls(): array; /** * Returns all possible states for a user. * - * @param \stdClass $user + * @param stdClass $user + * @return array */ - public function possible_states($user); + public function possible_states(stdClass $user): array; /** * Return summary condition for passing factor. * - * @return array + * @return string */ - public function get_summary_condition(); + public function get_summary_condition(): string; /** * Checks whether the factor combination is valid based on factor behaviour. @@ -254,28 +257,29 @@ interface object_factor { * @param array $combination array of factors that make up the combination * @return bool */ - public function check_combination($combination); + public function check_combination(array $combination): bool; /** * Gets the string for setup button on preferences page. * * @return string the string to display on the button. */ - public function get_setup_string(); + public function get_setup_string(): string; /** * Deletes all instances of a factor for user. * * @param stdClass $user the user to delete for. + * @return void */ - public function delete_factor_for_user($user); + public function delete_factor_for_user(stdClass $user): void; /** * Process a cancel action from a user. * * @return void */ - public function process_cancel_action(); + public function process_cancel_action(): void; /** * Hook point for global auth form action hooks. @@ -283,7 +287,7 @@ interface object_factor { * @param \MoodleQuickForm $mform Form to inject global elements into. * @return void */ - public function global_definition($mform); + public function global_definition(\MoodleQuickForm $mform): void; /** * Hook point for global auth form action hooks. @@ -291,7 +295,7 @@ interface object_factor { * @param \MoodleQuickForm $mform Form to inject global elements into. * @return void */ - public function global_definition_after_data($mform); + public function global_definition_after_data(\MoodleQuickForm $mform): void; /** * Hook point for global auth form action hooks. @@ -300,12 +304,13 @@ interface object_factor { * @param array $files Files form the form. * @return array of errors from validation. */ - public function global_validation($data, $files): array; + public function global_validation(array $data, array $files): array; /** * Hook point for global auth form action hooks. * * @param object $data Data from the form. + * @return void */ - public function global_submit($data); + public function global_submit(object $data): void; } diff --git a/admin/tool/mfa/classes/local/factor/object_factor_base.php b/admin/tool/mfa/classes/local/factor/object_factor_base.php index 2e66752bd99..df4a4b04df9 100644 --- a/admin/tool/mfa/classes/local/factor/object_factor_base.php +++ b/admin/tool/mfa/classes/local/factor/object_factor_base.php @@ -16,6 +16,8 @@ namespace tool_mfa\local\factor; +use stdClass; + /** * MFA factor abstract class. * @@ -54,10 +56,11 @@ abstract class object_factor_base implements object_factor { /** * This loads the locked state from the DB - * * Base class implementation. + * + * @return void */ - public function load_locked_state() { + public function load_locked_state(): void { global $DB, $USER; // Check if lockcounter column exists (incase upgrade hasnt run yet). @@ -92,7 +95,7 @@ abstract class object_factor_base implements object_factor { * @return bool * @throws \dml_exception */ - public function is_enabled() { + public function is_enabled(): bool { $status = get_config('factor_'.$this->name, 'enabled'); if ($status == 1) { return true; @@ -108,7 +111,7 @@ abstract class object_factor_base implements object_factor { * @return int * @throws \dml_exception */ - public function get_weight() { + public function get_weight(): int { $weight = get_config('factor_'.$this->name, 'weight'); if ($weight) { return (int) $weight; @@ -124,7 +127,7 @@ abstract class object_factor_base implements object_factor { * @return string * @throws \coding_exception */ - public function get_display_name() { + public function get_display_name(): string { return get_string('pluginname', 'factor_'.$this->name); } @@ -136,7 +139,7 @@ abstract class object_factor_base implements object_factor { * @return string * @throws \coding_exception */ - public function get_info() { + public function get_info(): string { return get_string('info', 'factor_'.$this->name); } @@ -148,7 +151,7 @@ abstract class object_factor_base implements object_factor { * @param \MoodleQuickForm $mform * @return object $mform */ - public function setup_factor_form_definition($mform) { + public function setup_factor_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm { return $mform; } @@ -160,7 +163,7 @@ abstract class object_factor_base implements object_factor { * @param \MoodleQuickForm $mform * @return object $mform */ - public function setup_factor_form_definition_after_data($mform) { + public function setup_factor_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm { return $mform; } @@ -173,7 +176,7 @@ abstract class object_factor_base implements object_factor { * @param array $data * @return array */ - public function setup_factor_form_validation($data) { + public function setup_factor_form_validation(array $data): array { return []; } @@ -183,10 +186,10 @@ abstract class object_factor_base implements object_factor { * * Dummy implementation. Should be overridden in child class. * - * @param array $data - * @return stdClass the record if created, or null. + * @param stdClass $data + * @return stdClass|null the record if created, or null. */ - public function setup_user_factor($data) { + public function setup_user_factor(stdClass $data): stdClass|null { return null; } @@ -198,7 +201,7 @@ abstract class object_factor_base implements object_factor { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors(stdClass $user): array { return []; } @@ -209,7 +212,7 @@ abstract class object_factor_base implements object_factor { * @param stdClass $user object to check against. * @return array */ - public function get_active_user_factors($user) { + public function get_active_user_factors(stdClass $user): array { $return = []; $factors = $this->get_all_user_factors($user); foreach ($factors as $factor) { @@ -228,7 +231,7 @@ abstract class object_factor_base implements object_factor { * @param \MoodleQuickForm $mform * @return object $mform */ - public function login_form_definition($mform) { + public function login_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm { return $mform; } @@ -240,7 +243,7 @@ abstract class object_factor_base implements object_factor { * @param \MoodleQuickForm $mform * @return object $mform */ - public function login_form_definition_after_data($mform) { + public function login_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm { return $mform; } @@ -253,7 +256,7 @@ abstract class object_factor_base implements object_factor { * @param array $data * @return array */ - public function login_form_validation($data) { + public function login_form_validation(array $data): array { return []; } @@ -265,7 +268,7 @@ abstract class object_factor_base implements object_factor { * * @return bool */ - public function has_revoke() { + public function has_revoke(): bool { return false; } @@ -277,7 +280,7 @@ abstract class object_factor_base implements object_factor { * @return bool * @throws \dml_exception */ - public function revoke_user_factor($factorid = null) { + public function revoke_user_factor(?int $factorid = null): bool { global $DB, $USER; if (!empty($factorid)) { @@ -307,10 +310,10 @@ abstract class object_factor_base implements object_factor { * When validation code is correct - update lastverified field for given factor. * If factor id is not provided, update all factor entries for user. * @param int $factorid - * @return bool + * @return bool|\dml_exception * @throws \dml_exception */ - public function update_lastverified($factorid = null) { + public function update_lastverified(?int $factorid = null): bool|\dml_exception { global $DB, $USER; if (!empty($factorid)) { $params = ['id' => $factorid]; @@ -326,7 +329,7 @@ abstract class object_factor_base implements object_factor { * @param int $factorid * @return int|bool the lastverified timestamp, or false if not found. */ - public function get_lastverified($factorid) { + public function get_lastverified(int $factorid): int|bool { global $DB; $record = $DB->get_record('tool_mfa', ['id' => $factorid]); @@ -335,12 +338,11 @@ abstract class object_factor_base implements object_factor { /** * Returns true if factor needs to be setup by user and has setup_form. - * * Override in child class if necessary. * * @return bool */ - public function has_setup() { + public function has_setup(): bool { return false; } @@ -349,7 +351,7 @@ abstract class object_factor_base implements object_factor { * * @return bool */ - public function show_setup_buttons() { + public function show_setup_buttons(): bool { return $this->has_setup(); } @@ -360,7 +362,7 @@ abstract class object_factor_base implements object_factor { * * @return bool */ - public function has_input() { + public function has_input(): bool { return true; } @@ -372,7 +374,7 @@ abstract class object_factor_base implements object_factor { * * @return bool */ - public function is_lockable() { + public function is_lockable(): bool { return $this->has_input(); } @@ -384,7 +386,7 @@ abstract class object_factor_base implements object_factor { * * @return mixed */ - public function get_state() { + public function get_state(): string { global $SESSION; $property = 'factor_'.$this->name; @@ -405,7 +407,7 @@ abstract class object_factor_base implements object_factor { * @param mixed $state the state constant to set * @return bool */ - public function set_state($state) { + public function set_state(string $state): bool { global $SESSION; // Do not allow overwriting fail states. @@ -424,7 +426,7 @@ abstract class object_factor_base implements object_factor { * @param object $user * @return void */ - public function create_event_after_factor_setup($user) { + public function create_event_after_factor_setup(object $user): void { $event = \tool_mfa\event\user_setup_factor::user_setup_factor_event($user, $this->get_display_name()); $event->trigger(); } @@ -432,8 +434,10 @@ abstract class object_factor_base implements object_factor { /** * Function for factor actions in the pass state. * Override in child class if necessary. + * + * @return void */ - public function post_pass_state() { + public function post_pass_state(): void { // Update lastverified for factor. if ($this->get_state() == \tool_mfa\plugininfo\factor::STATE_PASS) { $this->update_lastverified(); @@ -447,16 +451,19 @@ abstract class object_factor_base implements object_factor { * Function to retrieve the label for a factorid. * * @param int $factorid + * @return string|\dml_exception */ - public function get_label($factorid) { + public function get_label(int $factorid): string|\dml_exception { global $DB; return $DB->get_field('tool_mfa', 'label', ['id' => $factorid]); } /** * Function to get urls that should not be redirected from. + * + * @return array */ - public function get_no_redirect_urls() { + public function get_no_redirect_urls(): array { return []; } @@ -466,9 +473,10 @@ abstract class object_factor_base implements object_factor { * This should be overridden in factors where state is non-deterministic. * E.g. IP changes based on whether a user is using a VPN. * - * @param \stdClass $user + * @param stdClass $user + * @return array */ - public function possible_states($user) { + public function possible_states(stdClass $user): array { return [$this->get_state()]; } @@ -476,8 +484,10 @@ abstract class object_factor_base implements object_factor { * Returns condition for passing factor. * Implementation for basic conditions. * Override for complex conditions such as auth type. + * + * @return string */ - public function get_summary_condition() { + public function get_summary_condition(): string { return get_string('summarycondition', 'factor_'.$this->name); } @@ -489,14 +499,16 @@ abstract class object_factor_base implements object_factor { * @param array $combination array of factors that make up the combination * @return bool */ - public function check_combination($combination) { + public function check_combination(array $combination): bool { return true; } /** * Gets the string for setup button on preferences page. + * + * @return string */ - public function get_setup_string() { + public function get_setup_string(): string { return get_string('setupfactor', 'tool_mfa'); } @@ -504,8 +516,9 @@ abstract class object_factor_base implements object_factor { * Deletes all instances of factor for a user. * * @param stdClass $user the user to delete for. + * @return void */ - public function delete_factor_for_user($user) { + public function delete_factor_for_user(stdClass $user): void { global $DB, $USER; $DB->delete_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]); @@ -519,7 +532,7 @@ abstract class object_factor_base implements object_factor { * * @return void */ - public function increment_lock_counter() { + public function increment_lock_counter(): void { global $DB, $USER; // First make sure the state is loaded. @@ -549,7 +562,7 @@ abstract class object_factor_base implements object_factor { * * @return int the number of attempts at this factor remaining. */ - public function get_remaining_attempts() { + public function get_remaining_attempts(): int { $lockthreshold = get_config('tool_mfa', 'lockout'); if ($this->lockcounter === -1) { // If upgrade.php hasnt been run yet, just return 10. @@ -564,7 +577,7 @@ abstract class object_factor_base implements object_factor { * * @return void */ - public function process_cancel_action() { + public function process_cancel_action(): void { $this->set_state(\tool_mfa\plugininfo\factor::STATE_NEUTRAL); } @@ -574,7 +587,7 @@ abstract class object_factor_base implements object_factor { * @param \MoodleQuickForm $mform Form to inject global elements into. * @return void */ - public function global_definition($mform) { + public function global_definition(\MoodleQuickForm $mform): void { return; } @@ -584,7 +597,7 @@ abstract class object_factor_base implements object_factor { * @param \MoodleQuickForm $mform Form to inject global elements into. * @return void */ - public function global_definition_after_data($mform) { + public function global_definition_after_data(\MoodleQuickForm $mform): void { return; } @@ -595,7 +608,7 @@ abstract class object_factor_base implements object_factor { * @param array $files Files form the form. * @return array of errors from validation. */ - public function global_validation($data, $files): array { + public function global_validation(array $data, array $files): array { return []; } @@ -603,8 +616,9 @@ abstract class object_factor_base implements object_factor { * Hook point for global auth form action hooks. * * @param object $data Data from the form. + * @return void */ - public function global_submit($data) { + public function global_submit(object $data): void { return; } } diff --git a/admin/tool/mfa/classes/local/form/global_form_manager.php b/admin/tool/mfa/classes/local/form/global_form_manager.php index 1ce5b2dd877..4da1d5292e2 100644 --- a/admin/tool/mfa/classes/local/form/global_form_manager.php +++ b/admin/tool/mfa/classes/local/form/global_form_manager.php @@ -43,7 +43,7 @@ class global_form_manager { * @param \MoodleQuickForm $mform Form to inject global elements into. * @return void */ - public function definition(&$mform) { + public function definition(\MoodleQuickForm &$mform): void { foreach ($this->activefactors as $factor) { $factor->global_definition($mform); } @@ -55,7 +55,7 @@ class global_form_manager { * @param \MoodleQuickForm $mform Form to inject global elements into. * @return void */ - public function definition_after_data(&$mform) { + public function definition_after_data(\MoodleQuickForm &$mform): void { foreach ($this->activefactors as $factor) { $factor->global_definition_after_data($mform); } @@ -68,7 +68,7 @@ class global_form_manager { * @param array $files Files form the form. * @return array of errors from validation. */ - public function validation($data, $files) { + public function validation(array $data, array $files): array { $errors = []; foreach ($this->activefactors as $factor) { $errors = array_merge($errors, $factor->global_validation($data, $files)); @@ -82,7 +82,7 @@ class global_form_manager { * @param \stdClass $data Data from the form. * @return void */ - public function submit(\stdClass $data) { + public function submit(\stdClass $data): void { foreach ($this->activefactors as $factor) { $factor->global_submit($data); } diff --git a/admin/tool/mfa/classes/local/form/login_form.php b/admin/tool/mfa/classes/local/form/login_form.php index 304df64a66f..62406cd5fad 100644 --- a/admin/tool/mfa/classes/local/form/login_form.php +++ b/admin/tool/mfa/classes/local/form/login_form.php @@ -65,7 +65,7 @@ class login_form extends \moodleform { * {@inheritDoc} * @see moodleform::definition() */ - public function definition() { + public function definition(): void { $mform = $this->_form; $factor = $this->_customdata['factor']; $mform = $factor->login_form_definition($mform); @@ -74,8 +74,10 @@ class login_form extends \moodleform { /** * Invokes factor login_form_definition_after_data() method after form data has been set. + * + * @return void */ - public function definition_after_data() { + public function definition_after_data(): void { $mform = $this->_form; $factor = $this->_customdata['factor']; diff --git a/admin/tool/mfa/classes/local/form/reset_factor.php b/admin/tool/mfa/classes/local/form/reset_factor.php index 78061096236..3db0e114e91 100644 --- a/admin/tool/mfa/classes/local/form/reset_factor.php +++ b/admin/tool/mfa/classes/local/form/reset_factor.php @@ -32,7 +32,7 @@ class reset_factor extends \moodleform { /** * Form definition. */ - public function definition() { + public function definition(): void { $mform = $this->_form; $factors = $this->_customdata['factors']; $bulkaction = $this->_customdata['bulk']; diff --git a/admin/tool/mfa/classes/local/form/revoke_factor_form.php b/admin/tool/mfa/classes/local/form/revoke_factor_form.php index c5964826028..fc37fb59a94 100644 --- a/admin/tool/mfa/classes/local/form/revoke_factor_form.php +++ b/admin/tool/mfa/classes/local/form/revoke_factor_form.php @@ -34,7 +34,7 @@ class revoke_factor_form extends \moodleform { * {@inheritDoc} * @see moodleform::definition() */ - public function definition() { + public function definition(): void { global $OUTPUT; $mform = $this->_form; $factorname = $this->_customdata['factorname']; diff --git a/admin/tool/mfa/classes/local/form/setup_factor_form.php b/admin/tool/mfa/classes/local/form/setup_factor_form.php index 45f0f9a3a69..b9bdb9d068a 100644 --- a/admin/tool/mfa/classes/local/form/setup_factor_form.php +++ b/admin/tool/mfa/classes/local/form/setup_factor_form.php @@ -34,7 +34,7 @@ class setup_factor_form extends \moodleform { * {@inheritDoc} * @see moodleform::definition() */ - public function definition() { + public function definition(): void { $mform = $this->_form; $factorname = $this->_customdata['factorname']; @@ -64,7 +64,7 @@ class setup_factor_form extends \moodleform { /** * Invokes factor setup_factor_form_definition_after_data() method after form data has been set. */ - public function definition_after_data() { + public function definition_after_data(): void { $mform = $this->_form; $factorname = $this->_customdata['factorname']; @@ -75,13 +75,12 @@ class setup_factor_form extends \moodleform { } /** - * In newer versions of Totara with consistent cleaning enabled we need to ensure to mark static elements - * as "xss safe". Or in Totara's ideal world to use 'html' if form-like display is not required. + * Form elements clean up * * @param \HTML_QuickForm $mform * @return void */ - private function xss_whitelist_static_form_elements($mform) { + private function xss_whitelist_static_form_elements($mform): void { if (!method_exists('MoodleQuickForm_static', 'set_allow_xss')) { return; } diff --git a/admin/tool/mfa/classes/local/form/verification_field.php b/admin/tool/mfa/classes/local/form/verification_field.php index d316156c092..f92ee21972c 100644 --- a/admin/tool/mfa/classes/local/form/verification_field.php +++ b/admin/tool/mfa/classes/local/form/verification_field.php @@ -82,7 +82,7 @@ class verification_field extends \MoodleQuickForm_text { * * @return string */ - public function toHtml() { + public function toHtml(): string { // Empty the value after all attributes decided. $this->_attributes['value'] = ''; $result = parent::toHtml(); diff --git a/admin/tool/mfa/classes/local/secret_manager.php b/admin/tool/mfa/classes/local/secret_manager.php index 0eb67c19720..53b78fd3dbc 100644 --- a/admin/tool/mfa/classes/local/secret_manager.php +++ b/admin/tool/mfa/classes/local/secret_manager.php @@ -88,7 +88,7 @@ class secret_manager { * @param string $sessionid an optional sessionID to tie this record to * @return void */ - private function add_secret_to_db(string $secret, int $expires, string $sessionid = null) { + private function add_secret_to_db(string $secret, int $expires, string $sessionid = null): void { global $DB, $USER; $expirytime = time() + $expires; @@ -178,7 +178,7 @@ class secret_manager { * @param int $userid the userid to revoke the secret for. * @return void */ - public function revoke_secret(string $secret, $userid = null) { + public function revoke_secret(string $secret, $userid = null): void { global $DB, $USER; $userid = $userid ?? $USER->id; @@ -229,7 +229,7 @@ class secret_manager { * @param int $userid the userid to cleanup temp secrets for. * @return void */ - public function cleanup_temp_secrets($userid = null) { + public function cleanup_temp_secrets($userid = null): void { global $DB, $USER; // Session records are autocleaned up. // Only DB cleanup required. diff --git a/admin/tool/mfa/classes/manager.php b/admin/tool/mfa/classes/manager.php index 659017b1697..e32ed935ae9 100644 --- a/admin/tool/mfa/classes/manager.php +++ b/admin/tool/mfa/classes/manager.php @@ -16,6 +16,8 @@ namespace tool_mfa; +use dml_exception; + /** * MFA management class. * @@ -40,8 +42,10 @@ class manager { /** * Displays a debug table with current factor information. + * + * @return void */ - public static function display_debug_notification() { + public static function display_debug_notification(): void { global $OUTPUT, $PAGE; if (!get_config('tool_mfa', 'debugmode')) { @@ -142,7 +146,7 @@ class manager { * * @return int */ - public static function get_total_weight() { + public static function get_total_weight(): int { $totalweight = 0; $factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types(); @@ -162,7 +166,7 @@ class manager { * @return bool * @throws \dml_exception */ - public static function is_factorid_valid($factorid, $user) { + public static function is_factorid_valid(int $factorid, object $user): bool { global $DB; return $DB->record_exists('tool_mfa', ['userid' => $user->id, 'id' => $factorid]); } @@ -172,7 +176,7 @@ class manager { * * @return void */ - public static function cannot_login() { + public static function cannot_login(): void { global $ME, $PAGE, $SESSION, $USER; // Determine page URL without triggering warnings from $PAGE. @@ -205,7 +209,7 @@ class manager { * * @return void */ - public static function mfa_logout() { + public static function mfa_logout(): void { $authsequence = get_enabled_auth_plugins(); foreach ($authsequence as $authname) { $authplugin = get_auth_plugin($authname); @@ -217,9 +221,9 @@ class manager { /** * Function to get the overall status of a user's authentication. * - * @return mixed a STATE variable from plugininfo + * @return string a STATE variable from plugininfo */ - public static function get_status() { + public static function get_status(): string { global $SESSION; // Check for any instant fail states. @@ -264,7 +268,7 @@ class manager { * @param bool $shouldreload whether the function should reload (used for auth.php). * @return void */ - public static function resolve_mfa_status($shouldreload = false) { + public static function resolve_mfa_status(bool $shouldreload = false): void { global $SESSION; $state = self::get_status(); @@ -298,7 +302,7 @@ class manager { * * @return bool true if user has passed enough factors. */ - public static function passed_enough_factors() { + public static function passed_enough_factors(): bool { // Check for any instant fail states. $factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types(); @@ -321,7 +325,7 @@ class manager { * * @return void */ - public static function set_pass_state() { + public static function set_pass_state(): void { global $DB, $SESSION, $USER; if (!isset($SESSION->tool_mfa_authenticated)) { $SESSION->tool_mfa_authenticated = true; @@ -390,7 +394,7 @@ class manager { * * @return void */ - private static function update_pass_time() { + private static function update_pass_time(): void { global $DB, $USER; $exists = $DB->record_exists('tool_mfa_auth', ['userid' => $USER->id]); @@ -405,11 +409,11 @@ class manager { /** * Checks whether the user should be redirected from the provided url. * - * @param \moodle_url $url - * @param bool $preventredirect + * @param string|\moodle_url $url + * @param bool|null $preventredirect * @return int */ - public static function should_require_mfa($url, $preventredirect) { + public static function should_require_mfa(string|\moodle_url $url, bool|null $preventredirect): int { global $CFG, $USER, $SESSION; // If no cookies then no session so cannot do MFA. @@ -486,13 +490,10 @@ class manager { // Site policy. if (isset($USER->policyagreed) && !$USER->policyagreed) { - // Privacy classes may not exist in older Moodles/Totara. - if (class_exists('\core_privacy\local\sitepolicy\manager')) { - $manager = new \core_privacy\local\sitepolicy\manager(); - $policyurl = $manager->get_redirect_url(false); - if (!empty($policyurl) && $url->compare($policyurl, URL_MATCH_BASE)) { - return self::NO_REDIRECT; - } + $manager = new \core_privacy\local\sitepolicy\manager(); + $policyurl = $manager->get_redirect_url(false); + if (!empty($policyurl) && $url->compare($policyurl, URL_MATCH_BASE)) { + return self::NO_REDIRECT; } } @@ -547,8 +548,9 @@ class manager { /** * Clears the redirect counter for infinite redirect loops. Called from auth.php when a valid load is resolved. * + * @return void */ - public static function clear_redirect_counter() { + public static function clear_redirect_counter(): void { global $SESSION; unset($SESSION->mfa_redir_referer); @@ -560,7 +562,7 @@ class manager { * * @return array */ - public static function get_no_redirect_urls() { + public static function get_no_redirect_urls(): array { $factors = \tool_mfa\plugininfo\factor::get_factors(); $urls = [ new \moodle_url('/login/logout.php'), @@ -582,8 +584,10 @@ class manager { /** * Sleeps for an increasing period of time. + * + * @return void */ - public static function sleep_timer() { + public static function sleep_timer(): void { global $USER; $duration = get_user_preferences('mfa_sleep_duration', null, $USER); @@ -596,7 +600,7 @@ class manager { $duration = 0.05; } set_user_preference('mfa_sleep_duration', $duration, $USER); - sleep($duration); + sleep((int)$duration); } /** @@ -611,7 +615,7 @@ class manager { * @return void */ public static function require_auth($courseorid = null, $autologinguest = null, $cm = null, - $setwantsurltome = null, $preventredirect = null) { + $setwantsurltome = null, $preventredirect = null): void { global $PAGE, $SESSION, $FULLME; // Guest user should never interact with MFA, @@ -678,7 +682,7 @@ class manager { * @return bool true or exception * @throws dml_exception */ - public static function set_factor_config($data, $factor) { + public static function set_factor_config(array $data, string $factor): bool|dml_exception { $factorconf = get_config($factor); foreach ($data as $key => $newvalue) { if (empty($factorconf->$key)) { @@ -701,7 +705,7 @@ class manager { * @return bool * @throws \dml_exception */ - public static function is_ready() { + public static function is_ready(): bool { global $CFG, $USER; if (!empty($CFG->upgraderunning)) { @@ -737,7 +741,7 @@ class manager { * @return void * @throws dml_exception */ - public static function do_factor_action($factorname, $action) { + public static function do_factor_action(string $factorname, string $action): void { $order = explode(',', get_config('tool_mfa', 'factor_order')); $key = array_search($factorname, $order); @@ -783,7 +787,7 @@ class manager { * * @return bool */ - public static function possible_factor_setup() { + public static function possible_factor_setup(): bool { global $USER; // Get all active factors. @@ -815,8 +819,10 @@ class manager { /** * Gets current user weight, up until first unknown factor. + * + * @return int */ - public static function get_cumulative_weight() { + public static function get_cumulative_weight(): int { $factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types(); $totalweight = 0; foreach ($factors as $factor) { @@ -839,7 +845,7 @@ class manager { * @param string $factorname the name of the factor. * @return bool true if factor is pending. */ - public static function check_factor_pending($factorname) { + public static function check_factor_pending(string $factorname): bool { $factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types(); // Setup vars. $pending = []; diff --git a/admin/tool/mfa/classes/plugininfo/factor.php b/admin/tool/mfa/classes/plugininfo/factor.php index 29291f11a88..5d9216fbdda 100644 --- a/admin/tool/mfa/classes/plugininfo/factor.php +++ b/admin/tool/mfa/classes/plugininfo/factor.php @@ -16,6 +16,8 @@ namespace tool_mfa\plugininfo; +use stdClass; + /** * Subplugin info class. * @@ -46,7 +48,7 @@ class factor extends \core\plugininfo\base { * * @return array of factor objects. */ - public static function get_factors() { + public static function get_factors(): array { $return = []; $factors = \core_plugin_manager::instance()->get_plugins_of_type('factor'); @@ -66,7 +68,7 @@ class factor extends \core\plugininfo\base { * @return array of factor objects * @throws \dml_exception */ - public static function sort_factors_by_order($unsorted) { + public static function sort_factors_by_order(array $unsorted): array { $sorted = []; $orderarray = explode(',', get_config('tool_mfa', 'factor_order')); @@ -90,7 +92,7 @@ class factor extends \core\plugininfo\base { * * @return mixed factor object or false if factor not found. */ - public static function get_factor($name) { + public static function get_factor(string $name): object|bool { $factors = \core_plugin_manager::instance()->get_plugins_of_type('factor'); foreach ($factors as $factor) { @@ -110,7 +112,7 @@ class factor extends \core\plugininfo\base { * * @return array of factor objects */ - public static function get_enabled_factors() { + public static function get_enabled_factors(): array { $return = []; $factors = self::get_factors(); @@ -128,7 +130,7 @@ class factor extends \core\plugininfo\base { * * @return array of factor objects. */ - public static function get_active_user_factor_types() { + public static function get_active_user_factor_types(): array { global $USER; $return = []; $factors = self::get_enabled_factors(); @@ -149,7 +151,7 @@ class factor extends \core\plugininfo\base { * @param stdClass $user the user to get types for. * @return array of factor objects. */ - public static function get_active_other_user_factor_types($user) { + public static function get_active_other_user_factor_types(stdClass $user): array { $return = []; $factors = self::get_enabled_factors(); @@ -168,7 +170,7 @@ class factor extends \core\plugininfo\base { * * @return mixed factor object the next factor to be authenticated or false. */ - public static function get_next_user_factor() { + public static function get_next_user_factor(): object { $factors = self::get_active_user_factor_types(); foreach ($factors as $factor) { @@ -189,7 +191,7 @@ class factor extends \core\plugininfo\base { * * @return array */ - public static function get_factor_actions() { + public static function get_factor_actions(): array { $actions = []; $actions[] = 'setup'; $actions[] = 'revoke'; @@ -212,7 +214,7 @@ class factor extends \core\plugininfo\base { * * @return null|bool */ - public function is_enabled() { + public function is_enabled(): null|bool { if (!$this->rootdir) { // Plugin missing. return false; @@ -232,7 +234,7 @@ class factor extends \core\plugininfo\base { * * @return string */ - public function get_settings_section_name() { + public function get_settings_section_name(): string { return $this->type . '_' . $this->name; } @@ -246,7 +248,7 @@ class factor extends \core\plugininfo\base { * @param string $parentnodename * @param bool $hassiteconfig whether the current user has moodle/site:config capability */ - public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig): void { if (!$this->is_installed_and_upgraded()) { return; @@ -274,7 +276,7 @@ class factor extends \core\plugininfo\base { * * @return bool */ - public static function factor_exists($factorname) { + public static function factor_exists(string $factorname): bool { $factor = self::get_factor($factorname); return !$factor ? false : true; } @@ -286,7 +288,7 @@ class factor extends \core\plugininfo\base { * * @return stdClass|null Factor instance or nothing if not found. */ - public static function get_instance_from_id($factorid) { + public static function get_instance_from_id(int $factorid): stdClass|null { global $DB; return $DB->get_record('tool_mfa', ['id' => $factorid]); } diff --git a/admin/tool/mfa/classes/privacy/provider.php b/admin/tool/mfa/classes/privacy/provider.php index 990aa0922ce..60393b86fe6 100644 --- a/admin/tool/mfa/classes/privacy/provider.php +++ b/admin/tool/mfa/classes/privacy/provider.php @@ -103,8 +103,9 @@ class provider implements * Gets the list of users who have data with a context. Secrets context is a subset of this table. * * @param userlist $userlist the userlist containing users who have data in this context. + * @return void */ - public static function get_users_in_context(userlist $userlist) { + public static function get_users_in_context(userlist $userlist): void { $context = $userlist->get_context(); // If current context is system, all users are contained within, get all users. if ($context->contextlevel == CONTEXT_SYSTEM) { @@ -119,8 +120,9 @@ class provider implements * Exports all data stored in provided contexts for user. Secrets should not be exported as they are transient. * * @param approved_contextlist $contextlist the list of contexts to export for. + * @return void */ - public static function export_user_data(approved_contextlist $contextlist) { + public static function export_user_data(approved_contextlist $contextlist): void { global $DB; $userid = $contextlist->get_user()->id; foreach ($contextlist as $context) { @@ -166,8 +168,9 @@ class provider implements * Deletes data for all users in context. * * @param context $context The context to delete for. + * @return void */ - public static function delete_data_for_all_users_in_context(\context $context) { + public static function delete_data_for_all_users_in_context(\context $context): void { global $DB; // All data contained in system context. if ($context->contextlevel == CONTEXT_SYSTEM) { @@ -181,8 +184,9 @@ class provider implements * Deletes all data in all provided contexts for user. * * @param approved_contextlist $contextlist the list of contexts to delete for. + * @return void */ - public static function delete_data_for_user(approved_contextlist $contextlist) { + public static function delete_data_for_user(approved_contextlist $contextlist): void { global $DB; $userid = $contextlist->get_user()->id; foreach ($contextlist as $context) { @@ -199,8 +203,9 @@ class provider implements * Given a userlist, deletes all data in all provided contexts for the users * * @param approved_userlist $userlist the list of users to delete data for + * @return void */ - public static function delete_data_for_users(approved_userlist $userlist) { + public static function delete_data_for_users(approved_userlist $userlist): void { $users = $userlist->get_users(); foreach ($users as $user) { // Create contextlist. diff --git a/admin/tool/mfa/db/upgrade.php b/admin/tool/mfa/db/upgrade.php deleted file mode 100644 index ad6a2afdaa7..00000000000 --- a/admin/tool/mfa/db/upgrade.php +++ /dev/null @@ -1,136 +0,0 @@ -. - -/** - * MFA upgrade library. - * - * @package tool_mfa - * @copyright 2020 Peter Burnett - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -/** - * Function to upgrade tool_mfa. - * - * @param int $oldversion the version we are upgrading from - * @return bool result - */ -function xmldb_tool_mfa_upgrade($oldversion) { - global $DB; - - $dbman = $DB->get_manager(); - - if ($oldversion < 2020050700) { - // Define field lockcounter to be added to tool_mfa. - $table = new xmldb_table('tool_mfa'); - $field = new xmldb_field('lockcounter', XMLDB_TYPE_INTEGER, '5', null, XMLDB_NOTNULL, null, '0', 'revoked'); - - // Conditionally launch add field lockcounter. - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } - - // MFA savepoint reached. - upgrade_plugin_savepoint(true, 2020050700, 'tool', 'mfa'); - } - - if ($oldversion < 2020051900) { - // Define index userid (not unique) to be added to tool_mfa. - $table = new xmldb_table('tool_mfa'); - $index = new xmldb_index('userid', XMLDB_INDEX_NOTUNIQUE, ['userid']); - - // Conditionally launch add index userid. - if (!$dbman->index_exists($table, $index)) { - $dbman->add_index($table, $index); - } - - // Define index factor (not unique) to be added to tool_mfa. - $table = new xmldb_table('tool_mfa'); - $index = new xmldb_index('factor', XMLDB_INDEX_NOTUNIQUE, ['factor']); - - // Conditionally launch add index factor. - if (!$dbman->index_exists($table, $index)) { - $dbman->add_index($table, $index); - } - - // Define index lockcounter (not unique) to be added to tool_mfa. - $table = new xmldb_table('tool_mfa'); - $index = new xmldb_index('lockcounter', XMLDB_INDEX_NOTUNIQUE, ['userid', 'factor', 'lockcounter']); - - // Conditionally launch add index lockcounter. - if (!$dbman->index_exists($table, $index)) { - $dbman->add_index($table, $index); - } - - // Mfa savepoint reached. - upgrade_plugin_savepoint(true, 2020051900, 'tool', 'mfa'); - } - - if ($oldversion < 2020090300) { - // Define table tool_mfa_secrets to be created. - $table = new xmldb_table('tool_mfa_secrets'); - - // Adding fields to table tool_mfa_secrets. - $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); - $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); - $table->add_field('factor', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null); - $table->add_field('secret', XMLDB_TYPE_CHAR, '1333', null, XMLDB_NOTNULL, null, null); - $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '15', null, XMLDB_NOTNULL, null, null); - $table->add_field('expiry', XMLDB_TYPE_INTEGER, '15', null, XMLDB_NOTNULL, null, null); - $table->add_field('revoked', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0'); - $table->add_field('sessionid', XMLDB_TYPE_CHAR, '100', null, null, null, null); - - // Adding keys to table tool_mfa_secrets. - $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); - $table->add_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']); - - // Adding indexes to table tool_mfa_secrets. - $table->add_index('factor', XMLDB_INDEX_NOTUNIQUE, ['factor']); - $table->add_index('expiry', XMLDB_INDEX_NOTUNIQUE, ['expiry']); - - // Conditionally launch create table for tool_mfa_secrets. - if (!$dbman->table_exists($table)) { - $dbman->create_table($table); - } - - // Mfa savepoint reached. - upgrade_plugin_savepoint(true, 2020090300, 'tool', 'mfa'); - } - - if ($oldversion < 2021021900) { - // Define table tool_mfa_auth to be created. - $table = new xmldb_table('tool_mfa_auth'); - - // Adding fields to table tool_mfa_auth. - $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); - $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); - $table->add_field('lastverified', XMLDB_TYPE_INTEGER, '15', null, XMLDB_NOTNULL, null, '0'); - - // Adding keys to table tool_mfa_auth. - $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); - $table->add_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']); - - // Conditionally launch create table for tool_mfa_auth. - if (!$dbman->table_exists($table)) { - $dbman->create_table($table); - } - - // Mfa savepoint reached. - upgrade_plugin_savepoint(true, 2021021900, 'tool', 'mfa'); - } - - return true; -} diff --git a/admin/tool/mfa/example1 b/admin/tool/mfa/example1 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/admin/tool/mfa/example2 b/admin/tool/mfa/example2 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/admin/tool/mfa/factor/admin/classes/factor.php b/admin/tool/mfa/factor/admin/classes/factor.php index 7c470f5e22f..76368db849d 100644 --- a/admin/tool/mfa/factor/admin/classes/factor.php +++ b/admin/tool/mfa/factor/admin/classes/factor.php @@ -16,6 +16,7 @@ namespace factor_admin; +use stdClass; use tool_mfa\local\factor\object_factor_base; /** @@ -35,7 +36,7 @@ class factor extends object_factor_base { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors(stdClass $user): array { global $DB; $records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]); @@ -62,7 +63,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_input() { + public function has_input(): bool { return false; } @@ -72,7 +73,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_state() { + public function get_state(): string { if (is_siteadmin()) { return \tool_mfa\plugininfo\factor::STATE_NEUTRAL; } @@ -83,10 +84,10 @@ class factor extends object_factor_base { * Admin Factor implementation. * The state can never be set. Always return true. * - * @param mixed $state the state constant to set + * @param string $state the state constant to set * @return bool */ - public function set_state($state) { + public function set_state($state): bool { return true; } } diff --git a/admin/tool/mfa/factor/admin/classes/privacy/provider.php b/admin/tool/mfa/factor/admin/classes/privacy/provider.php index 7561d43b434..1eab86049a6 100644 --- a/admin/tool/mfa/factor/admin/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/admin/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_admin\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ use core_privacy\local\legacy_polyfill; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/admin/version.php b/admin/tool/mfa/factor/admin/version.php index 393f9b321c3..9530e7155fb 100644 --- a/admin/tool/mfa/factor/admin/version.php +++ b/admin/tool/mfa/factor/admin/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019102400; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_admin'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; -$plugin->dependencies = ['tool_mfa' => 2019102400]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/factor/auth/classes/factor.php b/admin/tool/mfa/factor/auth/classes/factor.php index dd5c87e87a7..0c2745d1c7d 100644 --- a/admin/tool/mfa/factor/auth/classes/factor.php +++ b/admin/tool/mfa/factor/auth/classes/factor.php @@ -16,6 +16,7 @@ namespace factor_auth; +use stdClass; use tool_mfa\local\factor\object_factor_base; /** @@ -35,7 +36,7 @@ class factor extends object_factor_base { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors(stdClass $user): array { global $DB; $records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]); @@ -62,7 +63,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_input() { + public function has_input(): bool { return false; } @@ -72,7 +73,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_state() { + public function get_state(): string { global $USER; $safetypes = get_config('factor_auth', 'goodauth'); @@ -93,10 +94,10 @@ class factor extends object_factor_base { * Auth Factor implementation. * The state can never be set. Always return true. * - * @param mixed $state the state constant to set + * @param string $state the state constant to set * @return bool */ - public function set_state($state) { + public function set_state(string $state): bool { return true; } @@ -106,7 +107,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_summary_condition() { + public function get_summary_condition(): string { $safetypes = get_config('factor_auth', 'goodauth'); return get_string('summarycondition', 'factor_'.$this->name, $safetypes); diff --git a/admin/tool/mfa/factor/auth/classes/privacy/provider.php b/admin/tool/mfa/factor/auth/classes/privacy/provider.php index a0f8c208ac4..bb9498c9f7e 100644 --- a/admin/tool/mfa/factor/auth/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/auth/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_auth\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ use core_privacy\local\legacy_polyfill; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/auth/version.php b/admin/tool/mfa/factor/auth/version.php index e3a5318509d..4326a860688 100644 --- a/admin/tool/mfa/factor/auth/version.php +++ b/admin/tool/mfa/factor/auth/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2021020500; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_auth'; $plugin->release = 2021020500; $plugin->maturity = MATURITY_STABLE; -$plugin->dependencies = ['tool_mfa' => 2019102400]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/factor/capability/classes/factor.php b/admin/tool/mfa/factor/capability/classes/factor.php index 8cb94d83866..39428589748 100644 --- a/admin/tool/mfa/factor/capability/classes/factor.php +++ b/admin/tool/mfa/factor/capability/classes/factor.php @@ -16,6 +16,7 @@ namespace factor_capability; +use stdClass; use tool_mfa\local\factor\object_factor_base; /** @@ -35,7 +36,7 @@ class factor extends object_factor_base { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors(stdClass $user): array { global $DB; $records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]); @@ -62,7 +63,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_input() { + public function has_input(): bool { return false; } @@ -72,7 +73,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_state() { + public function get_state(): string { global $USER; $adminpass = (bool) get_config('factor_capability', 'adminpasses'); @@ -88,10 +89,10 @@ class factor extends object_factor_base { * User Capability implementation. * Cannot set state, return true. * - * @param mixed $state the state constant to set + * @param string $state the state constant to set * @return bool */ - public function set_state($state) { + public function set_state(string $state): bool { return true; } @@ -99,9 +100,10 @@ class factor extends object_factor_base { * User capability implementation. * Possible states are either neutral or pass. * - * @param \stdClass $user + * @param stdClass $user + * @return array */ - public function possible_states($user) { + public function possible_states(stdClass $user): array { return [ \tool_mfa\plugininfo\factor::STATE_PASS, \tool_mfa\plugininfo\factor::STATE_NEUTRAL, diff --git a/admin/tool/mfa/factor/capability/classes/privacy/provider.php b/admin/tool/mfa/factor/capability/classes/privacy/provider.php index 99ce4bb94d0..1d71426ee6b 100644 --- a/admin/tool/mfa/factor/capability/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/capability/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_capability\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ use core_privacy\local\legacy_polyfill; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/capability/version.php b/admin/tool/mfa/factor/capability/version.php index ee17d8f2251..7ee9ced1a61 100644 --- a/admin/tool/mfa/factor/capability/version.php +++ b/admin/tool/mfa/factor/capability/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020071400; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_capability'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; -$plugin->dependencies = ['tool_mfa' => 2019102400]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/factor/cohort/classes/factor.php b/admin/tool/mfa/factor/cohort/classes/factor.php index 61b5ad9bed1..7a28bb61fbb 100644 --- a/admin/tool/mfa/factor/cohort/classes/factor.php +++ b/admin/tool/mfa/factor/cohort/classes/factor.php @@ -19,6 +19,7 @@ namespace factor_cohort; defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../../../../../../cohort/lib.php'); +use stdClass; use tool_mfa\local\factor\object_factor_base; /** @@ -38,7 +39,7 @@ class factor extends object_factor_base { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors(stdClass $user): array { global $DB; $records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]); if (!empty($records)) { @@ -64,7 +65,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_input() { + public function has_input(): bool { return false; } @@ -74,7 +75,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_state() { + public function get_state(): string { global $USER; $cohortstring = get_config('factor_cohort', 'cohorts'); // Nothing selected, everyone passes. @@ -97,10 +98,10 @@ class factor extends object_factor_base { * cohort implementation. * Cannot set state, return true. * - * @param mixed $state the state constant to set + * @param string $state the state constant to set * @return bool */ - public function set_state($state) { + public function set_state(string $state): bool { return true; } @@ -108,9 +109,9 @@ class factor extends object_factor_base { * cohort implementation. * User can not influence. Result is whatever current state is. * - * @param \stdClass $user + * @param stdClass $user */ - public function possible_states($user) { + public function possible_states(stdClass $user): array { return [$this->get_state()]; } @@ -120,20 +121,33 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_summary_condition() { - global $DB; + public function get_summary_condition(): string { $selectedcohorts = get_config('factor_cohort', 'cohorts'); if (empty($selectedcohorts)) { return get_string('summarycondition', 'factor_cohort', get_string('none')); - } else { - $selectedcohorts = explode(',', $selectedcohorts); } - $names = []; - foreach ($selectedcohorts as $cohort) { - $record = $DB->get_record('cohort', ['id' => $cohort]); - $names[] = $record->name; + + $selectedcohorts = $this->get_cohorts(explode(',', $selectedcohorts)); + if (empty($selectedcohorts)) { + return get_string('summarycondition', 'factor_cohort', get_string('none')); } - $string = implode(', ', $names); - return get_string('summarycondition', 'factor_cohort', $string); + + return get_string('summarycondition', 'factor_cohort', implode(', ', $selectedcohorts)); + } + + /** + * Get array of the selected cohorts. + * + * @param array $selectedcohorts + * @return array + */ + public function get_cohorts(array $selectedcohorts) : array { + global $DB; + + [$insql, $inparams] = $DB->get_in_or_equal($selectedcohorts); + $sql = "SELECT id, name FROM {cohort} WHERE id $insql"; + $cohorts = $DB->get_records_sql_menu($sql, $inparams); + + return $cohorts; } } diff --git a/admin/tool/mfa/factor/cohort/classes/privacy/provider.php b/admin/tool/mfa/factor/cohort/classes/privacy/provider.php index 3e1c2de7759..813c9b328a1 100644 --- a/admin/tool/mfa/factor/cohort/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/cohort/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_cohort\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ use core_privacy\local\legacy_polyfill; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/cohort/tests/factor_test.php b/admin/tool/mfa/factor/cohort/tests/factor_test.php new file mode 100644 index 00000000000..df4c851a5e2 --- /dev/null +++ b/admin/tool/mfa/factor/cohort/tests/factor_test.php @@ -0,0 +1,55 @@ +. + +namespace factor_cohort; + +/** + * Tests for cohort factor. + * + * @covers \factor_cohort\factor + * @package factor_cohort + * @copyright 2023 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class factor_test extends \advanced_testcase { + + /** + * Tests getting the summary condition + * + * @covers ::get_summary_condition + * @covers ::get_cohorts + */ + public function test_get_summary_condition() { + $this->resetAfterTest(); + + set_config('enabled', 1, 'factor_cohort'); + $cohortfactor = \tool_mfa\plugininfo\factor::get_factor('cohort'); + + $cohort = $this->getDataGenerator()->create_cohort(); + $userassignover = $this->getDataGenerator()->create_user(); + cohort_add_member($cohort->id, $userassignover->id); + + // Add the created cohortid into factor_cohort plugin. + set_config('cohorts', $cohort->id, 'factor_cohort'); + + $selectedcohorts = get_config('factor_cohort', 'cohorts'); + $selectedcohorts = $cohortfactor->get_cohorts(explode(',', $selectedcohorts)); + $this->assertStringContainsString( + implode(', ', $selectedcohorts), + $cohortfactor->get_summary_condition() + ); + } +} diff --git a/admin/tool/mfa/factor/cohort/version.php b/admin/tool/mfa/factor/cohort/version.php index ec9b3879ff2..c524df7fbb5 100644 --- a/admin/tool/mfa/factor/cohort/version.php +++ b/admin/tool/mfa/factor/cohort/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2022101100; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_cohort'; $plugin->release = 'v0.2'; $plugin->maturity = MATURITY_STABLE; -$plugin->dependencies = ['tool_mfa' => 2022090600]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/factor/email/classes/event/unauth_email.php b/admin/tool/mfa/factor/email/classes/event/unauth_email.php index 71a147837d8..9b5e59a6b93 100644 --- a/admin/tool/mfa/factor/email/classes/event/unauth_email.php +++ b/admin/tool/mfa/factor/email/classes/event/unauth_email.php @@ -16,6 +16,8 @@ namespace factor_email\event; +use stdClass; + /** * Event for when a user receives an unauthorised email from MFA. * @@ -37,11 +39,11 @@ class unauth_email extends \core\event\base { * @param string $ip the ip address the unauthorised email came from. * @param string $useragent the browser fingerpring the unauthorised email came from. * - * @return user_passed_mfa the user_passed_mfa event + * @return \core\event\base the user_passed_mfa event * * @throws \coding_exception */ - public static function unauth_email_event($user, $ip, $useragent) { + public static function unauth_email_event(stdClass $user, string $ip, string $useragent): \core\event\base { $data = [ 'relateduserid' => null, @@ -61,7 +63,7 @@ class unauth_email extends \core\event\base { * * @return void */ - protected function init() { + protected function init(): void { $this->data['crud'] = 'r'; $this->data['edulevel'] = self::LEVEL_OTHER; } @@ -71,9 +73,12 @@ class unauth_email extends \core\event\base { * * @return string */ - public function get_description() { - return "The user with id '{$this->other['userid']}' made an unauthorised login attempt using email verification from - IP '{$this->other['ip']}' with browser agent '{$this->other['useragent']}'."; + public function get_description(): string { + $data = new stdClass(); + $data->userid = $this->other['userid']; + $data->ip = $this->other['ip']; + $data->useragent = $this->other['useragent']; + return get_string('unauthloginattempt', 'factor_email', $data); } /** @@ -82,7 +87,7 @@ class unauth_email extends \core\event\base { * @return string * @throws \coding_exception */ - public static function get_name() { + public static function get_name(): string { return get_string('event:unauthemail', 'factor_email'); } } diff --git a/admin/tool/mfa/factor/email/classes/factor.php b/admin/tool/mfa/factor/email/classes/factor.php index 7379bfabba5..11076975547 100644 --- a/admin/tool/mfa/factor/email/classes/factor.php +++ b/admin/tool/mfa/factor/email/classes/factor.php @@ -16,6 +16,7 @@ namespace factor_email; +use stdClass; use tool_mfa\local\factor\object_factor_base; /** @@ -35,7 +36,7 @@ class factor extends object_factor_base { * @param \MoodleQuickForm $mform * @return object $mform */ - public function login_form_definition($mform) { + public function login_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm { $mform->addElement('text', 'verificationcode', get_string('verificationcode', 'factor_email')); $mform->setType('verificationcode', PARAM_ALPHANUM); @@ -48,7 +49,7 @@ class factor extends object_factor_base { * @param \MoodleQuickForm $mform Form to inject global elements into. * @return object $mform */ - public function login_form_definition_after_data($mform) { + public function login_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm { $this->generate_and_email_code(); return $mform; } @@ -57,8 +58,9 @@ class factor extends object_factor_base { * Sends and e-mail to user with given verification code. * * @param int $instanceid + * @return void */ - public static function email_verification_code($instanceid) { + public static function email_verification_code(int $instanceid): void { global $PAGE, $USER; $noreplyuser = \core_user::get_noreply_user(); $subject = get_string('email:subject', 'factor_email'); @@ -73,7 +75,7 @@ class factor extends object_factor_base { * @param array $data * @return array */ - public function login_form_validation($data) { + public function login_form_validation(array $data): array { global $USER; $return = []; @@ -90,7 +92,7 @@ class factor extends object_factor_base { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors(stdClass $user): array { global $DB; $records = $DB->get_records('tool_mfa', [ @@ -121,7 +123,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_input() { + public function has_input(): bool { if (self::is_ready()) { return true; } @@ -133,7 +135,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_state() { + public function get_state(): string { if (!self::is_ready()) { return \tool_mfa\plugininfo\factor::STATE_NEUTRAL; } @@ -146,7 +148,7 @@ class factor extends object_factor_base { * * @return bool */ - private static function is_ready() { + private static function is_ready(): bool { global $DB, $USER; if (empty($USER->email)) { @@ -171,7 +173,7 @@ class factor extends object_factor_base { * * @return void */ - private function generate_and_email_code() { + private function generate_and_email_code(): void { global $DB, $USER; // Get instance that isnt parent email type (label check). @@ -224,7 +226,7 @@ class factor extends object_factor_base { * @param string $enteredcode * @return bool */ - private function check_verification_code($enteredcode) { + private function check_verification_code(string $enteredcode): bool { global $DB, $USER; $duration = get_config('factor_email', 'duration'); @@ -251,7 +253,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function post_pass_state() { + public function post_pass_state(): void { global $DB, $USER; // Delete all email records except base record. $selectsql = 'userid = ? @@ -269,7 +271,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_no_redirect_urls() { + public function get_no_redirect_urls(): array { $email = new \moodle_url('/admin/tool/mfa/factor/email/email.php'); return [$email]; } @@ -277,9 +279,9 @@ class factor extends object_factor_base { /** * Email factor implementation. * - * @param \stdClass $user + * @param stdClass $user */ - public function possible_states($user) { + public function possible_states(stdClass $user): array { // Email can return all states. return [ \tool_mfa\plugininfo\factor::STATE_FAIL, diff --git a/admin/tool/mfa/factor/email/classes/form/email.php b/admin/tool/mfa/factor/email/classes/form/email.php index f3abc9c06e6..afccc153750 100644 --- a/admin/tool/mfa/factor/email/classes/form/email.php +++ b/admin/tool/mfa/factor/email/classes/form/email.php @@ -33,7 +33,7 @@ class email extends \moodleform { /** * Form definition. */ - public function definition() { + public function definition(): void { $mform = $this->_form; $mform->addElement('html', get_string('email:accident', 'factor_email')); $this->add_action_buttons(true, get_string('continue')); @@ -49,7 +49,7 @@ class email extends \moodleform { * @return array of "element_name"=>"error_description" if there are errors, * or an empty array if everything is OK (true allowed for backwards compatibility too). */ - public function validation($data, $files) { + public function validation($data, $files): array { $errors = parent::validation($data, $files); return $errors; } diff --git a/admin/tool/mfa/factor/email/classes/privacy/provider.php b/admin/tool/mfa/factor/email/classes/privacy/provider.php index c7f2f22292b..7f0c36ab0f5 100644 --- a/admin/tool/mfa/factor/email/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/email/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_email\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ use core_privacy\local\legacy_polyfill; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/email/email.php b/admin/tool/mfa/factor/email/email.php index 5ee788b64e7..7c4f0e64835 100644 --- a/admin/tool/mfa/factor/email/email.php +++ b/admin/tool/mfa/factor/email/email.php @@ -88,7 +88,7 @@ if ($form->is_cancelled()) { // Suspend user account. if (get_config('factor_email', 'suspend')) { - $DB->set_field('user', 'suspended', 1, ['id' => $userid]); + $DB->set_field('user', 'suspended', 1, ['id' => $user->id]); } $message = get_string('email:revokesuccess', 'factor_email', fullname($user)); diff --git a/admin/tool/mfa/factor/email/lang/en/factor_email.php b/admin/tool/mfa/factor/email/lang/en/factor_email.php index 4d46f54d99d..78d03e668a7 100644 --- a/admin/tool/mfa/factor/email/lang/en/factor_email.php +++ b/admin/tool/mfa/factor/email/lang/en/factor_email.php @@ -52,6 +52,8 @@ $string['settings:suspend'] = 'Suspend unauthorised accounts'; $string['settings:suspend_help'] = 'Check this to suspend user accounts if an unauthorised email verification is received.'; $string['setupfactor'] = 'E-Mail Factor setup'; $string['summarycondition'] = 'has valid email setup'; +$string['unauthloginattempt'] = 'The user with id {$a->userid} made an unauthorised login attempt using email verification from +IP {$a->ip} with browser agent {$a->useragent}'; $string['unauthemail'] = 'Unauthorised Email'; $string['verificationcode'] = 'Enter verification code for confirmation'; $string['verificationcode_help'] = 'Verification code has been sent to your email address'; diff --git a/admin/tool/mfa/factor/email/renderer.php b/admin/tool/mfa/factor/email/renderer.php index 17fa2044036..55e60f73488 100644 --- a/admin/tool/mfa/factor/email/renderer.php +++ b/admin/tool/mfa/factor/email/renderer.php @@ -33,7 +33,7 @@ class factor_email_renderer extends plugin_renderer_base { * @param int $instanceid * @return string|boolean */ - public function generate_email($instanceid) { + public function generate_email(int $instanceid): string|bool { global $DB; $instance = $DB->get_record('tool_mfa', ['id' => $instanceid]); $authurl = new \moodle_url('/admin/tool/mfa/factor/email/email.php', diff --git a/admin/tool/mfa/factor/email/tests/factor_test.php b/admin/tool/mfa/factor/email/tests/factor_test.php new file mode 100644 index 00000000000..e0923555e70 --- /dev/null +++ b/admin/tool/mfa/factor/email/tests/factor_test.php @@ -0,0 +1,86 @@ +. + +namespace factor_email; + +/** + * Tests for email factor. + * + * @covers \factor_email\factor + * @package factor_email + * @copyright 2023 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class factor_test extends \advanced_testcase { + + /** + * Tests checking verification code + * + * @covers ::check_verification_code + * @covers ::post_pass_state + */ + public function test_check_verification_code() { + global $DB, $USER; + $this->resetAfterTest(true); + + $emailfactorclass = new \factor_email\factor('email'); + $rc = new \ReflectionClass($emailfactorclass::class); + $rcm = $rc->getMethod('check_verification_code'); + $rcm->setAccessible(true); + + // Assigned email to be used in getting the email factor. + $USER->email = 'user@mail.com'; + + set_config('enabled', 1, 'factor_email'); + + // Testing with current timecreated. + $newcode = random_int(100000, 999999); + $instanceid = $DB->insert_record('tool_mfa', [ + 'userid' => $USER->id, + 'factor' => 'email', + 'secret' => $newcode, + 'label' => 'unittest', + 'timecreated' => time(), + 'timemodified' => time(), + 'lastverified' => time(), + 'revoked' => 0, + ]); + + $data = $DB->get_record('tool_mfa', ['id' => $instanceid]); + $this->assertTrue($rcm->invoke($emailfactorclass, $data->secret)); + + // Update the data to test with really old timecreated. + $DB->update_record('tool_mfa', [ + 'id' => $instanceid, + 'timecreated' => time() - 1689657581, + 'timemodified' => time() - 1689657581, + 'lastverified' => time() - 1689657581, + 'revoked' => 0, + ]); + + $data = $DB->get_record('tool_mfa', ['id' => $instanceid]); + $this->assertFalse($rcm->invoke($emailfactorclass, $data->secret)); + + // Cleans up email records once MFA passed. + $rcm = $rc->getMethod('post_pass_state'); + $rcm->setAccessible(true); + $rcm->invoke($emailfactorclass); + + // Check if the email records have been deleted. + $data = $DB->count_records('tool_mfa', ['factor' => 'email']); + $this->assertEquals(0, $data); + } +} diff --git a/admin/tool/mfa/factor/email/version.php b/admin/tool/mfa/factor/email/version.php index 7f60b349879..0a3a2c1568c 100644 --- a/admin/tool/mfa/factor/email/version.php +++ b/admin/tool/mfa/factor/email/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019102400; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_email'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; -$plugin->dependencies = ['tool_mfa' => 2019102400]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/factor/grace/classes/factor.php b/admin/tool/mfa/factor/grace/classes/factor.php index 97724e2460b..761731b640d 100644 --- a/admin/tool/mfa/factor/grace/classes/factor.php +++ b/admin/tool/mfa/factor/grace/classes/factor.php @@ -16,6 +16,7 @@ namespace factor_grace; +use stdClass; use tool_mfa\local\factor\object_factor_base; /** @@ -35,7 +36,7 @@ class factor extends object_factor_base { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors(stdClass $user): array { global $DB; $records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]); @@ -63,7 +64,7 @@ class factor extends object_factor_base { * @param stdClass $user object to check against. * @return array the array of active factors. */ - public function get_active_user_factors($user) { + public function get_active_user_factors(stdClass $user): array { return $this->get_all_user_factors($user); } @@ -73,7 +74,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_input() { + public function has_input(): bool { return false; } @@ -84,7 +85,7 @@ class factor extends object_factor_base { * @param bool $redirectable should this state call be allowed to redirect the user? * @return string state constant */ - public function get_state($redirectable = true) { + public function get_state($redirectable = true): string { global $FULLME, $SESSION, $USER; $records = ($this->get_all_user_factors($USER)); $record = reset($records); @@ -154,10 +155,10 @@ class factor extends object_factor_base { * Grace Factor implementation. * State cannot be set. Return true. * - * @param mixed $state the state constant to set + * @param string $state the state constant to set * @return bool */ - public function set_state($state) { + public function set_state(string $state): bool { return true; } @@ -167,7 +168,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function post_pass_state() { + public function post_pass_state(): void { global $USER; parent::post_pass_state(); @@ -206,7 +207,7 @@ class factor extends object_factor_base { * @param array $combination array of factors that make up the combination * @return bool */ - public function check_combination($combination) { + public function check_combination(array $combination): bool { // If this combination has more than 1 factor that has setup or input, not valid. foreach ($combination as $factor) { if ($factor->has_setup() || $factor->has_input()) { @@ -220,9 +221,10 @@ class factor extends object_factor_base { * Grace Factor implementation. * Gracemode can change outcome just by waiting, or based on other factors. * - * @param \stdClass $user + * @param stdClass $user + * @return array */ - public function possible_states($user) { + public function possible_states(stdClass $user): array { return [ \tool_mfa\plugininfo\factor::STATE_PASS, \tool_mfa\plugininfo\factor::STATE_NEUTRAL, @@ -236,7 +238,7 @@ class factor extends object_factor_base { * * @return array */ - public function get_no_redirect_urls() { + public function get_no_redirect_urls(): array { $redirect = get_config('factor_grace', 'forcesetup'); // First check if user has any other input or setup factors active. diff --git a/admin/tool/mfa/factor/grace/classes/privacy/provider.php b/admin/tool/mfa/factor/grace/classes/privacy/provider.php index 86a20054e0d..11b966d014a 100644 --- a/admin/tool/mfa/factor/grace/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/grace/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_grace\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ use core_privacy\local\legacy_polyfill; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/grace/classes/task/revoke_expired_factors.php b/admin/tool/mfa/factor/grace/classes/task/revoke_expired_factors.php index c4f567afb03..83f49bbe6f4 100644 --- a/admin/tool/mfa/factor/grace/classes/task/revoke_expired_factors.php +++ b/admin/tool/mfa/factor/grace/classes/task/revoke_expired_factors.php @@ -35,7 +35,7 @@ class revoke_expired_factors extends \core\task\scheduled_task { * * @return string */ - public function get_name() { + public function get_name(): string { return get_string('revokeexpiredfactors', 'factor_grace'); } @@ -44,7 +44,7 @@ class revoke_expired_factors extends \core\task\scheduled_task { * * @return void */ - public function execute() { + public function execute(): void { mtrace('Starting to revoke expired Grace factors'); $this->revoke_factors(); } @@ -54,7 +54,7 @@ class revoke_expired_factors extends \core\task\scheduled_task { * * @return void */ - private function revoke_factors() { + private function revoke_factors(): void { global $DB; // If config is not set, pull out. diff --git a/admin/tool/mfa/factor/grace/tests/factor_test.php b/admin/tool/mfa/factor/grace/tests/factor_test.php index f5d8b86ae6c..140f29b7b26 100644 --- a/admin/tool/mfa/factor/grace/tests/factor_test.php +++ b/admin/tool/mfa/factor/grace/tests/factor_test.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace factor_grace\tests; +namespace factor_grace; /** * Tests for grace factor. @@ -26,6 +26,12 @@ namespace factor_grace\tests; */ class factor_test extends \advanced_testcase { + /** + * Test affecting factors + * + * @covers ::get_affecting_factors + * @return void + */ public function test_affecting_factors() { $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); diff --git a/admin/tool/mfa/factor/grace/version.php b/admin/tool/mfa/factor/grace/version.php index 6af7fcf345b..93a714fbd7a 100644 --- a/admin/tool/mfa/factor/grace/version.php +++ b/admin/tool/mfa/factor/grace/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2022020401; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_grace'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; -$plugin->dependencies = ['tool_mfa' => 2019102400]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/factor/iprange/classes/factor.php b/admin/tool/mfa/factor/iprange/classes/factor.php index 6905174e89e..519f529b39d 100644 --- a/admin/tool/mfa/factor/iprange/classes/factor.php +++ b/admin/tool/mfa/factor/iprange/classes/factor.php @@ -16,6 +16,7 @@ namespace factor_iprange; +use stdClass; use tool_mfa\local\factor\object_factor_base; /** @@ -35,7 +36,7 @@ class factor extends object_factor_base { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors(stdClass $user): array { global $DB; $records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]); @@ -62,7 +63,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_input() { + public function has_input(): bool { return false; } @@ -72,7 +73,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_state() { + public function get_state(): string { $safeips = get_config('factor_iprange', 'safeips'); // TODO: Check for failures here. @@ -90,10 +91,10 @@ class factor extends object_factor_base { * IP Range Factor implementation. * Cannot set state, return true. * - * @param mixed $state the state constant to set + * @param string $state the state constant to set * @return bool */ - public function set_state($state) { + public function set_state(string $state): bool { return true; } @@ -102,9 +103,9 @@ class factor extends object_factor_base { * User can influence state prior to login. * Possible states are either neutral or pass. * - * @param \stdClass $user + * @param stdClass $user */ - public function possible_states($user) { + public function possible_states(stdClass $user): array { return [ \tool_mfa\plugininfo\factor::STATE_PASS, \tool_mfa\plugininfo\factor::STATE_NEUTRAL, diff --git a/admin/tool/mfa/factor/iprange/classes/privacy/provider.php b/admin/tool/mfa/factor/iprange/classes/privacy/provider.php index 517e4d0fbac..9526fb6bd0c 100644 --- a/admin/tool/mfa/factor/iprange/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/iprange/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_iprange\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ use core_privacy\local\legacy_polyfill; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/iprange/version.php b/admin/tool/mfa/factor/iprange/version.php index 59369753e98..35d79c64773 100644 --- a/admin/tool/mfa/factor/iprange/version.php +++ b/admin/tool/mfa/factor/iprange/version.php @@ -25,9 +25,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019102400; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_iprange'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; -$plugin->dependencies = ['tool_mfa' => 2019102400]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/factor/nosetup/classes/factor.php b/admin/tool/mfa/factor/nosetup/classes/factor.php index 6a07410121a..8189c6b4a56 100644 --- a/admin/tool/mfa/factor/nosetup/classes/factor.php +++ b/admin/tool/mfa/factor/nosetup/classes/factor.php @@ -16,6 +16,7 @@ namespace factor_nosetup; +use stdClass; use tool_mfa\local\factor\object_factor_base; /** @@ -35,7 +36,7 @@ class factor extends object_factor_base { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors(stdClass $user): array { global $DB; $records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]); @@ -62,7 +63,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_input() { + public function has_input(): bool { return false; } @@ -72,7 +73,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_state() { + public function get_state(): string { // Check if user has any other input or setup factors active. $factors = \tool_mfa\plugininfo\factor::get_active_user_factor_types(); foreach ($factors as $factor) { @@ -91,7 +92,7 @@ class factor extends object_factor_base { * @param stdClass $user * @return void */ - public function possible_states($user) { + public function possible_states(stdClass $user): array { // We return Neutral here because to support optional rollouts // it needs to report neutral or the menu to setup will not display. return [\tool_mfa\plugininfo\factor::STATE_NEUTRAL]; @@ -101,10 +102,10 @@ class factor extends object_factor_base { * No Setup Factor implementation. * The state can never be set. Always return true. * - * @param mixed $state the state constant to set + * @param string $state the state constant to set * @return bool */ - public function set_state($state) { + public function set_state(string $state): bool { return true; } @@ -115,7 +116,7 @@ class factor extends object_factor_base { * @param array $combination array of factors that make up the combination * @return bool */ - public function check_combination($combination) { + public function check_combination(array $combination): bool { // If this combination has more than 1 factor that has setup or input, not valid. foreach ($combination as $factor) { if ($factor->has_setup() || $factor->has_input()) { diff --git a/admin/tool/mfa/factor/nosetup/classes/privacy/provider.php b/admin/tool/mfa/factor/nosetup/classes/privacy/provider.php index 934f50e457a..b1f4da0bed7 100644 --- a/admin/tool/mfa/factor/nosetup/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/nosetup/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_nosetup\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ use core_privacy\local\legacy_polyfill; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/nosetup/classes/task/delete_unusable_factors.php b/admin/tool/mfa/factor/nosetup/classes/task/delete_unusable_factors.php index efd64a4641b..ac678ddf26a 100644 --- a/admin/tool/mfa/factor/nosetup/classes/task/delete_unusable_factors.php +++ b/admin/tool/mfa/factor/nosetup/classes/task/delete_unusable_factors.php @@ -35,7 +35,7 @@ class delete_unusable_factors extends \core\task\scheduled_task { * * @return string */ - public function get_name() { + public function get_name(): string { return get_string('deleteunusablefactors', 'factor_nosetup'); } @@ -44,7 +44,7 @@ class delete_unusable_factors extends \core\task\scheduled_task { * * @return void */ - public function execute() { + public function execute(): void { mtrace('Starting to revoke unusable Nosetup factors'); $this->revoke_factors(); } @@ -54,7 +54,7 @@ class delete_unusable_factors extends \core\task\scheduled_task { * * @return void */ - private function revoke_factors() { + private function revoke_factors(): void { global $DB; $factorobject = \tool_mfa\plugininfo\factor::get_factor('nosetup'); diff --git a/admin/tool/mfa/factor/nosetup/version.php b/admin/tool/mfa/factor/nosetup/version.php index 5508c8d41fb..e48a8480320 100644 --- a/admin/tool/mfa/factor/nosetup/version.php +++ b/admin/tool/mfa/factor/nosetup/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020042302; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_nosetup'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; -$plugin->dependencies = ['tool_mfa' => 2019102400]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/factor/role/classes/factor.php b/admin/tool/mfa/factor/role/classes/factor.php index e84ad33fadc..63d8d72d7ba 100644 --- a/admin/tool/mfa/factor/role/classes/factor.php +++ b/admin/tool/mfa/factor/role/classes/factor.php @@ -16,6 +16,7 @@ namespace factor_role; +use stdClass; use tool_mfa\local\factor\object_factor_base; /** @@ -35,7 +36,7 @@ class factor extends object_factor_base { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors(stdClass $user): array { global $DB; $records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]); @@ -62,7 +63,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_input() { + public function has_input(): bool { return false; } @@ -72,7 +73,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_state() { + public function get_state(): string { global $USER; $rolestring = get_config('factor_role', 'roles'); @@ -114,10 +115,10 @@ class factor extends object_factor_base { * Role implementation. * Cannot set state, return true. * - * @param mixed $state the state constant to set + * @param string $state the state constant to set * @return bool */ - public function set_state($state) { + public function set_state(string $state): bool { return true; } @@ -125,9 +126,10 @@ class factor extends object_factor_base { * Role implementation. * User can not influence. Result is whatever current state is. * - * @param \stdClass $user + * @param stdClass $user + * @return array */ - public function possible_states($user) { + public function possible_states(stdClass $user): array { return [$this->get_state()]; } @@ -137,27 +139,46 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_summary_condition() { - global $DB; - + public function get_summary_condition(): string { $selectedroles = get_config('factor_role', 'roles'); if (empty($selectedroles)) { return get_string('summarycondition', 'factor_role', get_string('none')); - } else { - $selectedroles = explode(',', $selectedroles); } - $names = []; - foreach ($selectedroles as $role) { - if ($role === 'admin') { - $names[] = get_string('administrator'); - } else { - $record = $DB->get_record('role', ['id' => $role]); - $names[] = role_get_name($record); + $selectedroles = $this->get_roles(explode(',', $selectedroles)); + if (empty($selectedroles)) { + return get_string('summarycondition', 'factor_role', get_string('none')); + } + + return get_string('summarycondition', 'factor_role', implode(', ', $selectedroles)); + } + + /** + * Get array of the selected role name. + * + * @param array $selectedroles + * @return array + */ + public function get_roles(array $selectedroles) : array { + global $DB; + + $roles = []; + + // Checks for admin role and gets its role name. + if (($key = array_search('admin', $selectedroles)) !== false) { + $roles[] = get_string('administrator'); + unset($selectedroles[$key]); + } + + // Gets role name for all non admin roles. + if (count($selectedroles) > 0) { + [$insql, $inparams] = $DB->get_in_or_equal($selectedroles); + $otherroles = $DB->get_records_select('role', 'id ' . $insql, $inparams); + foreach ($otherroles as $role) { + $roles[] = role_get_name($role); } } - $string = implode(', ', $names); - return get_string('summarycondition', 'factor_role', $string); + return $roles; } } diff --git a/admin/tool/mfa/factor/role/classes/privacy/provider.php b/admin/tool/mfa/factor/role/classes/privacy/provider.php index 4ddac45b3bf..3c42264412c 100644 --- a/admin/tool/mfa/factor/role/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/role/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_role\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ use core_privacy\local\legacy_polyfill; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/role/tests/factor_test.php b/admin/tool/mfa/factor/role/tests/factor_test.php new file mode 100644 index 00000000000..66c243722e4 --- /dev/null +++ b/admin/tool/mfa/factor/role/tests/factor_test.php @@ -0,0 +1,104 @@ +. + +namespace factor_role; + +/** + * Tests for role factor. + * + * @covers \factor_role\factor + * @package factor_role + * @copyright 2023 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class factor_test extends \advanced_testcase { + + /** + * Tests getting the summary condition + * + * @covers ::get_summary_condition + * @covers ::get_roles + */ + public function test_get_summary_condition() { + global $DB; + + $this->resetAfterTest(); + + set_config('enabled', 1, 'factor_role'); + $rolefactor = \tool_mfa\plugininfo\factor::get_factor('role'); + + // Admin is disabled by default in this factor. + $selectedroles = get_config('factor_role', 'roles'); + $selectedroles = $rolefactor->get_roles(explode(',', $selectedroles)); + $this->assertStringContainsString( + implode(', ', $selectedroles), + $rolefactor->get_summary_condition() + ); + + // Disabled role factor for managers. + $managerrole = $DB->get_record('role', ['shortname' => 'manager']); + set_config('roles', $managerrole->id, 'factor_role'); + + $selectedroles = get_config('factor_role', 'roles'); + $selectedroles = $rolefactor->get_roles(explode(',', $selectedroles)); + $this->assertStringContainsString( + implode(', ', $selectedroles), + $rolefactor->get_summary_condition() + ); + + // Disabled role factor for teachers. + $teacherrole = $DB->get_record('role', ['shortname' => 'teacher']); + set_config('roles', $teacherrole->id, 'factor_role'); + + $selectedroles = get_config('factor_role', 'roles'); + $selectedroles = $rolefactor->get_roles(explode(',', $selectedroles)); + $this->assertStringContainsString( + implode(', ', $selectedroles), + $rolefactor->get_summary_condition() + ); + + // Disabled role factor for students. + $studentrole = $DB->get_record('role', ['shortname' => 'student']); + set_config('roles', $studentrole->id, 'factor_role'); + + $selectedroles = get_config('factor_role', 'roles'); + $selectedroles = $rolefactor->get_roles(explode(',', $selectedroles)); + $this->assertStringContainsString( + implode(', ', $selectedroles), + $rolefactor->get_summary_condition() + ); + + // Disabled role factor for admins, managers, teachers and students. + $managerrole = $DB->get_record('role', ['shortname' => 'manager']); + $teacherrole = $DB->get_record('role', ['shortname' => 'teacher']); + $studentrole = $DB->get_record('role', ['shortname' => 'student']); + set_config('roles', "admin,$managerrole->id,$teacherrole->id,$studentrole->id", 'factor_role'); + + $selectedroles = get_config('factor_role', 'roles'); + $selectedroles = $rolefactor->get_roles(explode(',', $selectedroles)); + $this->assertStringContainsString( + implode(', ', $selectedroles), + $rolefactor->get_summary_condition() + ); + + // Enable all roles. + unset_config('roles', 'factor_role'); + $this->assertEquals( + get_string('summarycondition', 'factor_role', get_string('none')), + $rolefactor->get_summary_condition() + ); + } +} diff --git a/admin/tool/mfa/factor/role/version.php b/admin/tool/mfa/factor/role/version.php index 52cc3f0f033..322270ed543 100644 --- a/admin/tool/mfa/factor/role/version.php +++ b/admin/tool/mfa/factor/role/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2020072100; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_role'; $plugin->release = 'v0.1'; $plugin->maturity = MATURITY_STABLE; -$plugin->dependencies = ['tool_mfa' => 2019102400]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/factor/token/classes/event/token_created.php b/admin/tool/mfa/factor/token/classes/event/token_created.php index bbba1b84f99..4e8d7975f53 100644 --- a/admin/tool/mfa/factor/token/classes/event/token_created.php +++ b/admin/tool/mfa/factor/token/classes/event/token_created.php @@ -15,6 +15,9 @@ // along with Moodle. If not, see . namespace factor_token\event; + +use stdClass; + /** * Event for a token being created for a user. * @@ -31,11 +34,11 @@ class token_created extends \core\event\base { * @param stdClass $user the User object of the User who had the token creeated. * @param array $state an array of the state of the token. * - * @return token_created the token_created_event event + * @return \core\event\base the token_created_event event * * @throws \coding_exception */ - public static function token_created_event($user, $state) { + public static function token_created_event(stdClass $user, array $state): \core\event\base { $data = [ 'relateduserid' => $user->id, 'context' => \context_user::instance($user->id), @@ -53,7 +56,7 @@ class token_created extends \core\event\base { * * @return void */ - protected function init() { + protected function init(): void { $this->data['crud'] = 'c'; $this->data['edulevel'] = self::LEVEL_OTHER; } @@ -63,7 +66,7 @@ class token_created extends \core\event\base { * * @return string */ - public function get_description() { + public function get_description(): string { $info = json_decode($this->other['state']); $string = '
'; foreach ($info as $name => $value) { @@ -74,7 +77,10 @@ class token_created extends \core\event\base { $string .= ucwords($name) . ': ' . $value . '
'; } - return "The user with id '{$this->other['userid']}' had an MFA token stored on their device.
Information:" . $string; + $data = new stdClass(); + $data->string = $string; + $data->userid = $this->other['userid']; + return get_string('tokenstoredindevice', 'factor_token', $data); } /** @@ -83,7 +89,7 @@ class token_created extends \core\event\base { * @return string * @throws \coding_exception */ - public static function get_name() { + public static function get_name(): string { return get_string('event:token_created', 'factor_token'); } } diff --git a/admin/tool/mfa/factor/token/classes/factor.php b/admin/tool/mfa/factor/token/classes/factor.php index 39c2576ba98..58bb69b6979 100644 --- a/admin/tool/mfa/factor/token/classes/factor.php +++ b/admin/tool/mfa/factor/token/classes/factor.php @@ -16,6 +16,7 @@ namespace factor_token; +use stdClass; use tool_mfa\local\factor\object_factor_base; use tool_mfa\local\secret_manager; @@ -34,7 +35,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_input() { + public function has_input(): bool { return false; } @@ -45,7 +46,7 @@ class factor extends object_factor_base { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors(stdClass $user): array { global $DB; $records = $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]); @@ -72,7 +73,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_state() { + public function get_state(): string { global $USER; // Check if there was a previous locked status to return. @@ -108,10 +109,10 @@ class factor extends object_factor_base { * Token Implementation. * We can't get_state like the parent here or it will recurse forever. * - * @param mixed $state the state constant to set + * @param string $state the state constant to set * @return bool */ - public function set_state($state) { + public function set_state($state): bool { global $SESSION; $property = 'factor_' . $this->name; $SESSION->$property = $state; @@ -121,9 +122,10 @@ class factor extends object_factor_base { /** * Token implementation. * - * @param \stdClass $user + * @param stdClass $user + * @return array */ - public function possible_states($user) { + public function possible_states(stdClass $user): array { return [ \tool_mfa\plugininfo\factor::STATE_PASS, \tool_mfa\plugininfo\factor::STATE_NEUTRAL, @@ -138,7 +140,7 @@ class factor extends object_factor_base { * @param \MoodleQuickForm $mform Form to inject global elements into. * @return void */ - public function global_definition_after_data($mform) { + public function global_definition_after_data($mform): void { global $SESSION; // First thing, we need to decide on whether we should show the checkbox. @@ -159,8 +161,9 @@ class factor extends object_factor_base { * Store information about the token status. * * @param object $data Data from the form. + * @return void */ - public function global_submit($data) { + public function global_submit($data): void { global $SESSION; // Store any kind of response here, we shouldnt show again. @@ -174,7 +177,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function post_pass_state() { + public function post_pass_state(): void { global $CFG, $SESSION, $USER; if (!property_exists($SESSION, 'tool_mfa_factor_token')) { diff --git a/admin/tool/mfa/factor/token/classes/privacy/provider.php b/admin/tool/mfa/factor/token/classes/privacy/provider.php index e76671f9496..505585af30f 100644 --- a/admin/tool/mfa/factor/token/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/token/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_token\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ use core_privacy\local\legacy_polyfill; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/token/lang/en/factor_token.php b/admin/tool/mfa/factor/token/lang/en/factor_token.php index 463863cae5b..6c07f2f00c4 100644 --- a/admin/tool/mfa/factor/token/lang/en/factor_token.php +++ b/admin/tool/mfa/factor/token/lang/en/factor_token.php @@ -32,3 +32,4 @@ $string['settings:expireovernight_help'] = 'This forces tokens to expire overnig $string['settings:expiry'] = 'Trust duration'; $string['settings:expiry_help'] = 'The duration a device is trusted before requiring a new MFA authentication.'; $string['summarycondition'] = 'the user has previously trusted this device'; +$string['tokenstoredindevice'] = 'The user with id {$a->userid} had an MFA token stored on their device.
Information: {$a->string}.'; diff --git a/admin/tool/mfa/factor/token/tests/factor_test.php b/admin/tool/mfa/factor/token/tests/factor_test.php index c14e8d4edd3..3a0b1450c66 100644 --- a/admin/tool/mfa/factor/token/tests/factor_test.php +++ b/admin/tool/mfa/factor/token/tests/factor_test.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace factor_token\tests; +namespace factor_token; /** * Tests for MFA manager class. @@ -27,11 +27,24 @@ namespace factor_token\tests; */ class factor_test extends \advanced_testcase { + /** + * Holds specific requested factor, which is token factor. + * + * @var \factor_token\factor $factor + */ + public \factor_token\factor $factor; + public function setUp(): void { $this->resetAfterTest(); $this->factor = new \factor_token\factor('token'); } + /** + * Test calculating expiry time in general + * + * @covers ::calculate_expiry_time + * @return void + */ public function test_calculate_expiry_time_in_general() { $timestamp = 1642213800; // 1230 UTC. @@ -42,7 +55,7 @@ class factor_test extends \advanced_testcase { // Test that non-overnight timestamps are just exactly as configured. // We don't need to care about 0 or negative ints, they will just make the cookie expire immediately. $expiry = $method->invoke($this->factor, $timestamp); - $this->assertEquals($expiry[1], DAYSECS); + $this->assertEquals(DAYSECS, $expiry[1]); set_config('expiry', HOURSECS, 'factor_token'); $expiry = $method->invoke($this->factor, $timestamp); @@ -75,6 +88,7 @@ class factor_test extends \advanced_testcase { * value, provided it never goes past raw value expiry time, and when it * needs to be 2am, it's 2am on the following morning. * + * @covers ::calculate_expiry_time * @param int $timestamp * @dataProvider timestamp_provider */ @@ -123,6 +137,7 @@ class factor_test extends \advanced_testcase { * value, provided it never goes past raw value expiry time, and when it * needs to be 2am, it's 2am on the morning after tomorrow. * + * @covers ::calculate_expiry_time * @param int $timestamp * @dataProvider timestamp_provider */ @@ -173,6 +188,7 @@ class factor_test extends \advanced_testcase { /** * This should check if the 3am expiry is pushed back to 2am as expected, but everything else appears as expected * + * @covers ::calculate_expiry_time * @param int $timestamp * @dataProvider timestamp_provider */ @@ -217,6 +233,7 @@ class factor_test extends \advanced_testcase { /** * Only relevant based on the hour padding used, which is currently set to 2 hours (2am). * + * @covers ::calculate_expiry_time * @param int $timestamp * @dataProvider timestamp_provider */ diff --git a/admin/tool/mfa/factor/token/version.php b/admin/tool/mfa/factor/token/version.php index 45dcff68099..d3a114a0a25 100644 --- a/admin/tool/mfa/factor/token/version.php +++ b/admin/tool/mfa/factor/token/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2022011700; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_token'; $plugin->release = 2022011700; $plugin->maturity = MATURITY_STABLE; -$plugin->dependencies = ['tool_mfa' => 2019102400]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/factor/totp/classes/factor.php b/admin/tool/mfa/factor/totp/classes/factor.php index 1874cd6cef3..9e45fa1b4cc 100644 --- a/admin/tool/mfa/factor/totp/classes/factor.php +++ b/admin/tool/mfa/factor/totp/classes/factor.php @@ -34,6 +34,7 @@ require_once(__DIR__.'/../extlib/ParagonIE/ConstantTime/Base32.php'); use tool_mfa\local\factor\object_factor_base; use OTPHP\TOTP; +use stdClass; /** * TOTP factor class. @@ -69,7 +70,7 @@ class factor extends object_factor_base { * @param string $secret * @return string */ - public function generate_totp_uri($secret) { + public function generate_totp_uri(string $secret): string { global $USER, $SITE, $CFG; $host = parse_url($CFG->wwwroot, PHP_URL_HOST); $sitename = str_replace(':', '', $SITE->fullname); @@ -86,7 +87,7 @@ class factor extends object_factor_base { * @param string $secret * @return string */ - public function generate_qrcode($secret) { + public function generate_qrcode(string $secret): string { $uri = $this->generate_totp_uri($secret); $qrcode = new \TCPDF2DBarcode($uri, 'QRCODE'); $image = $qrcode->getBarcodePngData(7, 7); @@ -100,7 +101,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_state() { + public function get_state(): string { global $USER; $userfactors = $this->get_active_user_factors($USER); @@ -116,9 +117,9 @@ class factor extends object_factor_base { * TOTP Factor implementation. * * @param \MoodleQuickForm $mform - * @return object $mform + * @return \MoodleQuickForm $mform */ - public function setup_factor_form_definition($mform) { + public function setup_factor_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm { $secret = $this->generate_secret_code(); $mform->addElement('hidden', 'secret', $secret); $mform->setType('secret', PARAM_ALPHANUM); @@ -130,12 +131,12 @@ class factor extends object_factor_base { * TOTP Factor implementation. * * @param \MoodleQuickForm $mform - * @return object $mform + * @return \MoodleQuickForm $mform */ - public function setup_factor_form_definition_after_data($mform) { + public function setup_factor_form_definition_after_data(\MoodleQuickForm $mform): \MoodleQuickForm { global $OUTPUT, $SITE, $USER; - // Array of elements to allow XSS on when we're running Totara. + // Array of elements to allow XSS. $xssallowedelements = []; $mform->addElement('html', $OUTPUT->heading(get_string('setupfactor', 'factor_totp'), 2)); @@ -199,7 +200,7 @@ class factor extends object_factor_base { $html = $togglelink . $html; $xssallowedelements[] = $mform->addElement('static', 'enter', '', $html); - // Allow XSS on Totara. + // Allow XSS. if (method_exists('MoodleQuickForm_static', 'set_allow_xss')) { foreach ($xssallowedelements as $xssallowedelement) { $xssallowedelement->set_allow_xss(true); @@ -220,7 +221,7 @@ class factor extends object_factor_base { * @param array $data * @return array */ - public function setup_factor_form_validation($data) { + public function setup_factor_form_validation(array $data): array { $errors = []; $totp = TOTP::create($data['secret']); @@ -235,9 +236,9 @@ class factor extends object_factor_base { * TOTP Factor implementation. * * @param \MoodleQuickForm $mform - * @return object $mform + * @return \MoodleQuickForm $mform */ - public function login_form_definition($mform) { + public function login_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm { $mform->disable_form_change_checker(); $mform->addElement(new \tool_mfa\local\form\verification_field()); @@ -253,7 +254,7 @@ class factor extends object_factor_base { * @param array $data * @return array */ - public function login_form_validation($data) { + public function login_form_validation(array $data): array { global $USER; $factors = $this->get_active_user_factors($USER); $result = ['verificationcode' => get_string('error:wrongverification', 'factor_totp')]; @@ -295,9 +296,9 @@ class factor extends object_factor_base { * @param TOTP $totp the totp object to check against. * @param stdClass $factor the factor with information required. * - * @return const constant with verification state. + * @return string constant with verification state. */ - public function validate_code($code, $window, $totp, $factor) { + public function validate_code(string $code, int $window, TOTP $totp, stdClass $factor): string { // First check if this code matches the last verified timestamp. $lastverified = $this->get_lastverified($factor->id); if ($lastverified > 0 && $totp->verify($code, $lastverified, $window)) { @@ -328,7 +329,7 @@ class factor extends object_factor_base { * * @return string */ - public function generate_secret_code() { + public function generate_secret_code(): string { $totp = TOTP::create(); return substr($totp->getSecret(), 0, 16); } @@ -339,11 +340,11 @@ class factor extends object_factor_base { * @param stdClass $data * @return stdClass the factor record, or null. */ - public function setup_user_factor($data) { + public function setup_user_factor(stdClass $data): stdClass|null { global $DB, $USER; if (!empty($data->secret)) { - $row = new \stdClass(); + $row = new stdClass(); $row->userid = $USER->id; $row->factor = $this->name; $row->secret = $data->secret; @@ -381,7 +382,7 @@ class factor extends object_factor_base { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors($user): array { global $DB; return $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]); } @@ -391,7 +392,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_revoke() { + public function has_revoke(): bool { return true; } @@ -400,7 +401,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_setup() { + public function has_setup(): bool { return true; } @@ -409,7 +410,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function show_setup_buttons() { + public function show_setup_buttons(): bool { return true; } @@ -419,7 +420,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function post_pass_state() { + public function post_pass_state(): void { return; } @@ -427,9 +428,9 @@ class factor extends object_factor_base { * TOTP Factor implementation. * TOTP cannot return fail state. * - * @param \stdClass $user + * @param stdClass $user */ - public function possible_states($user) { + public function possible_states(stdClass $user): array { return [ \tool_mfa\plugininfo\factor::STATE_PASS, \tool_mfa\plugininfo\factor::STATE_NEUTRAL, @@ -442,7 +443,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_setup_string() { + public function get_setup_string(): string { return get_string('factorsetup', 'factor_totp'); } } diff --git a/admin/tool/mfa/factor/totp/classes/privacy/provider.php b/admin/tool/mfa/factor/totp/classes/privacy/provider.php index c670b855894..c6c5b23a20e 100644 --- a/admin/tool/mfa/factor/totp/classes/privacy/provider.php +++ b/admin/tool/mfa/factor/totp/classes/privacy/provider.php @@ -17,7 +17,6 @@ namespace factor_totp\privacy; use core_privacy\local\metadata\null_provider; -use core_privacy\local\legacy_polyfill; /** * Privacy provider. @@ -28,7 +27,6 @@ use core_privacy\local\legacy_polyfill; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements null_provider { - use legacy_polyfill; /** * Get the language string identifier with the component's language @@ -36,7 +34,7 @@ class provider implements null_provider { * * @return string */ - public static function _get_reason() { + public static function get_reason(): string { return 'privacy:metadata'; } } diff --git a/admin/tool/mfa/factor/totp/extlib/Assert/LICENSE b/admin/tool/mfa/factor/totp/extlib/Assert/LICENSE new file mode 100644 index 00000000000..43672e7e62d --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/Assert/LICENSE @@ -0,0 +1,11 @@ +Copyright (c) 2011-2013, Benjamin Eberlei +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. diff --git a/admin/tool/mfa/factor/totp/extlib/Assert/composer.json b/admin/tool/mfa/factor/totp/extlib/Assert/composer.json new file mode 100644 index 00000000000..37784796a9b --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/Assert/composer.json @@ -0,0 +1,23 @@ +{ + "name": "beberlei/assert", + "description": "Thin assertion library for input validation in business models.", + "authors": [ + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"} + ], + "license": "BSD-2-Clause", + "keywords": ["assert", "assertion", "validation"], + "require": { + "ext-mbstring": "*" + }, + "autoload": { + "psr-0": { + "Assert": "lib/" + }, + "files": ["lib/Assert/functions.php"] + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/admin/tool/mfa/factor/totp/extlib/Assert/readme_moodle.txt b/admin/tool/mfa/factor/totp/extlib/Assert/readme_moodle.txt new file mode 100644 index 00000000000..bccc0a3edbf --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/Assert/readme_moodle.txt @@ -0,0 +1,17 @@ +Assert 2.1 +-------------- +https://github.com/beberlei/assert/releases/tag/v2.1 + +Instructions to import WebAuthn into Moodle: + +1. Download the latest release from https://github.com/beberlei/assert/releases/tag/vx.x + (choose "Source code") +2. Unzip the source code +3. Copy the following files from assert-x.x/lib/Assert into admin/tool/mfa/factor/totp/extlib/Assert: + 1. Assertion.php + 2. AssertionFailedException.php + 3. InvalidArgumentException.php + +4. Copy the following files from assert-x.x into admin/tool/mfa/factor/totp/extlib/Assert: + 1. LICENSE + 2. composer.json diff --git a/admin/tool/mfa/factor/totp/extlib/OTPHP/LICENSE b/admin/tool/mfa/factor/totp/extlib/OTPHP/LICENSE new file mode 100644 index 00000000000..e6a673e34dd --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/OTPHP/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014-2016 Florent Morselli + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/admin/tool/mfa/factor/totp/extlib/OTPHP/composer.json b/admin/tool/mfa/factor/totp/extlib/OTPHP/composer.json new file mode 100644 index 00000000000..1abd98395fa --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/OTPHP/composer.json @@ -0,0 +1,40 @@ +{ + "name": "spomky-labs/otphp", + "type": "library", + "description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator", + "license": "MIT", + "keywords": ["otp", "hotp", "totp", "RFC 4226", "RFC 6238", "Google Authenticator", "FreeOTP"], + "homepage": "https://github.com/Spomky-Labs/otphp", + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/otphp/contributors" + } + ], + "require": { + "php": "^7.1", + "paragonie/constant_time_encoding": "^2.0", + "beberlei/assert": "^2.4" + }, + "require-dev": { + "phpunit/phpunit": "^6.0", + "satooshi/php-coveralls": "^1.0" + }, + "suggest": { + }, + "autoload": { + "psr-4": { "OTPHP\\": "src/" } + }, + "autoload-dev": { + "psr-4": { "OTPHP\\Test\\": "tests/" } + }, + "extra": { + "branch-alias": { + "dev-master": "9.0.x-dev" + } + } +} diff --git a/admin/tool/mfa/factor/totp/extlib/OTPHP/readme_moodle.txt b/admin/tool/mfa/factor/totp/extlib/OTPHP/readme_moodle.txt new file mode 100644 index 00000000000..ba87585e750 --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/OTPHP/readme_moodle.txt @@ -0,0 +1,19 @@ +OTPHP 9.1.1 +-------------- +https://github.com/Spomky-Labs/otphp/releases/tag/v9.1.1 + +Instructions to import WebAuthn into Moodle: + +1. Download the latest release from https://github.com/Spomky-Labs/otphp/releases/tag/vx.x.x + (choose "Source code") +2. Unzip the source code +3. Copy the following files from otphp-x.x/lib/OTPHP into admin/tool/mfa/factor/totp/extlib/OTPHP: + 1. OTP.php + 2. OTPInterface.php + 3. ParameterTrait.php + 4. TOTP.php + 5. TOTPInterface.php + +4. Copy the following files from otphp-x.x into admin/tool/mfa/factor/totp/extlib/OTPHP: + 1. LICENSE + 2. composer.json diff --git a/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/LICENSE.txt b/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/LICENSE.txt new file mode 100644 index 00000000000..06302425a34 --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/LICENSE.txt @@ -0,0 +1,48 @@ +The MIT License (MIT) + +Copyright (c) 2016 Paragon Initiative Enterprises + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------ +This library was based on the work of Steve "Sc00bz" Thomas. +------------------------------------------------------------------------------ + +The MIT License (MIT) + +Copyright (c) 2014 Steve Thomas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/composer.json b/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/composer.json new file mode 100644 index 00000000000..7aad332b7d9 --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/composer.json @@ -0,0 +1,40 @@ +{ + "name": "paragonie/constant_time_encoding", + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base64", "encoding", "rfc4648", "base32", "base16", "hex", "bin2hex", "hex2bin", "base64_encode", "base64_decode", "base32_encode", "base32_decode" + ], + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "support": { + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "email": "info@paragonie.com", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "^6", + "vimeo/psalm": "^0.3|^" + }, + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + } +} diff --git a/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/readme_moodle.txt b/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/readme_moodle.txt new file mode 100644 index 00000000000..b54b217bca1 --- /dev/null +++ b/admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime/readme_moodle.txt @@ -0,0 +1,17 @@ +Constant-Time Encoding 2.1.1 +-------------- +https://github.com/paragonie/constant_time_encoding/releases/tag/v2.1.1 + +Instructions to import WebAuthn into Moodle: + +1. Download the latest release from https://github.com/paragonie/constant_time_encoding/releases/tag/vx.x.x + (choose "Source code") +2. Unzip the source code +3. Copy the following files from constant_time_encoding-x.x/lib/constant_time_encoding into admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime: + 1. Base32.php + 2. Binary.php + 3. EncoderInterface.php + +4. Copy the following files from constant_time_encoding-x.x into admin/tool/mfa/factor/totp/extlib/ParagonIE/ConstantTime: + 1. LICENSE + 2. composer.json diff --git a/admin/tool/mfa/factor/totp/tests/factor_test.php b/admin/tool/mfa/factor/totp/tests/factor_test.php index af0765d6f4c..fe36fa7c07f 100644 --- a/admin/tool/mfa/factor/totp/tests/factor_test.php +++ b/admin/tool/mfa/factor/totp/tests/factor_test.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace factor_totp\tests; +namespace factor_totp; defined('MOODLE_INTERNAL') || die(); diff --git a/admin/tool/mfa/factor/totp/thirdpartylibs.xml b/admin/tool/mfa/factor/totp/thirdpartylibs.xml index ce5b19d6109..5365218a40c 100644 --- a/admin/tool/mfa/factor/totp/thirdpartylibs.xml +++ b/admin/tool/mfa/factor/totp/thirdpartylibs.xml @@ -3,22 +3,22 @@ extlib/Assert Assert - + 2.1 MIT - 2.1+ + https://github.com/beberlei/assert extlib/OTPHP OTPHP - + 9.1.1 MIT - 2.1+ + https://github.com/Spomky-Labs/otphp - extlib/ParagonIE - ParagonIE - - Custom - + extlib/ParagonIE/ConstantTime + Constant-Time Encoding + 2.1.1 + MIT + https://github.com/paragonie/constant_time_encoding diff --git a/admin/tool/mfa/factor/totp/version.php b/admin/tool/mfa/factor/totp/version.php index 0b8b45dc94c..b799ed7777c 100644 --- a/admin/tool/mfa/factor/totp/version.php +++ b/admin/tool/mfa/factor/totp/version.php @@ -26,9 +26,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2021021700; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). $plugin->release = 2021021700; -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_totp'; $plugin->maturity = MATURITY_STABLE; -$plugin->dependencies = ['tool_mfa' => 2019102400]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/factor/webauthn/amd/build/login.min.js b/admin/tool/mfa/factor/webauthn/amd/build/login.min.js index 719a3e42c2e..87e61e7d3c6 100644 --- a/admin/tool/mfa/factor/webauthn/amd/build/login.min.js +++ b/admin/tool/mfa/factor/webauthn/amd/build/login.min.js @@ -6,6 +6,6 @@ * @author Alex Morris * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("factor_webauthn/login",["factor_webauthn/utils"],(function(utils){return{init:function(getArgs){document.getElementById("id_submitbutton").addEventListener("click",(async function(e){if(e.preventDefault(),!navigator.credentials||!navigator.credentials.create)throw new Error("Browser not supported.");if(!1===(getArgs=JSON.parse(getArgs)).success)throw new Error(getArgs.msg||"unknown error occured");utils.recursiveBase64StrToArrayBuffer(getArgs);const cred=await navigator.credentials.get(getArgs),authenticatorAttestationResponse={id:cred.rawId?utils.arrayBufferToBase64(cred.rawId):null,clientDataJSON:cred.response.clientDataJSON?utils.arrayBufferToBase64(cred.response.clientDataJSON):null,authenticatorData:cred.response.authenticatorData?utils.arrayBufferToBase64(cred.response.authenticatorData):null,signature:cred.response.signature?utils.arrayBufferToBase64(cred.response.signature):null,userHandle:cred.response.userHandle?utils.arrayBufferToBase64(cred.response.userHandle):null};document.getElementById("id_response_input").value=JSON.stringify(authenticatorAttestationResponse),document.getElementById("id_response_input").form.submit()}))}}})); +define("factor_webauthn/login",["factor_webauthn/utils"],(function(utils){return{init:function(getArgs){const idSubmitButton=document.getElementById("id_submitbutton");idSubmitButton&&idSubmitButton.addEventListener("click",(async function(e){if(e.preventDefault(),!navigator.credentials||!navigator.credentials.create)throw new Error("Browser not supported.");if(!1===(getArgs=JSON.parse(getArgs)).success)throw new Error(getArgs.msg||"unknown error occured");utils.recursiveBase64StrToArrayBuffer(getArgs);const cred=await navigator.credentials.get(getArgs),authenticatorAttestationResponse={id:cred.rawId?utils.arrayBufferToBase64(cred.rawId):null,clientDataJSON:cred.response.clientDataJSON?utils.arrayBufferToBase64(cred.response.clientDataJSON):null,authenticatorData:cred.response.authenticatorData?utils.arrayBufferToBase64(cred.response.authenticatorData):null,signature:cred.response.signature?utils.arrayBufferToBase64(cred.response.signature):null,userHandle:cred.response.userHandle?utils.arrayBufferToBase64(cred.response.userHandle):null},responseInput=document.getElementById("id_response_input");responseInput.value=JSON.stringify(authenticatorAttestationResponse),responseInput.form.submit()}))}}})); //# sourceMappingURL=login.min.js.map \ No newline at end of file diff --git a/admin/tool/mfa/factor/webauthn/amd/build/login.min.js.map b/admin/tool/mfa/factor/webauthn/amd/build/login.min.js.map index dae3578cda6..443e22b461b 100644 --- a/admin/tool/mfa/factor/webauthn/amd/build/login.min.js.map +++ b/admin/tool/mfa/factor/webauthn/amd/build/login.min.js.map @@ -1 +1 @@ -{"version":3,"file":"login.min.js","sources":["../src/login.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * For collecting WebAuthn authenticator details on login\n *\n * @module factor_webauthn/login\n * @copyright Catalyst IT\n * @author Alex Morris \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['factor_webauthn/utils'], function(utils) {\n return {\n init: function(getArgs) {\n document.getElementById('id_submitbutton').addEventListener('click', async function(e) {\n e.preventDefault();\n if (!navigator.credentials || !navigator.credentials.create) {\n throw new Error('Browser not supported.');\n }\n\n getArgs = JSON.parse(getArgs);\n\n if (getArgs.success === false) {\n throw new Error(getArgs.msg || 'unknown error occured');\n }\n\n utils.recursiveBase64StrToArrayBuffer(getArgs);\n\n const cred = await navigator.credentials.get(getArgs);\n\n const authenticatorAttestationResponse = {\n id: cred.rawId ? utils.arrayBufferToBase64(cred.rawId) : null,\n clientDataJSON: cred.response.clientDataJSON ? utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,\n authenticatorData:\n cred.response.authenticatorData ? utils.arrayBufferToBase64(cred.response.authenticatorData) : null,\n signature: cred.response.signature ? utils.arrayBufferToBase64(cred.response.signature) : null,\n userHandle: cred.response.userHandle ? utils.arrayBufferToBase64(cred.response.userHandle) : null\n };\n\n document.getElementById('id_response_input').value = JSON.stringify(authenticatorAttestationResponse);\n document.getElementById('id_response_input').form.submit();\n });\n }\n };\n});\n"],"names":["define","utils","init","getArgs","document","getElementById","addEventListener","async","e","preventDefault","navigator","credentials","create","Error","JSON","parse","success","msg","recursiveBase64StrToArrayBuffer","cred","get","authenticatorAttestationResponse","id","rawId","arrayBufferToBase64","clientDataJSON","response","authenticatorData","signature","userHandle","value","stringify","form","submit"],"mappings":";;;;;;;;AAwBAA,+BAAO,CAAC,0BAA0B,SAASC,aAChC,CACHC,KAAM,SAASC,SACXC,SAASC,eAAe,mBAAmBC,iBAAiB,SAASC,eAAeC,MAChFA,EAAEC,kBACGC,UAAUC,cAAgBD,UAAUC,YAAYC,aAC3C,IAAIC,MAAM,8BAKI,KAFxBV,QAAUW,KAAKC,MAAMZ,UAETa,cACF,IAAIH,MAAMV,QAAQc,KAAO,yBAGnChB,MAAMiB,gCAAgCf,eAEhCgB,WAAaT,UAAUC,YAAYS,IAAIjB,SAEvCkB,iCAAmC,CACrCC,GAAIH,KAAKI,MAAQtB,MAAMuB,oBAAoBL,KAAKI,OAAS,KACzDE,eAAgBN,KAAKO,SAASD,eAAiBxB,MAAMuB,oBAAoBL,KAAKO,SAASD,gBAAkB,KACzGE,kBACIR,KAAKO,SAASC,kBAAoB1B,MAAMuB,oBAAoBL,KAAKO,SAASC,mBAAqB,KACnGC,UAAWT,KAAKO,SAASE,UAAY3B,MAAMuB,oBAAoBL,KAAKO,SAASE,WAAa,KAC1FC,WAAYV,KAAKO,SAASG,WAAa5B,MAAMuB,oBAAoBL,KAAKO,SAASG,YAAc,MAGjGzB,SAASC,eAAe,qBAAqByB,MAAQhB,KAAKiB,UAAUV,kCACpEjB,SAASC,eAAe,qBAAqB2B,KAAKC"} \ No newline at end of file +{"version":3,"file":"login.min.js","sources":["../src/login.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * For collecting WebAuthn authenticator details on login\n *\n * @module factor_webauthn/login\n * @copyright Catalyst IT\n * @author Alex Morris \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['factor_webauthn/utils'], function(utils) {\n return {\n init: function(getArgs) {\n const idSubmitButton = document.getElementById('id_submitbutton');\n if (idSubmitButton) {\n idSubmitButton.addEventListener('click', async function(e) {\n e.preventDefault();\n if (!navigator.credentials || !navigator.credentials.create) {\n throw new Error('Browser not supported.');\n }\n\n getArgs = JSON.parse(getArgs);\n\n if (getArgs.success === false) {\n throw new Error(getArgs.msg || 'unknown error occured');\n }\n\n utils.recursiveBase64StrToArrayBuffer(getArgs);\n\n const cred = await navigator.credentials.get(getArgs);\n\n const authenticatorAttestationResponse = {\n id: cred.rawId ? utils.arrayBufferToBase64(cred.rawId) : null,\n clientDataJSON:\n cred.response.clientDataJSON ? utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,\n authenticatorData:\n cred.response.authenticatorData ? utils.arrayBufferToBase64(cred.response.authenticatorData) : null,\n signature: cred.response.signature ? utils.arrayBufferToBase64(cred.response.signature) : null,\n userHandle: cred.response.userHandle ? utils.arrayBufferToBase64(cred.response.userHandle) : null\n };\n\n const responseInput = document.getElementById('id_response_input');\n responseInput.value = JSON.stringify(authenticatorAttestationResponse);\n responseInput.form.submit();\n });\n }\n }\n };\n});\n"],"names":["define","utils","init","getArgs","idSubmitButton","document","getElementById","addEventListener","async","e","preventDefault","navigator","credentials","create","Error","JSON","parse","success","msg","recursiveBase64StrToArrayBuffer","cred","get","authenticatorAttestationResponse","id","rawId","arrayBufferToBase64","clientDataJSON","response","authenticatorData","signature","userHandle","responseInput","value","stringify","form","submit"],"mappings":";;;;;;;;AAwBAA,+BAAO,CAAC,0BAA0B,SAASC,aAChC,CACHC,KAAM,SAASC,eACLC,eAAiBC,SAASC,eAAe,mBAC3CF,gBACAA,eAAeG,iBAAiB,SAASC,eAAeC,MACpDA,EAAEC,kBACGC,UAAUC,cAAgBD,UAAUC,YAAYC,aAC3C,IAAIC,MAAM,8BAKI,KAFxBX,QAAUY,KAAKC,MAAMb,UAETc,cACF,IAAIH,MAAMX,QAAQe,KAAO,yBAGnCjB,MAAMkB,gCAAgChB,eAEhCiB,WAAaT,UAAUC,YAAYS,IAAIlB,SAEvCmB,iCAAmC,CACrCC,GAAIH,KAAKI,MAAQvB,MAAMwB,oBAAoBL,KAAKI,OAAS,KACzDE,eACIN,KAAKO,SAASD,eAAiBzB,MAAMwB,oBAAoBL,KAAKO,SAASD,gBAAkB,KAC7FE,kBACIR,KAAKO,SAASC,kBAAoB3B,MAAMwB,oBAAoBL,KAAKO,SAASC,mBAAqB,KACnGC,UAAWT,KAAKO,SAASE,UAAY5B,MAAMwB,oBAAoBL,KAAKO,SAASE,WAAa,KAC1FC,WAAYV,KAAKO,SAASG,WAAa7B,MAAMwB,oBAAoBL,KAAKO,SAASG,YAAc,MAG3FC,cAAgB1B,SAASC,eAAe,qBAC9CyB,cAAcC,MAAQjB,KAAKkB,UAAUX,kCACrCS,cAAcG,KAAKC"} \ No newline at end of file diff --git a/admin/tool/mfa/factor/webauthn/amd/build/utils.min.js b/admin/tool/mfa/factor/webauthn/amd/build/utils.min.js index f4caf2699f1..21efa9c2b4f 100644 --- a/admin/tool/mfa/factor/webauthn/amd/build/utils.min.js +++ b/admin/tool/mfa/factor/webauthn/amd/build/utils.min.js @@ -6,6 +6,6 @@ * @author Alex Morris * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("factor_webauthn/utils",[],(function(){return{recursiveBase64StrToArrayBuffer:function(obj){if("object"==typeof obj)for(let key in obj)if("string"==typeof obj[key]){let str=obj[key];if("=?BINARY?B?"===str.substring(0,"=?BINARY?B?".length)&&"?="===str.substring(str.length-"?=".length)){str=str.substring("=?BINARY?B?".length,str.length-"?=".length);let binary_string=window.atob(str),len=binary_string.length,bytes=new Uint8Array(len);for(let i=0;i.\n\n/**\n * WebAuthn utility functions, for handling array buffers.\n *\n * @module factor_webauthn/utils\n * @copyright Catalyst IT\n * @author Alex Morris \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine([], function() {\n return {\n recursiveBase64StrToArrayBuffer: function(obj) {\n let prefix = '=?BINARY?B?';\n let suffix = '?=';\n if (typeof obj === 'object') {\n for (let key in obj) {\n if (typeof obj[key] === 'string') {\n let str = obj[key];\n if (str.substring(0, prefix.length) === prefix && str.substring(str.length - suffix.length) === suffix) {\n str = str.substring(prefix.length, str.length - suffix.length);\n\n let binary_string = window.atob(str);\n let len = binary_string.length;\n let bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) {\n bytes[i] = binary_string.charCodeAt(i);\n }\n obj[key] = bytes.buffer;\n }\n } else {\n this.recursiveBase64StrToArrayBuffer(obj[key]);\n }\n }\n }\n },\n arrayBufferToBase64: function(buffer) {\n let binary = '';\n let bytes = new Uint8Array(buffer);\n let len = bytes.byteLength;\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return window.btoa(binary);\n },\n };\n});\n"],"names":["define","recursiveBase64StrToArrayBuffer","obj","key","str","substring","length","binary_string","window","atob","len","bytes","Uint8Array","i","charCodeAt","buffer","arrayBufferToBase64","binary","byteLength","String","fromCharCode","btoa"],"mappings":";;;;;;;;AAwBAA,+BAAO,IAAI,iBACA,CACHC,gCAAiC,SAASC,QAGnB,iBAARA,QACF,IAAIC,OAAOD,OACY,iBAAbA,IAAIC,KAAmB,KAC1BC,IAAMF,IAAIC,QALb,gBAMGC,IAAIC,UAAU,EANjB,cAM2BC,SAL3B,OAKiDF,IAAIC,UAAUD,IAAIE,OALnE,KAKmFA,QAAoB,CACpGF,IAAMA,IAAIC,UAPb,cAO8BC,OAAQF,IAAIE,OAN1C,KAM0DA,YAEnDC,cAAgBC,OAAOC,KAAKL,KAC5BM,IAAMH,cAAcD,OACpBK,MAAQ,IAAIC,WAAWF,SACtB,IAAIG,EAAI,EAAGA,EAAIH,IAAKG,IACrBF,MAAME,GAAKN,cAAcO,WAAWD,GAExCX,IAAIC,KAAOQ,MAAMI,kBAGhBd,gCAAgCC,IAAIC,OAKzDa,oBAAqB,SAASD,YACtBE,OAAS,GACTN,MAAQ,IAAIC,WAAWG,QACvBL,IAAMC,MAAMO,eACX,IAAIL,EAAI,EAAGA,EAAIH,IAAKG,IACrBI,QAAUE,OAAOC,aAAaT,MAAME,WAEjCL,OAAOa,KAAKJ"} \ No newline at end of file +{"version":3,"file":"utils.min.js","sources":["../src/utils.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * WebAuthn utility functions, for handling array buffers.\n *\n * @module factor_webauthn/utils\n * @copyright Catalyst IT\n * @author Alex Morris \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine([], function() {\n return {\n recursiveBase64StrToArrayBuffer: function(obj) {\n let prefix = '=?BINARY?B?';\n let suffix = '?=';\n if (typeof obj === 'object') {\n for (let key in obj) {\n let isString = true;\n if (typeof obj[key] !== 'string') {\n this.recursiveBase64StrToArrayBuffer(obj[key]);\n isString = false;\n }\n\n let str = obj[key];\n if (isString && str.substring(0, prefix.length) === prefix &&\n str.substring(str.length - suffix.length) === suffix) {\n str = str.substring(prefix.length, str.length - suffix.length);\n\n const binaryString = window.atob(str);\n let len = binaryString.length;\n let bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n obj[key] = bytes.buffer;\n }\n }\n }\n },\n arrayBufferToBase64: function(buffer) {\n let binary = '';\n let bytes = new Uint8Array(buffer);\n let len = bytes.byteLength;\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return window.btoa(binary);\n },\n };\n});\n"],"names":["define","recursiveBase64StrToArrayBuffer","obj","key","isString","str","substring","length","binaryString","window","atob","len","bytes","Uint8Array","i","charCodeAt","buffer","arrayBufferToBase64","binary","byteLength","String","fromCharCode","btoa"],"mappings":";;;;;;;;AAwBAA,+BAAO,IAAI,iBACA,CACHC,gCAAiC,SAASC,QAGnB,iBAARA,QACF,IAAIC,OAAOD,IAAK,KACbE,UAAW,EACS,iBAAbF,IAAIC,YACNF,gCAAgCC,IAAIC,MACzCC,UAAW,OAGXC,IAAMH,IAAIC,QACVC,UAXC,gBAWWC,IAAIC,UAAU,EAXzB,cAWmCC,SAVnC,OAWAF,IAAIC,UAAUD,IAAIE,OAXlB,KAWkCA,QAAoB,CACvDF,IAAMA,IAAIC,UAbT,cAa0BC,OAAQF,IAAIE,OAZtC,KAYsDA,cAEjDC,aAAeC,OAAOC,KAAKL,SAC7BM,IAAMH,aAAaD,OACnBK,MAAQ,IAAIC,WAAWF,SACtB,IAAIG,EAAI,EAAGA,EAAIH,IAAKG,IACrBF,MAAME,GAAKN,aAAaO,WAAWD,GAEvCZ,IAAIC,KAAOS,MAAMI,UAKjCC,oBAAqB,SAASD,YACtBE,OAAS,GACTN,MAAQ,IAAIC,WAAWG,QACvBL,IAAMC,MAAMO,eACX,IAAIL,EAAI,EAAGA,EAAIH,IAAKG,IACrBI,QAAUE,OAAOC,aAAaT,MAAME,WAEjCL,OAAOa,KAAKJ"} \ No newline at end of file diff --git a/admin/tool/mfa/factor/webauthn/amd/src/login.js b/admin/tool/mfa/factor/webauthn/amd/src/login.js index 741f16d19af..0d853deb65e 100644 --- a/admin/tool/mfa/factor/webauthn/amd/src/login.js +++ b/admin/tool/mfa/factor/webauthn/amd/src/login.js @@ -25,34 +25,39 @@ define(['factor_webauthn/utils'], function(utils) { return { init: function(getArgs) { - document.getElementById('id_submitbutton').addEventListener('click', async function(e) { - e.preventDefault(); - if (!navigator.credentials || !navigator.credentials.create) { - throw new Error('Browser not supported.'); - } + const idSubmitButton = document.getElementById('id_submitbutton'); + if (idSubmitButton) { + idSubmitButton.addEventListener('click', async function(e) { + e.preventDefault(); + if (!navigator.credentials || !navigator.credentials.create) { + throw new Error('Browser not supported.'); + } - getArgs = JSON.parse(getArgs); + getArgs = JSON.parse(getArgs); - if (getArgs.success === false) { - throw new Error(getArgs.msg || 'unknown error occured'); - } + if (getArgs.success === false) { + throw new Error(getArgs.msg || 'unknown error occured'); + } - utils.recursiveBase64StrToArrayBuffer(getArgs); + utils.recursiveBase64StrToArrayBuffer(getArgs); - const cred = await navigator.credentials.get(getArgs); + const cred = await navigator.credentials.get(getArgs); - const authenticatorAttestationResponse = { - id: cred.rawId ? utils.arrayBufferToBase64(cred.rawId) : null, - clientDataJSON: cred.response.clientDataJSON ? utils.arrayBufferToBase64(cred.response.clientDataJSON) : null, - authenticatorData: - cred.response.authenticatorData ? utils.arrayBufferToBase64(cred.response.authenticatorData) : null, - signature: cred.response.signature ? utils.arrayBufferToBase64(cred.response.signature) : null, - userHandle: cred.response.userHandle ? utils.arrayBufferToBase64(cred.response.userHandle) : null - }; + const authenticatorAttestationResponse = { + id: cred.rawId ? utils.arrayBufferToBase64(cred.rawId) : null, + clientDataJSON: + cred.response.clientDataJSON ? utils.arrayBufferToBase64(cred.response.clientDataJSON) : null, + authenticatorData: + cred.response.authenticatorData ? utils.arrayBufferToBase64(cred.response.authenticatorData) : null, + signature: cred.response.signature ? utils.arrayBufferToBase64(cred.response.signature) : null, + userHandle: cred.response.userHandle ? utils.arrayBufferToBase64(cred.response.userHandle) : null + }; - document.getElementById('id_response_input').value = JSON.stringify(authenticatorAttestationResponse); - document.getElementById('id_response_input').form.submit(); - }); + const responseInput = document.getElementById('id_response_input'); + responseInput.value = JSON.stringify(authenticatorAttestationResponse); + responseInput.form.submit(); + }); + } } }; }); diff --git a/admin/tool/mfa/factor/webauthn/amd/src/utils.js b/admin/tool/mfa/factor/webauthn/amd/src/utils.js index 2985fb52bdf..dcf5ab66857 100644 --- a/admin/tool/mfa/factor/webauthn/amd/src/utils.js +++ b/admin/tool/mfa/factor/webauthn/amd/src/utils.js @@ -29,21 +29,24 @@ define([], function() { let suffix = '?='; if (typeof obj === 'object') { for (let key in obj) { - if (typeof obj[key] === 'string') { - let str = obj[key]; - if (str.substring(0, prefix.length) === prefix && str.substring(str.length - suffix.length) === suffix) { - str = str.substring(prefix.length, str.length - suffix.length); - - let binary_string = window.atob(str); - let len = binary_string.length; - let bytes = new Uint8Array(len); - for (let i = 0; i < len; i++) { - bytes[i] = binary_string.charCodeAt(i); - } - obj[key] = bytes.buffer; - } - } else { + let isString = true; + if (typeof obj[key] !== 'string') { this.recursiveBase64StrToArrayBuffer(obj[key]); + isString = false; + } + + let str = obj[key]; + if (isString && str.substring(0, prefix.length) === prefix && + str.substring(str.length - suffix.length) === suffix) { + str = str.substring(prefix.length, str.length - suffix.length); + + const binaryString = window.atob(str); + let len = binaryString.length; + let bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + obj[key] = bytes.buffer; } } } diff --git a/admin/tool/mfa/factor/webauthn/classes/factor.php b/admin/tool/mfa/factor/webauthn/classes/factor.php index 24ae7018d88..3cb823cf32f 100644 --- a/admin/tool/mfa/factor/webauthn/classes/factor.php +++ b/admin/tool/mfa/factor/webauthn/classes/factor.php @@ -23,6 +23,7 @@ require_once($CFG->libdir . '/webauthn/src/WebAuthn.php'); use lbuchs\WebAuthn\Binary\ByteBuffer; use lbuchs\WebAuthn\WebAuthn; use lbuchs\WebAuthn\WebAuthnException; +use stdClass; use tool_mfa\local\factor\object_factor_base; /** @@ -63,7 +64,7 @@ class factor extends object_factor_base { * @param stdClass $user the user to check against. * @return array */ - public function get_all_user_factors($user) { + public function get_all_user_factors(stdClass $user): array { global $DB; return $DB->get_records('tool_mfa', ['userid' => $user->id, 'factor' => $this->name]); } @@ -73,7 +74,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_input() { + public function has_input(): bool { return true; } @@ -82,7 +83,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_revoke() { + public function has_revoke(): bool { return true; } @@ -91,7 +92,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function has_setup() { + public function has_setup(): bool { return true; } @@ -100,16 +101,17 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function show_setup_buttons() { + public function show_setup_buttons(): bool { return true; } /** * WebAuthn factor implementation. * - * @param \stdClass $user + * @param stdClass $user + * @return array */ - public function possible_states($user) { + public function possible_states(stdClass $user): array { return [ \tool_mfa\plugininfo\factor::STATE_PASS, \tool_mfa\plugininfo\factor::STATE_NEUTRAL, @@ -122,7 +124,7 @@ class factor extends object_factor_base { * * {@inheritDoc} */ - public function get_state() { + public function get_state(): string { global $USER; $userfactors = $this->get_active_user_factors($USER); @@ -136,8 +138,10 @@ class factor extends object_factor_base { /** * Gets the string for setup button on preferences page. + * + * @return string */ - public function get_setup_string() { + public function get_setup_string(): string { return get_string('setupfactor', 'factor_webauthn'); } @@ -145,11 +149,13 @@ class factor extends object_factor_base { * WebAuthn Factor implementation. * * @param \MoodleQuickForm $mform - * @return object $mform + * @return \MoodleQuickForm $mform */ - public function login_form_definition($mform) { + public function login_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm { global $PAGE, $USER, $SESSION; + $mform->addElement('html', get_string('loginexplanation', 'factor_webauthn')); + $mform->addElement('hidden', 'response_input', '', ['id' => 'id_response_input']); $mform->setType('response_input', PARAM_RAW); @@ -184,7 +190,7 @@ class factor extends object_factor_base { * @param array $data * @return array */ - public function login_form_validation($data) { + public function login_form_validation(array $data): array { global $USER, $SESSION; $errors = []; @@ -234,7 +240,7 @@ class factor extends object_factor_base { * @param \MoodleQuickForm $mform * @return object $mform */ - public function setup_factor_form_definition($mform) { + public function setup_factor_form_definition(\MoodleQuickForm $mform): \MoodleQuickForm { global $PAGE, $USER, $SESSION; $mform->addElement('text', 'webauthn_name', get_string('authenticatorname', 'factor_webauthn')); @@ -283,10 +289,10 @@ class factor extends object_factor_base { /** * WebAuthn Factor implementation. * - * @param array $data - * @return array + * @param object $data + * @return stdClass|null */ - public function setup_user_factor($data) { + public function setup_user_factor(object $data): stdClass|null { global $DB, $USER, $SESSION; if (!empty($data->webauthn_name) && !empty($data->response_input) && isset($SESSION->factor_webauthn_challenge)) { diff --git a/admin/tool/mfa/factor/webauthn/lang/en/factor_webauthn.php b/admin/tool/mfa/factor/webauthn/lang/en/factor_webauthn.php index 4841371da19..7e38d11c566 100644 --- a/admin/tool/mfa/factor/webauthn/lang/en/factor_webauthn.php +++ b/admin/tool/mfa/factor/webauthn/lang/en/factor_webauthn.php @@ -32,10 +32,11 @@ $string['authenticator:usb'] = 'USB'; $string['authenticatorname'] = 'Security key name'; $string['authenticatortypelimitation'] = 'Please note that you can only use security keys of one of these types: {$a}.
Registering other security keys is possible, but you cannot use them during login.'; $string['error'] = 'Failed to authenticate'; -$string['info'] = '

Use a WebAuthn supported authenticator

'; -$string['loginskip'] = 'I don\'t have my authenticator'; -$string['loginsubmit'] = 'Verify authenticator'; -$string['pluginname'] = 'WebAuthn'; +$string['info'] = '

Use a security key

'; +$string['loginexplanation'] = 'Your account settings require that you authenticate with your security key in addition to your password.'; +$string['loginskip'] = 'I don\'t have my security key'; +$string['loginsubmit'] = 'Verify security key'; +$string['pluginname'] = 'Security Key'; $string['privacy:metadata'] = 'The WebAuthn factor plugin does not store any personal data'; $string['register'] = 'Register authenticator'; $string['settings:authenticatortypes'] = 'Types of authenticator'; diff --git a/admin/tool/mfa/factor/webauthn/version.php b/admin/tool/mfa/factor/webauthn/version.php index e5c4d61f852..914865f3fb9 100644 --- a/admin/tool/mfa/factor/webauthn/version.php +++ b/admin/tool/mfa/factor/webauthn/version.php @@ -25,9 +25,9 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2023052400; // The current plugin version (Date: YYYYMMDDXX). -$plugin->release = 2023052400; -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->release = 2023062900; +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'factor_webauthn'; $plugin->maturity = MATURITY_ALPHA; -$plugin->dependencies = ['tool_mfa' => 2023031600]; +$plugin->dependencies = ['tool_mfa' => 2023080300]; diff --git a/admin/tool/mfa/guide.php b/admin/tool/mfa/guide.php index 30dc1b674fb..709515157e2 100644 --- a/admin/tool/mfa/guide.php +++ b/admin/tool/mfa/guide.php @@ -37,7 +37,7 @@ if (!get_config('tool_mfa', 'guidance')) { } // Navigation. Target user preferences as previous node if authed. -if (isloggedin() && (!empty($SESSION->tool_mfa_authenticated) || $SESSION->tool_mfa_authenticated)) { +if (isloggedin() && (isset($SESSION->tool_mfa_authenticated) && $SESSION->tool_mfa_authenticated)) { if ($node = $PAGE->settingsnav->find('usercurrentsettings', null)) { $PAGE->navbar->add($node->get_content(), $node->action()); } diff --git a/admin/tool/mfa/lang/en/tool_mfa.php b/admin/tool/mfa/lang/en/tool_mfa.php index b93be039e6c..25572c75c6e 100644 --- a/admin/tool/mfa/lang/en/tool_mfa.php +++ b/admin/tool/mfa/lang/en/tool_mfa.php @@ -142,7 +142,7 @@ $string['settings:guidancepage_help'] = 'HTML here will be displayed on the guid $string['settings:lockout'] = 'Lockout threshold'; $string['settings:lockout_help'] = 'Amount of attempts a user has at answering input factors before they are not permitted to login.'; $string['settings:redir_exclusions'] = 'Urls which should not redirect the MFA check'; -$string['settings:redir_exclusions_help'] = 'Each new line is a relative URL from the siteroot for which the MFA check will not redirect from eg. /admin/tool/securityquestions/set_responses.php'; +$string['settings:redir_exclusions_help'] = 'Each new line is a relative URL from the siteroot for which the MFA check will not redirect from'; $string['settings:weight'] = 'Factor weight'; $string['settings:weight_help'] = 'The weight of this factor if passed. A user needs at least 100 points to login.'; $string['setup'] = 'Setup'; diff --git a/admin/tool/mfa/lib.php b/admin/tool/mfa/lib.php index 4a3709642f0..c858c764c3d 100644 --- a/admin/tool/mfa/lib.php +++ b/admin/tool/mfa/lib.php @@ -23,6 +23,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use core\context; + /** * Main hook. * @@ -37,7 +39,7 @@ * @throws \moodle_exception */ function tool_mfa_after_require_login($courseorid = null, $autologinguest = null, $cm = null, - $setwantsurltome = null, $preventredirect = null) { + $setwantsurltome = null, $preventredirect = null): void { global $SESSION; // Tests for hooks being fired to test patches. @@ -59,10 +61,10 @@ function tool_mfa_after_require_login($courseorid = null, $autologinguest = null * @param stdClass $course * @param context_course $coursecontext * - * @return void or null + * @return mix void or null * @throws \moodle_exception */ -function tool_mfa_extend_navigation_user_settings($navigation, $user, $usercontext, $course, $coursecontext) { +function tool_mfa_extend_navigation_user_settings(navigation_node $navigation, stdClass $user, $usercontext, stdClass $course, $coursecontext) { global $PAGE; // Only inject if user is on the preferences page. @@ -83,8 +85,10 @@ function tool_mfa_extend_navigation_user_settings($navigation, $user, $userconte /** * Triggered as soon as practical on every moodle bootstrap after config has * been loaded. The $USER object is available at this point too. + * + * @return void */ -function tool_mfa_after_config() { +function tool_mfa_after_config(): void { global $CFG, $SESSION; // Tests for hooks being fired to test patches. @@ -104,8 +108,10 @@ function tool_mfa_after_config() { /** * Any plugin typically an admin tool can add new bulk user actions + * + * @return array */ -function tool_mfa_bulk_user_actions() { +function tool_mfa_bulk_user_actions(): array { return [ 'tool_mfa_reset_factors' => new action_link( new moodle_url('/admin/tool/mfa/reset_factor.php'), @@ -126,7 +132,8 @@ function tool_mfa_bulk_user_actions() { * @param array $options * @return bool */ -function tool_mfa_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = []) { +function tool_mfa_pluginfile(stdClass $course, stdClass $cm, context $context, string $filearea, + array $args, bool $forcedownload, array $options = []): bool { // Hardcode to only send guidance files from the top level. $fs = get_file_storage(); $file = $fs->get_file( diff --git a/admin/tool/mfa/patch/MOODLE_35_STABLE.diff b/admin/tool/mfa/patch/MOODLE_35_STABLE.diff deleted file mode 100644 index 735a581724a..00000000000 --- a/admin/tool/mfa/patch/MOODLE_35_STABLE.diff +++ /dev/null @@ -1,86 +0,0 @@ -From 6d6f2d3543cd4b172aa85f0e47d7f531b7ec4d53 Mon Sep 17 00:00:00 2001 -From: Brendan Heywood -Date: Wed, 18 Oct 2017 16:20:33 +1100 -Subject: [PATCH 1/2] MDL-60470 core: New hook 'after_require_login' - -This adds a hook towards the end of the require_login function. ---- - lib/moodlelib.php | 14 ++++++++++++++ - 1 file changed, 14 insertions(+) - -diff --git a/lib/moodlelib.php b/lib/moodlelib.php -index 3ac3d8be1b7..d9e5baa175e 100644 ---- a/lib/moodlelib.php -+++ b/lib/moodlelib.php -@@ -2705,6 +2705,8 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $ - $CFG->forceclean = true; - } - -+ $afterlogins = get_plugins_with_function('after_require_login', 'lib.php'); -+ - // Do not bother admins with any formalities, except for activities pending deletion. - if (is_siteadmin() && !($cm && $cm->deletioninprogress)) { - // Set the global $COURSE. -@@ -2716,6 +2718,12 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $ - } - // Set accesstime or the user will appear offline which messes up messaging. - user_accesstime_log($course->id); -+ -+ foreach ($afterlogins as $plugintype => $plugins) { -+ foreach ($plugins as $pluginfunction) { -+ $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect); -+ } -+ } - return; - } - -@@ -2923,6 +2931,12 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $ - $PAGE->set_course($course); - } - -+ foreach ($afterlogins as $plugintype => $plugins) { -+ foreach ($plugins as $pluginfunction) { -+ $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect); -+ } -+ } -+ - // Finally access granted, update lastaccess times. - user_accesstime_log($course->id); - } --- -2.17.1 - - -From 7235752ed449ab6662a317f059e444598bf1a862 Mon Sep 17 00:00:00 2001 -From: Brendan Heywood -Date: Thu, 8 Aug 2019 13:26:50 +1000 -Subject: [PATCH 2/2] MDL-66340 setup: Add after_config for after setup.php is - loaded - ---- - lib/setup.php | 13 +++++++++++++ - 1 file changed, 13 insertions(+) - -diff --git a/lib/setup.php b/lib/setup.php -index b1cb7e054ec..df1a2d978a5 100644 ---- a/lib/setup.php -+++ b/lib/setup.php -@@ -1042,3 +1042,16 @@ if (false) { - $OUTPUT = new core_renderer(null, null); - $PAGE = new moodle_page(); - } -+ -+// Allow plugins to callback as soon possible after setup.php is loaded. -+$pluginswithfunction = get_plugins_with_function('after_config', 'lib.php'); -+foreach ($pluginswithfunction as $plugins) { -+ foreach ($plugins as $function) { -+ try { -+ $function(); -+ } catch (Exception $e) { -+ debugging("Exception calling '$function'", DEBUG_DEVELOPER, $e->getTrace()); -+ } -+ } -+} -+ --- -2.17.1 diff --git a/admin/tool/mfa/patch/MOODLE_36_STABLE.diff b/admin/tool/mfa/patch/MOODLE_36_STABLE.diff deleted file mode 100644 index 259ef93ea96..00000000000 --- a/admin/tool/mfa/patch/MOODLE_36_STABLE.diff +++ /dev/null @@ -1,87 +0,0 @@ -From 51484a595701e56897b0913974c566c6a13c1f32 Mon Sep 17 00:00:00 2001 -From: Brendan Heywood -Date: Wed, 18 Oct 2017 16:20:33 +1100 -Subject: [PATCH 1/2] MDL-60470 core: New hook 'after_require_login' - -This adds a hook towards the end of the require_login function. ---- - lib/moodlelib.php | 14 ++++++++++++++ - 1 file changed, 14 insertions(+) - -diff --git a/lib/moodlelib.php b/lib/moodlelib.php -index e2016a8bf75..29e11632233 100644 ---- a/lib/moodlelib.php -+++ b/lib/moodlelib.php -@@ -2769,6 +2769,8 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $ - $CFG->forceclean = true; - } - -+ $afterlogins = get_plugins_with_function('after_require_login', 'lib.php'); -+ - // Do not bother admins with any formalities, except for activities pending deletion. - if (is_siteadmin() && !($cm && $cm->deletioninprogress)) { - // Set the global $COURSE. -@@ -2783,6 +2785,12 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $ - if (!WS_SERVER && !AJAX_SCRIPT) { - user_accesstime_log($course->id); - } -+ -+ foreach ($afterlogins as $plugintype => $plugins) { -+ foreach ($plugins as $pluginfunction) { -+ $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect); -+ } -+ } - return; - } - -@@ -2990,6 +2998,12 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $ - $PAGE->set_course($course); - } - -+ foreach ($afterlogins as $plugintype => $plugins) { -+ foreach ($plugins as $pluginfunction) { -+ $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect); -+ } -+ } -+ - // Finally access granted, update lastaccess times. - // Do not update access time for webservice or ajax requests. - if (!WS_SERVER && !AJAX_SCRIPT) { --- -2.17.1 - - -From 838ad593636395e4c4884e1bf47836504413b702 Mon Sep 17 00:00:00 2001 -From: Brendan Heywood -Date: Thu, 8 Aug 2019 13:26:50 +1000 -Subject: [PATCH 2/2] MDL-66340 setup: Add after_config for after setup.php is - loaded - ---- - lib/setup.php | 13 +++++++++++++ - 1 file changed, 13 insertions(+) - -diff --git a/lib/setup.php b/lib/setup.php -index 2cf12822c6d..a4e0acf0bb8 100644 ---- a/lib/setup.php -+++ b/lib/setup.php -@@ -1041,3 +1041,16 @@ if (false) { - $OUTPUT = new core_renderer(null, null); - $PAGE = new moodle_page(); - } -+ -+// Allow plugins to callback as soon possible after setup.php is loaded. -+$pluginswithfunction = get_plugins_with_function('after_config', 'lib.php'); -+foreach ($pluginswithfunction as $plugins) { -+ foreach ($plugins as $function) { -+ try { -+ $function(); -+ } catch (Exception $e) { -+ debugging("Exception calling '$function'", DEBUG_DEVELOPER, $e->getTrace()); -+ } -+ } -+} -+ --- -2.17.1 - diff --git a/admin/tool/mfa/patch/MOODLE_37_STABLE.diff b/admin/tool/mfa/patch/MOODLE_37_STABLE.diff deleted file mode 100644 index c8edc6eded3..00000000000 --- a/admin/tool/mfa/patch/MOODLE_37_STABLE.diff +++ /dev/null @@ -1,34 +0,0 @@ -From 1275e9b283c81b80b73d1c87c64449eb472cc037 Mon Sep 17 00:00:00 2001 -From: Brendan Heywood -Date: Thu, 8 Aug 2019 13:26:50 +1000 -Subject: [PATCH] MDL-66340 setup: Add after_config for after setup.php is - loaded - ---- - lib/setup.php | 13 +++++++++++++ - 1 file changed, 13 insertions(+) - -diff --git a/lib/setup.php b/lib/setup.php -index 2cf12822c6d..a4e0acf0bb8 100644 ---- a/lib/setup.php -+++ b/lib/setup.php -@@ -1041,3 +1041,16 @@ if (false) { - $OUTPUT = new core_renderer(null, null); - $PAGE = new moodle_page(); - } -+ -+// Allow plugins to callback as soon possible after setup.php is loaded. -+$pluginswithfunction = get_plugins_with_function('after_config', 'lib.php'); -+foreach ($pluginswithfunction as $plugins) { -+ foreach ($plugins as $function) { -+ try { -+ $function(); -+ } catch (Exception $e) { -+ debugging("Exception calling '$function'", DEBUG_DEVELOPER, $e->getTrace()); -+ } -+ } -+} -+ --- -2.17.1 - diff --git a/admin/tool/mfa/patch/TOTARA_16.patch b/admin/tool/mfa/patch/TOTARA_16.patch deleted file mode 100644 index 0d3ea86ab0d..00000000000 --- a/admin/tool/mfa/patch/TOTARA_16.patch +++ /dev/null @@ -1,61 +0,0 @@ -diff --git a/server/lib/moodlelib.php b/server/lib/moodlelib.php -index e80b84cb652..033de11df7f 100644 ---- a/server/lib/moodlelib.php -+++ b/server/lib/moodlelib.php -@@ -2895,6 +2895,8 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $ - // Make sure the USER has a sesskey set up. Used for CSRF protection. - sesskey(); - -+ $afterlogins = get_plugins_with_function('after_require_login', 'lib.php'); -+ - // Do not bother admins with any formalities, except for activities pending deletion. - if (is_siteadmin() && !($cm && $cm->deletioninprogress)) { - // Set the global $COURSE. -@@ -2906,6 +2908,12 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $ - } - // Set accesstime or the user will appear offline which messes up messaging. - user_accesstime_log($course->id); -+ -+ foreach ($afterlogins as $plugintype => $plugins) { -+ foreach ($plugins as $pluginfunction) { -+ $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect); -+ } -+ } - return; - } - -@@ -3176,6 +3184,12 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $ - } - } - -+ foreach ($afterlogins as $plugintype => $plugins) { -+ foreach ($plugins as $pluginfunction) { -+ $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect); -+ } -+ } -+ - // Finally access granted, update lastaccess times. - user_accesstime_log($course->id); - } -diff --git a/server/lib/setup.php b/server/lib/setup.php -index c05e13b03aa..4d89eda0713 100644 ---- a/server/lib/setup.php -+++ b/server/lib/setup.php -@@ -800,3 +800,17 @@ if (!function_exists('hash_equals')) { - return false; - } - } -+ -+ -+// Allow plugins to callback as soon possible after setup.php is loaded. -+$pluginswithfunction = get_plugins_with_function('after_config', 'lib.php'); -+foreach ($pluginswithfunction as $plugins) { -+ foreach ($plugins as $function) { -+ try { -+ $function(); -+ } catch (Throwable $e) { -+ debugging("Exception calling '$function'", DEBUG_DEVELOPER, $e->getTrace()); -+ } -+ } -+} -+ diff --git a/admin/tool/mfa/renderer.php b/admin/tool/mfa/renderer.php index 7dbb5b74158..ea65a92dbd8 100644 --- a/admin/tool/mfa/renderer.php +++ b/admin/tool/mfa/renderer.php @@ -28,9 +28,9 @@ class tool_mfa_renderer extends plugin_renderer_base { * Returns the state of the factor as a badge * * @param string $state - * @return html + * @return string */ - public function get_state_badge($state) { + public function get_state_badge(string $state): string { switch ($state) { case \tool_mfa\plugininfo\factor::STATE_PASS: @@ -57,9 +57,9 @@ class tool_mfa_renderer extends plugin_renderer_base { /** * Returns a list of factors which a user can add * - * @return html + * @return string */ - public function available_factors() { + public function available_factors(): string { $html = $this->output->heading(get_string('preferences:availablefactors', 'tool_mfa'), 2); $factors = \tool_mfa\plugininfo\factor::get_enabled_factors(); @@ -77,10 +77,10 @@ class tool_mfa_renderer extends plugin_renderer_base { /** * Returns the html section for factor setup * - * @param object $factor object of the factor class - * @return void + * @param object $factor object of the factor class + * @return string */ - public function setup_factor($factor) { + public function setup_factor(object $factor): string { $html = ''; $html .= html_writer::start_tag('div', ['class' => 'card']); @@ -105,7 +105,7 @@ class tool_mfa_renderer extends plugin_renderer_base { * @return string $html * @throws \coding_exception */ - public function active_factors() { + public function active_factors(): string { global $USER, $CFG; require_once($CFG->dirroot . '/iplookup/lib.php'); @@ -206,7 +206,7 @@ class tool_mfa_renderer extends plugin_renderer_base { * * @return string $notification */ - public function not_enough_factors() { + public function not_enough_factors(): string { global $CFG, $SITE; $notification = \html_writer::tag('h4', get_string('error:notenoughfactors', 'tool_mfa')); @@ -231,7 +231,7 @@ class tool_mfa_renderer extends plugin_renderer_base { // Logout button. $url = new \moodle_url('/admin/tool/mfa/auth.php', ['logout' => 1]); - $btn = new \single_button($url, get_string('logout'), 'post', true); + $btn = new \single_button($url, get_string('logout'), 'post', \single_button::BUTTON_PRIMARY); $return .= $this->render($btn); $return .= $this->guide_link(); @@ -245,7 +245,7 @@ class tool_mfa_renderer extends plugin_renderer_base { * @param int $lookback the period to view. * @return string the HTML for the table */ - public function factors_in_use_table($lookback) { + public function factors_in_use_table(int $lookback): string { global $DB; $factors = \tool_mfa\plugininfo\factor::get_factors(); @@ -326,8 +326,8 @@ class tool_mfa_renderer extends plugin_renderer_base { // Auth rows. $authtypes = get_enabled_auth_plugins(true); + $row = []; foreach ($authtypes as $authtype) { - $row = []; $row[] = \html_writer::tag('b', $authtype); // Setup the overall totals columns. @@ -380,7 +380,7 @@ class tool_mfa_renderer extends plugin_renderer_base { * * @return string the HTML for the table */ - public function factors_locked_table() { + public function factors_locked_table(): string { global $DB; $factors = \tool_mfa\plugininfo\factor::get_factors(); @@ -435,7 +435,7 @@ class tool_mfa_renderer extends plugin_renderer_base { * @param object $factor the factor class * @return string the HTML for the table */ - public function factor_locked_users_table($factor) { + public function factor_locked_users_table(object $factor): string { global $DB; $table = new html_table(); @@ -509,9 +509,9 @@ class tool_mfa_renderer extends plugin_renderer_base { /** * Returns a html section render of the guide link template * - * @return string + * @return string */ - public function guide_link() { + public function guide_link(): string { if (!get_config('tool_mfa', 'guidance')) { return ''; } @@ -528,11 +528,12 @@ class tool_mfa_renderer extends plugin_renderer_base { * @param HTML_QuickForm_element $element element * @param bool $required if input is required field * @param bool $advanced if input is an advanced field - * @param string $error error message to display + * @param string|null $error error message to display * @param bool $ingroup True if this element is rendered as part of a group * @return mixed string|bool */ - public function mform_element($element, $required, $advanced, $error, $ingroup) { + public function mform_element(HTML_QuickForm_element $element, bool $required, + bool $advanced, string|null $error, bool $ingroup): string|bool { $script = null; if ($element instanceof tool_mfa\local\form\verification_field) { if ($this->page->pagelayout === 'secure') { diff --git a/admin/tool/mfa/settings.php b/admin/tool/mfa/settings.php index e34845c9458..3a5af4dc96d 100644 --- a/admin/tool/mfa/settings.php +++ b/admin/tool/mfa/settings.php @@ -73,15 +73,9 @@ if ($hassiteconfig) { $plugin->load_settings($ADMIN, 'toolmfafolder', $hassiteconfig); } - if (file_exists($CFG->dirroot . '/totara')) { - // Totara navigation. - $section = 'toolmfafolder'; - } else { - // Moodle navigation. - $ADMIN->add('reports', new admin_category('toolmfareports', get_string('mfareports', 'tool_mfa'))); - $section = 'toolmfareports'; - } - $ADMIN->add($section, + $ADMIN->add('reports', new admin_category('toolmfareports', get_string('mfareports', 'tool_mfa'))); + + $ADMIN->add('toolmfareports', new admin_externalpage('factorreport', get_string('factorreport', 'tool_mfa'), new moodle_url('/admin/tool/mfa/factor_report.php'))); } diff --git a/admin/tool/mfa/styles.css b/admin/tool/mfa/styles.css index 635719b7894..4d7301fc60b 100644 --- a/admin/tool/mfa/styles.css +++ b/admin/tool/mfa/styles.css @@ -1,8 +1,8 @@ input.tool-mfa-verification-code, .tool-mfa-verification-code input { /* Some elements must be important to override form element*/ - font-size: 1.25em !important; + font-size: 1.25em; letter-spacing: 1.05em; font-family: monospace; - width: 11.5em !important; + width: 11.5em; } \ No newline at end of file diff --git a/admin/tool/mfa/templates/guide_link.mustache b/admin/tool/mfa/templates/guide_link.mustache index df1ce003d2e..313c99cf92b 100644 --- a/admin/tool/mfa/templates/guide_link.mustache +++ b/admin/tool/mfa/templates/guide_link.mustache @@ -25,6 +25,6 @@ }} \ No newline at end of file diff --git a/admin/tool/mfa/tests/admin_setting_managemfa_test.php b/admin/tool/mfa/tests/admin_setting_managemfa_test.php index f5fb6c2e3d7..5af422fd589 100644 --- a/admin/tool/mfa/tests/admin_setting_managemfa_test.php +++ b/admin/tool/mfa/tests/admin_setting_managemfa_test.php @@ -14,11 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace tool_mfa\tests; +namespace tool_mfa; +use tool_mfa\tool_mfa_trait; defined('MOODLE_INTERNAL') || die(); - -require_once(__DIR__ . '/tool_mfa_testcase.php'); +require_once(__DIR__ . '/tool_mfa_trait.php'); /** * Tests for MFA admin settings @@ -29,8 +29,15 @@ require_once(__DIR__ . '/tool_mfa_testcase.php'); * @copyright Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class admin_setting_managemfa_test extends tool_mfa_testcase { +class admin_setting_managemfa_test extends \advanced_testcase { + use tool_mfa_trait; + + /** + * Tests getting the factor combinations + * + * @covers ::get_factor_combinations + */ public function test_get_factor_combinations_default() { $namagemfa = new \tool_mfa\local\admin_setting_managemfa(); $factors = \tool_mfa\plugininfo\factor::get_enabled_factors(); @@ -39,7 +46,12 @@ class admin_setting_managemfa_test extends tool_mfa_testcase { $this->assertEquals(0, count($combinations)); } - public function test_get_factor_combinations_provider() { + /** + * Data provider for test_get_factor_combinations_with_data_provider(). + * + * @return array + */ + public function get_factor_combinations_provider() { $provider = []; $factors = []; @@ -122,9 +134,10 @@ class admin_setting_managemfa_test extends tool_mfa_testcase { } /** - * Tests getting the factor combinations + * Tests getting the factor combinations with data provider * - * @dataProvider test_get_factor_combinations_provider + * @covers ::get_factor_combinations + * @dataProvider get_factor_combinations_provider * @param array $factorset configured factors * @param int $combinationscount expected count of available combinations */ @@ -156,6 +169,11 @@ class admin_setting_managemfa_test extends tool_mfa_testcase { $this->assertEquals($combinationscount, count($combinations)); } + /** + * Tests checking the factor combinations + * + * @covers ::get_factor_combinations + */ public function test_factor_combination_checker() { $this->resetAfterTest(); $managemfa = new \tool_mfa\local\admin_setting_managemfa(); diff --git a/admin/tool/mfa/tests/manager_test.php b/admin/tool/mfa/tests/manager_test.php index 1996007c737..1e976cb334e 100644 --- a/admin/tool/mfa/tests/manager_test.php +++ b/admin/tool/mfa/tests/manager_test.php @@ -14,10 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace tool_mfa\tests; +namespace tool_mfa; +use tool_mfa\tool_mfa_trait; defined('MOODLE_INTERNAL') || die(); -require_once(__DIR__ . '/tool_mfa_testcase.php'); +require_once(__DIR__ . '/tool_mfa_trait.php'); /** * Tests for MFA manager class. @@ -27,8 +28,16 @@ require_once(__DIR__ . '/tool_mfa_testcase.php'); * @copyright Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class manager_test extends tool_mfa_testcase { +class manager_test extends \advanced_testcase { + use tool_mfa_trait; + + /** + * Tests getting the factor total weight + * + * @covers ::get_total_weight + * @covers ::setup_user_factor + */ public function test_get_total_weight() { $this->resetAfterTest(true); @@ -69,6 +78,11 @@ class manager_test extends tool_mfa_testcase { $this->assertEquals(300, \tool_mfa\manager::get_total_weight()); } + /** + * Tests getting the factor status + * + * @covers ::get_status + */ public function test_get_status() { $this->resetAfterTest(true); @@ -77,21 +91,21 @@ class manager_test extends tool_mfa_testcase { $this->setUser($user); // Check for fail status with no factors. - $this->assertEquals(\tool_mfa\manager::get_status(), \tool_mfa\plugininfo\factor::STATE_FAIL); + $this->assertEquals(\tool_mfa\plugininfo\factor::STATE_FAIL, \tool_mfa\manager::get_status()); // Now add a no input factor. $this->set_factor_state('auth', 1, 100); set_config('goodauth', 'manual', 'factor_auth'); // Check state is now passing. - $this->assertEquals(\tool_mfa\manager::get_status(), \tool_mfa\plugininfo\factor::STATE_PASS); + $this->assertEquals(\tool_mfa\plugininfo\factor::STATE_PASS, \tool_mfa\manager::get_status()); // Now add a failure state factor, and ensure that fail takes precedent. $this->set_factor_state('email', 1, 100); $factoremail = \tool_mfa\plugininfo\factor::get_factor('email'); $factoremail->set_state(\tool_mfa\plugininfo\factor::STATE_FAIL); - $this->assertEquals(\tool_mfa\manager::get_status(), \tool_mfa\plugininfo\factor::STATE_FAIL); + $this->assertEquals(\tool_mfa\plugininfo\factor::STATE_FAIL, \tool_mfa\manager::get_status()); // Remove no input factor, and remove fail state by logging in/out. Simulates no data entered yet. $this->setUser(null); @@ -99,9 +113,14 @@ class manager_test extends tool_mfa_testcase { $this->set_factor_state('auth', 0, 100); $factoremail->set_state(\tool_mfa\plugininfo\factor::STATE_UNKNOWN); - $this->assertEquals(\tool_mfa\manager::get_status(), \tool_mfa\plugininfo\factor::STATE_NEUTRAL); + $this->assertEquals(\tool_mfa\plugininfo\factor::STATE_NEUTRAL, \tool_mfa\manager::get_status()); } + /** + * Tests checking if passed enough factors + * + * @covers ::passed_enough_factors + */ public function test_passed_enough_factors() { $this->resetAfterTest(true); @@ -110,27 +129,27 @@ class manager_test extends tool_mfa_testcase { $this->setUser($user); // Check when no factors are setup. - $this->assertEquals(\tool_mfa\manager::passed_enough_factors(), false); + $this->assertEquals(false, \tool_mfa\manager::passed_enough_factors()); // Setup a no input factor. $this->set_factor_state('auth', 1, 100); set_config('goodauth', 'manual', 'factor_auth'); // Check that is enough to pass. - $this->assertEquals(\tool_mfa\manager::passed_enough_factors(), true); + $this->assertEquals(true, \tool_mfa\manager::passed_enough_factors()); // Lower the weight of the factor. $this->set_factor_state('auth', 1, 75); - $this->assertEquals(\tool_mfa\manager::passed_enough_factors(), false); + $this->assertEquals(false, \tool_mfa\manager::passed_enough_factors()); // Add another factor to get enough weight to pass, but dont set pass state yet. $this->set_factor_state('email', 1, 100); $factoremail = \tool_mfa\plugininfo\factor::get_factor('email'); - $this->assertEquals(\tool_mfa\manager::passed_enough_factors(), false); + $this->assertEquals(false, \tool_mfa\manager::passed_enough_factors()); // Now pass the factor and check weight. $factoremail->set_state(\tool_mfa\plugininfo\factor::STATE_PASS); - $this->assertEquals(\tool_mfa\manager::passed_enough_factors(), true); + $this->assertEquals(true, \tool_mfa\manager::passed_enough_factors()); } /** @@ -154,15 +173,13 @@ class manager_test extends tool_mfa_testcase { ['/admin/tool/mfa/action.php', 'http://test.server/parent/directory', true], ['/', 'http://test.server/parent/directory', true, ['url' => $badparam1]], ['/', 'http://test.server/parent/directory', true, ['url' => $badparam2]], - ['/admin/tool/securityquestions/set_responses.php', 'http://test.server', false], - ['/admin/tool/securityquestions/set_responses.php', 'http://test.server', false, ['delete' => 1]], - ['/admin/tool/securityquestions/randompage.php', 'http://test.server', true, ['delete' => 1]], ]; } /** * Tests whether it should require mfa * + * @covers ::should_require_mfa * @param string $urlstring * @param string $webroot * @param bool $status @@ -176,9 +193,14 @@ class manager_test extends tool_mfa_testcase { $this->setUser($user); $CFG->wwwroot = $webroot; $url = new \moodle_url($urlstring, $params); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), $status); + $this->assertEquals($status, \tool_mfa\manager::should_require_mfa($url, false)); } + /** + * Tests whether it should require the mfa checks + * + * @covers ::should_require_mfa + */ public function test_should_require_mfa_checks() { // Setup test and user. global $CFG; @@ -189,61 +211,68 @@ class manager_test extends tool_mfa_testcase { // Upgrade checks. $this->setAdminUser(); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT); + // Mark the site as upgraded so it will not fail when running the unittest as a whole. + $CFG->allversionshash = \core_component::get_all_versions_hash(); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); $oldhash = $CFG->allversionshash; $CFG->allversionshash = 'abc'; - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::NO_REDIRECT); + $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); $CFG->allversionshash = $oldhash; $upgradesettings = new \moodle_url('/admin/upgradesettings.php'); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($upgradesettings, false), \tool_mfa\manager::NO_REDIRECT); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($upgradesettings, false)); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); // Admin not setup. $this->setUser($user); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); $CFG->adminsetuppending = 1; - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::NO_REDIRECT); + $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); $CFG->adminsetuppending = 0; // Check prevent_redirect. - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, true), \tool_mfa\manager::NO_REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); + $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, true)); // User not setup properly. - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); $notsetup = clone($user); unset($notsetup->firstname); $this->setUser($notsetup); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::NO_REDIRECT); + $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); $this->setUser($user); // Enrolment. $enrolurl = new \moodle_url('/enrol/index.php'); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($enrolurl, false), \tool_mfa\manager::NO_REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); + $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($enrolurl, false)); // Guest User. - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); $this->setGuestUser(); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::NO_REDIRECT); + $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); $this->setUser($user); // Forced password changes. - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); set_user_preference('auth_forcepasswordchange', true); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::NO_REDIRECT); + $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); set_user_preference('auth_forcepasswordchange', false); // Login as check. $user2 = $this->getDataGenerator()->create_user(); $syscontext = \context_system::instance(); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); $this->setAdminUser(); \core\session\manager::loginas($user2->id, $syscontext, false); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($badurl, false), \tool_mfa\manager::NO_REDIRECT); + $this->assertEquals(\tool_mfa\manager::NO_REDIRECT, \tool_mfa\manager::should_require_mfa($badurl, false)); $this->setUser($user); } + /** + * Tests should require the mfa redirection loop + * + * @covers ::should_require_mfa + */ public function test_should_require_mfa_redirection_loop() { // Setup test and user. global $CFG, $SESSION; @@ -257,44 +286,50 @@ class manager_test extends tool_mfa_testcase { $url = new \moodle_url('/'); // Test you get three redirs then exception. - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); // Set count to threshold. $SESSION->mfa_redir_count = 5; - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT_EXCEPTION); + $this->assertEquals(\tool_mfa\manager::REDIRECT_EXCEPTION, \tool_mfa\manager::should_require_mfa($url, false)); // Reset session vars. unset($SESSION->mfa_redir_referer); unset($SESSION->mfa_redir_count); // Test 4 different redir urls. - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); $_SERVER['HTTP_REFERER'] = 'http://phpunit.test/2'; - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); $_SERVER['HTTP_REFERER'] = 'http://phpunit3.test/3'; - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); $_SERVER['HTTP_REFERER'] = 'http://phpunit4.test/4'; - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); // Reset session vars. unset($SESSION->mfa_redir_referer); unset($SESSION->mfa_redir_count); // Test 6 then jump to new referer (5 + 1 to set the first time). $_SERVER['HTTP_REFERER'] = 'http://phpunit.test'; - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); $_SERVER['HTTP_REFERER'] = 'http://phpunit.test/2'; - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); // Now test that going back to original URL doesnt cause exception. $_SERVER['HTTP_REFERER'] = 'http://phpunit.test'; - $this->assertEquals(\tool_mfa\manager::should_require_mfa($url, false), \tool_mfa\manager::REDIRECT); + $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($url, false)); } + /** + * Tests checking for possible setup factor + * + * @covers ::possible_factor_setup + * @covers ::setup_user_factor + */ public function test_possible_factor_setup() { // Setup test and user. $this->resetAfterTest(true); @@ -325,6 +360,11 @@ class manager_test extends tool_mfa_testcase { set_config('enabled', 0, 'factor_admin'); } + /** + * Tests checking if a factor is ready + * + * @covers ::is_ready + */ public function test_is_ready() { // Setup test and user. global $CFG; @@ -360,6 +400,12 @@ class manager_test extends tool_mfa_testcase { set_config('enabled', 1, 'factor_nosetup'); } + /** + * Tests core hooks + * + * @covers ::mfa_config_hook_test + * @covers ::mfa_login_hook_test + */ public function test_core_hooks() { // Setup test and user. global $CFG, $SESSION; @@ -374,6 +420,11 @@ class manager_test extends tool_mfa_testcase { $this->assertTrue($SESSION->mfa_login_hook_test); } + /** + * Tests circular redirect auth + * + * @covers ::should_require_mfa + */ public function test_circular_redirect_auth() { // Setup test and user. $this->resetAfterTest(true); @@ -388,7 +439,7 @@ class manager_test extends tool_mfa_testcase { $this->assertEquals(\tool_mfa\manager::REDIRECT, \tool_mfa\manager::should_require_mfa($baseurl, false)); - // Now hammer it up to the threshold to emulate a repeated force browse from auth.php + // Now hammer it up to the threshold to emulate a repeated force browse from auth.php. for ($i = 0; $i < \tool_mfa\manager::REDIR_LOOP_THRESHOLD; $i++) { \tool_mfa\manager::should_require_mfa($baseurl, false); } diff --git a/admin/tool/mfa/tests/object_factor_base_test.php b/admin/tool/mfa/tests/object_factor_base_test.php index aa658d175a2..a2ab262b167 100644 --- a/admin/tool/mfa/tests/object_factor_base_test.php +++ b/admin/tool/mfa/tests/object_factor_base_test.php @@ -13,9 +13,12 @@ // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace tool_mfa\tests; -require_once(__DIR__ . '/tool_mfa_testcase.php'); +namespace tool_mfa; +use tool_mfa\tool_mfa_trait; + +defined('MOODLE_INTERNAL') || die(); +require_once(__DIR__ . '/tool_mfa_trait.php'); /** * Tests for base factor implementation methods. @@ -25,7 +28,16 @@ require_once(__DIR__ . '/tool_mfa_testcase.php'); * @copyright 2023 Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class object_factor_base_test extends tool_mfa_testcase { +class object_factor_base_test extends \advanced_testcase { + + use tool_mfa_trait; + + /** + * Test deleting user's configured factors + * + * @covers ::setup_user_factor + * @return void + */ public function test_revoke_user_factor() { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); diff --git a/admin/tool/mfa/tests/plugininfo_factor_test.php b/admin/tool/mfa/tests/plugininfo_factor_test.php index 4e5aa27e616..61e57345ef8 100644 --- a/admin/tool/mfa/tests/plugininfo_factor_test.php +++ b/admin/tool/mfa/tests/plugininfo_factor_test.php @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +namespace tool_mfa; + /** * Tests for plugininfo. * @@ -22,8 +24,18 @@ * @copyright Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class plugininfo_factor_test extends advanced_testcase { +class plugininfo_factor_test extends \advanced_testcase { + /** + * Tests getting next user factor + * + * @covers ::get_next_user_factor + * @covers ::setup_user_factor + * @covers ::get_enabled_factors + * @covers ::is_enabled + * @covers ::has_setup + * @covers ::get_active_user_factor_types + */ public function test_get_next_user_factor() { $this->resetAfterTest(true); @@ -33,7 +45,7 @@ class plugininfo_factor_test extends advanced_testcase { $this->setUser($user); // Test that with no enabled factors, fallback is returned. - $this->assertEquals(\tool_mfa\plugininfo\factor::get_next_user_factor()->name, 'fallback'); + $this->assertEquals('fallback', \tool_mfa\plugininfo\factor::get_next_user_factor()->name); // Setup enabled totp factor for user. set_config('enabled', 1, 'factor_totp'); @@ -45,11 +57,11 @@ class plugininfo_factor_test extends advanced_testcase { $this->assertNotEmpty($totpfactor->setup_user_factor((object) $totpdata)); // Test that factor now appears (from STATE_UNKNOWN). - $this->assertEquals(\tool_mfa\plugininfo\factor::get_next_user_factor()->name, 'totp'); + $this->assertEquals('totp', \tool_mfa\plugininfo\factor::get_next_user_factor()->name); // Now pass this factor, check for fallback. $totpfactor->set_state(\tool_mfa\plugininfo\factor::STATE_PASS); - $this->assertEquals(\tool_mfa\plugininfo\factor::get_next_user_factor()->name, 'fallback'); + $this->assertEquals('fallback', \tool_mfa\plugininfo\factor::get_next_user_factor()->name); // Add in a no-input factor. set_config('enabled', 1, 'factor_auth'); @@ -61,6 +73,6 @@ class plugininfo_factor_test extends advanced_testcase { // Check that the next factor is still the fallback factor. $this->assertEquals(2, count(\tool_mfa\plugininfo\factor::get_active_user_factor_types())); - $this->assertEquals(\tool_mfa\plugininfo\factor::get_next_user_factor()->name, 'fallback'); + $this->assertEquals('fallback', \tool_mfa\plugininfo\factor::get_next_user_factor()->name); } } diff --git a/admin/tool/mfa/tests/secret_manager_test.php b/admin/tool/mfa/tests/secret_manager_test.php index 6ef09c92292..5ad87b21db0 100644 --- a/admin/tool/mfa/tests/secret_manager_test.php +++ b/admin/tool/mfa/tests/secret_manager_test.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace tool_mfa\tests; +namespace tool_mfa; /** * Tests for MFA secret manager class. @@ -26,6 +26,11 @@ namespace tool_mfa\tests; */ class secret_manager_test extends \advanced_testcase { + /** + * Tests create factor's secret + * + * @covers ::create_secret + */ public function test_create_secret() { global $DB; @@ -75,6 +80,12 @@ class secret_manager_test extends \advanced_testcase { $DB->delete_records('tool_mfa_secrets', []); } + /** + * Tests add factor's secret to database + * + * @covers ::get_record + * @covers ::delete_records + */ public function test_add_secret_to_db() { global $DB, $USER; @@ -107,6 +118,12 @@ class secret_manager_test extends \advanced_testcase { $this->assertEquals($sid, $record->sessionid); } + /** + * Tests validating factor's secret + * + * @covers ::validate_secret + * @covers ::create_secret + */ public function test_validate_secret() { global $DB; @@ -161,6 +178,13 @@ class secret_manager_test extends \advanced_testcase { $DB->delete_records('tool_mfa_secrets', []); } + /** + * Tests revoking factor's secret + * + * @covers ::validate_secret + * @covers ::create_secret + * @covers ::revoke_secret + */ public function test_revoke_secret() { global $DB, $SESSION; @@ -186,6 +210,12 @@ class secret_manager_test extends \advanced_testcase { $this->assertEquals(\tool_mfa\local\secret_manager::NONVALID, $secman->validate_secret('nonvalid')); } + /** + * Tests checking if factor has an active secret + * + * @covers ::create_secret + * @covers ::revoke_secret + */ public function test_has_active_secret() { global $DB; diff --git a/admin/tool/mfa/tests/tool_mfa_testcase.php b/admin/tool/mfa/tests/tool_mfa_trait.php similarity index 87% rename from admin/tool/mfa/tests/tool_mfa_testcase.php rename to admin/tool/mfa/tests/tool_mfa_trait.php index 5d15a5a9908..0d24de4a739 100644 --- a/admin/tool/mfa/tests/tool_mfa_testcase.php +++ b/admin/tool/mfa/tests/tool_mfa_trait.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace tool_mfa\tests; +namespace tool_mfa; defined('MOODLE_INTERNAL') || die(); @@ -23,7 +23,7 @@ require_once($CFG->libdir.'/adminlib.php'); require_once(__DIR__ . '/../lib.php'); /** - * Base testcase class for testing this plugin + * Trait for testing this plugin * * @package tool_mfa * @author Mikhail Golenkov @@ -31,7 +31,7 @@ require_once(__DIR__ . '/../lib.php'); * @copyright Catalyst IT * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -abstract class tool_mfa_testcase extends \advanced_testcase { +trait tool_mfa_trait { /** * Sets the state of the factor, in particular the weight and whether it is enabled @@ -40,7 +40,7 @@ abstract class tool_mfa_testcase extends \advanced_testcase { * @param int $enabled * @param int $weight */ - protected function set_factor_state($factorname, $enabled = 0, $weight = 100) { + public function set_factor_state($factorname, $enabled = 0, $weight = 100) { $factor = \tool_mfa\plugininfo\factor::get_factor($factorname); $this->set_factor_config($factor, 'enabled', $enabled); $this->set_factor_config($factor, 'weight', $weight); @@ -53,7 +53,7 @@ abstract class tool_mfa_testcase extends \advanced_testcase { * @param string $key * @param mixed $value */ - protected function set_factor_config($factor, $key, $value) { + public function set_factor_config($factor, $key, $value) { \tool_mfa\manager::set_factor_config([$key => $value], 'factor_' . $factor->name); if ($key == 'enabled') { diff --git a/admin/tool/mfa/version.php b/admin/tool/mfa/version.php index 69e6ad8649a..e327895a96b 100644 --- a/admin/tool/mfa/version.php +++ b/admin/tool/mfa/version.php @@ -25,9 +25,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2023031600; // The current plugin version (Date: YYYYMMDDXX). -$plugin->release = 2023031600; // Same as version. -$plugin->requires = 2017051500.00; // Support back to 3.3 - Totara 12. Patches required. +$plugin->version = 2023080300; // The current plugin version (Date: YYYYMMDDXX). +$plugin->release = 2023080100; // Same as version. +$plugin->requires = 2023042400.00; // Supports from 4.2. $plugin->component = 'tool_mfa'; $plugin->maturity = MATURITY_STABLE; -$plugin->supported = [35, 401]; diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php index cf49ea22f1f..0a13f414564 100644 --- a/lib/classes/plugin_manager.php +++ b/lib/classes/plugin_manager.php @@ -2050,7 +2050,7 @@ class core_plugin_manager { 'installaddon', 'langimport', 'licensemanager', 'log', 'lp', 'lpimportcsv', 'lpmigrate', 'messageinbound', 'mobile', 'moodlenet', 'multilangupgrade', 'monitor', 'oauth2', 'phpunit', 'policy', 'profiling', 'recyclebin', 'replace', 'spamcleaner', 'task', 'templatelibrary', 'uploadcourse', 'uploaduser', 'unsuproles', - 'usertours', 'xmldb' + 'usertours', 'xmldb', 'mfa' ), 'webservice' => array(