first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
Description of phpCAS import into Moodle
Last release can be found at https://github.com/apereo/phpCAS/releases
NOTICE:
* Before running composer command, make sure you have the composer version updated.
* Composer version 2.2.4 2022-01-08 12:30:42
STEPS:
* Make sure you're using the lowest supported PHP version for the given release (e.g. PHP 7.4 for Moodle 4.1)
* Create a temporary folder outside your Moodle installation
* Execute 'composer require apereo/phpcas:VERSION'
* Check any new libraries that have been added and make sure they do not exist in Moodle already.
* Remove the old 'vendor' directory in auth/cas/CAS/
* Copy contents of 'vendor' directory
* Create a commit with only the library changes.
- Note: Make sure to check the list of unversioned files and add any new files to the staging area.
* Update auth/cas/thirdpartylibs.xml
* Apply the modifications described in the CHANGES section
* Create another commit with the previous two steps of changes
CHANGES:
* Remove all the hidden folders and files in vendor/apereo/phpcas/ (find . -name ".*"):
- .codecov.yml
- .gitattributes
- .github
+8
View File
@@ -0,0 +1,8 @@
CAS-module README
+400
View File
@@ -0,0 +1,400 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Authentication Plugin: CAS Authentication
*
* Authentication using CAS (Central Authentication Server).
*
* @author Martin Dougiamas
* @author Jerome GUTIERREZ
* @author Iñaki Arenaza
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package auth_cas
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/auth/ldap/auth.php');
require_once($CFG->dirroot.'/auth/cas/CAS/vendor/autoload.php');
require_once($CFG->dirroot.'/auth/cas/CAS/vendor/apereo/phpcas/source/CAS.php');
/**
* CAS authentication plugin.
*/
class auth_plugin_cas extends auth_plugin_ldap {
/**
* Constructor.
*/
public function __construct() {
$this->authtype = 'cas';
$this->roleauth = 'auth_cas';
$this->errorlogtag = '[AUTH CAS] ';
$this->init_plugin($this->authtype);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function auth_plugin_cas() {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct();
}
function prevent_local_passwords() {
return true;
}
/**
* Authenticates user against CAS
* Returns true if the username and password work and false if they are
* wrong or don't exist.
*
* @param string $username The username (with system magic quotes)
* @param string $password The password (with system magic quotes)
* @return bool Authentication success or failure.
*/
function user_login($username, $password) {
$this->connectCAS();
return phpCAS::isAuthenticated() && (trim(core_text::strtolower(phpCAS::getUser())) == $username);
}
/**
* Returns true if this authentication plugin is 'internal'.
*
* @return bool
*/
function is_internal() {
return false;
}
/**
* Returns true if this authentication plugin can change the user's
* password.
*
* @return bool
*/
function can_change_password() {
return false;
}
/**
* Authentication choice (CAS or other)
* Redirection to the CAS form or to login/index.php
* for other authentication
*/
function loginpage_hook() {
global $frm;
global $CFG;
global $SESSION, $OUTPUT, $PAGE;
$site = get_site();
$CASform = get_string('CASform', 'auth_cas');
$username = optional_param('username', '', PARAM_RAW);
$courseid = optional_param('courseid', 0, PARAM_INT);
if (!empty($username)) {
if (isset($SESSION->wantsurl) && (strstr($SESSION->wantsurl, 'ticket') ||
strstr($SESSION->wantsurl, 'NOCAS'))) {
unset($SESSION->wantsurl);
}
return;
}
// Return if CAS enabled and settings not specified yet
if (empty($this->config->hostname)) {
return;
}
// If the multi-authentication setting is used, check for the param before connecting to CAS.
if ($this->config->multiauth) {
// If there is an authentication error, stay on the default authentication page.
if (!empty($SESSION->loginerrormsg)) {
return;
}
$authCAS = optional_param('authCAS', '', PARAM_RAW);
if ($authCAS != 'CAS') {
return;
}
}
// Connection to CAS server
$this->connectCAS();
if (phpCAS::checkAuthentication()) {
$frm = new stdClass();
$frm->username = phpCAS::getUser();
$frm->password = 'passwdCas';
$frm->logintoken = \core\session\manager::get_login_token();
// Redirect to a course if multi-auth is activated, authCAS is set to CAS and the courseid is specified.
if ($this->config->multiauth && !empty($courseid)) {
redirect(new moodle_url('/course/view.php', array('id'=>$courseid)));
}
return;
}
if (isset($_GET['loginguest']) && ($_GET['loginguest'] == true)) {
$frm = new stdClass();
$frm->username = 'guest';
$frm->password = 'guest';
$frm->logintoken = \core\session\manager::get_login_token();
return;
}
// Force CAS authentication (if needed).
if (!phpCAS::isAuthenticated()) {
phpCAS::setLang($this->config->language);
phpCAS::forceAuthentication();
}
}
/**
* Connect to the CAS (clientcas connection or proxycas connection)
*
*/
function connectCAS() {
global $CFG;
static $connected = false;
if (!$connected) {
// Form the base URL of the server with just the protocol and hostname.
$serverurl = new moodle_url("/");
$servicebaseurl = $serverurl->get_scheme() ? $serverurl->get_scheme() . "://" : '';
$servicebaseurl .= $serverurl->get_host();
// Add the port if set.
$servicebaseurl .= $serverurl->get_port() ? ':' . $serverurl->get_port() : '';
// Make sure phpCAS doesn't try to start a new PHP session when connecting to the CAS server.
if ($this->config->proxycas) {
phpCAS::proxy($this->config->casversion, $this->config->hostname, (int) $this->config->port, $this->config->baseuri,
$servicebaseurl, false);
} else {
phpCAS::client($this->config->casversion, $this->config->hostname, (int) $this->config->port,
$this->config->baseuri, $servicebaseurl, false);
}
// Some CAS installs require SSLv3 that should be explicitly set.
if (!empty($this->config->curl_ssl_version)) {
phpCAS::setExtraCurlOption(CURLOPT_SSLVERSION, $this->config->curl_ssl_version);
}
$connected = true;
}
// If Moodle is configured to use a proxy, phpCAS needs some curl options set.
if (!empty($CFG->proxyhost) && !is_proxybypass(phpCAS::getServerLoginURL())) {
phpCAS::setExtraCurlOption(CURLOPT_PROXY, $CFG->proxyhost);
if (!empty($CFG->proxyport)) {
phpCAS::setExtraCurlOption(CURLOPT_PROXYPORT, $CFG->proxyport);
}
if (!empty($CFG->proxytype)) {
// Only set CURLOPT_PROXYTYPE if it's something other than the curl-default http
if ($CFG->proxytype == 'SOCKS5') {
phpCAS::setExtraCurlOption(CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
}
}
if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
phpCAS::setExtraCurlOption(CURLOPT_PROXYUSERPWD, $CFG->proxyuser.':'.$CFG->proxypassword);
if (defined('CURLOPT_PROXYAUTH')) {
// any proxy authentication if PHP 5.1
phpCAS::setExtraCurlOption(CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
}
}
}
if ($this->config->certificate_check && $this->config->certificate_path){
phpCAS::setCasServerCACert($this->config->certificate_path);
} else {
// Don't try to validate the server SSL credentials
phpCAS::setNoCasServerValidation();
}
}
/**
* Returns the URL for changing the user's pw, or empty if the default can
* be used.
*
* @return moodle_url
*/
function change_password_url() {
return null;
}
/**
* Returns true if user should be coursecreator.
*
* @param mixed $username username (without system magic quotes)
* @return boolean result
*/
function iscreator($username) {
if (empty($this->config->host_url) or (empty($this->config->attrcreators) && empty($this->config->groupecreators)) or empty($this->config->memberattribute)) {
return false;
}
$extusername = core_text::convert($username, 'utf-8', $this->config->ldapencoding);
// Test for group creator
if (!empty($this->config->groupecreators)) {
$ldapconnection = $this->ldap_connect();
if ($this->config->memberattribute_isdn) {
if(!($userid = $this->ldap_find_userdn($ldapconnection, $extusername))) {
return false;
}
} else {
$userid = $extusername;
}
$group_dns = explode(';', $this->config->groupecreators);
if (ldap_isgroupmember($ldapconnection, $userid, $group_dns, $this->config->memberattribute)) {
return true;
}
}
// Build filter for attrcreator
if (!empty($this->config->attrcreators)) {
$attrs = explode(';', $this->config->attrcreators);
$filter = '(& ('.$this->config->user_attribute."=$username)(|";
foreach ($attrs as $attr){
if(strpos($attr, '=')) {
$filter .= "($attr)";
} else {
$filter .= '('.$this->config->memberattribute."=$attr)";
}
}
$filter .= '))';
// Search
$result = $this->ldap_get_userlist($filter);
if (count($result) != 0) {
return true;
}
}
return false;
}
/**
* Reads user information from LDAP and returns it as array()
*
* If no LDAP servers are configured, user information has to be
* provided via other methods (CSV file, manually, etc.). Return
* an empty array so existing user info is not lost. Otherwise,
* calls parent class method to get user info.
*
* @param string $username username
* @return mixed array with no magic quotes or false on error
*/
function get_userinfo($username) {
if (empty($this->config->host_url)) {
return array();
}
return parent::get_userinfo($username);
}
/**
* Syncronizes users from LDAP server to moodle user table.
*
* If no LDAP servers are configured, simply return. Otherwise,
* call parent class method to do the work.
*
* @param bool $do_updates will do pull in data updates from LDAP if relevant
* @return nothing
*/
function sync_users($do_updates=true) {
if (empty($this->config->host_url)) {
error_log('[AUTH CAS] '.get_string('noldapserver', 'auth_cas'));
return;
}
parent::sync_users($do_updates);
}
/**
* Hook for logout page
*/
function logoutpage_hook() {
global $USER, $redirect;
// Only do this if the user is actually logged in via CAS
if ($USER->auth === $this->authtype) {
// Check if there is an alternative logout return url defined
if (isset($this->config->logout_return_url) && !empty($this->config->logout_return_url)) {
// Set redirect to alternative return url
$redirect = $this->config->logout_return_url;
}
}
}
/**
* Post logout hook.
*
* Note: this method replace the prelogout_hook method to avoid redirect to CAS logout
* before the event userlogout being triggered.
*
* @param stdClass $user clone of USER object object before the user session was terminated
*/
public function postlogout_hook($user) {
global $CFG;
// Only redirect to CAS logout if the user is logged as a CAS user.
if (!empty($this->config->logoutcas) && $user->auth == $this->authtype) {
$backurl = !empty($this->config->logout_return_url) ? $this->config->logout_return_url : $CFG->wwwroot;
$this->connectCAS();
phpCAS::logoutWithRedirectService($backurl);
}
}
/**
* Return a list of identity providers to display on the login page.
*
* @param string|moodle_url $wantsurl The requested URL.
* @return array List of arrays with keys url, iconurl and name.
*/
public function loginpage_idp_list($wantsurl) {
if (empty($this->config->hostname)) {
// CAS is not configured.
return [];
}
if ($this->config->auth_logo) {
$iconurl = moodle_url::make_pluginfile_url(
context_system::instance()->id,
'auth_cas',
'logo',
null,
null,
$this->config->auth_logo);
} else {
$iconurl = null;
}
return [
[
'url' => new moodle_url(get_login_url(), [
'authCAS' => 'CAS',
]),
'iconurl' => $iconurl,
'name' => format_string($this->config->auth_name),
],
];
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy Subsystem implementation for auth_cas.
*
* @package auth_cas
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_cas\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for auth_cas implementing null_provider.
*
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A scheduled task for CAS user sync.
*
* @package auth_cas
* @copyright 2015 Vadim Dvorovenko <Vadimon@mail.ru>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_cas\task;
/**
* A scheduled task class for CAS user sync.
*
* @copyright 2015 Vadim Dvorovenko <Vadimon@mail.ru>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sync_task extends \core\task\scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('synctask', 'auth_cas');
}
/**
* Run users sync.
*/
public function execute() {
global $CFG;
if (is_enabled_auth('cas')) {
$auth = get_auth_plugin('cas');
$auth->sync_users(true);
}
}
}
+67
View File
@@ -0,0 +1,67 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* CAS user sync script.
*
* This script is meant to be called from a cronjob to sync moodle with the CAS
* backend in those setups where the CAS backend acts as 'master'.
*
* Notes:
* - it is required to use the web server account when executing PHP CLI scripts
* - you need to change the "www-data" to match the apache user account
* - use "su" if "sudo" not available
* - If you have a large number of users, you may want to raise the memory limits
* by passing -d momory_limit=256M
* - For debugging & better logging, you are encouraged to use in the command line:
* -d log_errors=1 -d error_reporting=E_ALL -d display_errors=0 -d html_errors=0
*
* Performance notes:
* We have optimized it as best as we could for PostgreSQL and MySQL, with 27K students
* we have seen this take 10 minutes.
*
* @package auth_cas
* @copyright 2007 Jerome Gutierrez - based on code by Martin Langhoff
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 3.0 MDL-51824 - please do not use this CLI script any more, use scheduled task instead.
* @todo MDL-50264 This will be deleted in Moodle 3.2.
*/
define('CLI_SCRIPT', true);
require(__DIR__.'/../../../config.php');
require_once($CFG->dirroot.'/course/lib.php');
require_once($CFG->libdir.'/clilib.php');
// Ensure errors are well explained
set_debugging(DEBUG_DEVELOPER, true);
if (!is_enabled_auth('cas')) {
error_log('[AUTH CAS] '.get_string('pluginnotenabled', 'auth_ldap'));
die;
}
cli_problem('[AUTH CAS] The sync users cron has been deprecated. Please use the scheduled task instead.');
// Abort execution of the CLI script if the auth_cas\task\sync_task is enabled.
$task = \core\task\manager::get_scheduled_task('auth_cas\task\sync_task');
if (!$task->get_disabled()) {
cli_error('[AUTH CAS] The scheduled task sync_task is enabled, the cron execution has been aborted.');
}
$casauth = get_auth_plugin('cas');
$casauth->sync_users(true);
+6
View File
@@ -0,0 +1,6 @@
<?php
function xmldb_auth_cas_install() {
global $CFG, $DB;
}
+39
View File
@@ -0,0 +1,39 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Definition of auth_cas tasks.
*
* @package auth_cas
* @category task
* @copyright 2015 Vadim Dvorovenko <Vadimon@mail.ru>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$tasks = array(
array(
'classname' => 'auth_cas\task\sync_task',
'blocking' => 0,
'minute' => '0',
'hour' => '0',
'day' => '*',
'month' => '*',
'dayofweek' => '*',
'disabled' => 1
)
);
+44
View File
@@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* CAS authentication plugin upgrade code
*
* @package auth_cas
* @copyright 2013 Iñaki Arenaza
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Function to upgrade auth_cas.
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_auth_cas_upgrade($oldversion) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+80
View File
@@ -0,0 +1,80 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'auth_cas', language 'en'.
*
* @package auth_cas
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['auth_cas_auth_name'] = 'Authentication method name';
$string['auth_cas_auth_name_description'] = 'Provide a name for the CAS authentication method that is familiar to your users.';
$string['auth_cas_auth_logo'] = 'Authentication method logo';
$string['auth_cas_auth_logo_description'] = 'Provide a logo for the CAS authentication method that is familiar to your users.';
$string['auth_cas_auth_user_create'] = 'Create users externally';
$string['auth_cas_auth_service'] = 'CAS';
$string['auth_cas_baseuri'] = 'URI of the server (nothing if no baseUri)<br />For example, if the CAS server responds to host.domaine.fr/CAS/ then<br />cas_baseuri = CAS/';
$string['auth_cas_baseuri_key'] = 'Base URI';
$string['auth_cas_broken_password'] = 'You cannot proceed without changing your password, however there is no available page for changing it. Please contact your Moodle Administrator.';
$string['auth_cas_cantconnect'] = 'LDAP part of CAS-module cannot connect to server: {$a}';
$string['auth_cas_casversion'] = 'CAS protocol version';
$string['auth_cas_certificate_check'] = 'Select \'yes\' if you want to validate the server certificate';
$string['auth_cas_certificate_path_empty'] = 'If you turn on Server validation, you need to specify a certificate path';
$string['auth_cas_certificate_check_key'] = 'Server validation';
$string['auth_cas_certificate_path'] = 'Path of the CA chain file (PEM Format) to validate the server certificate';
$string['auth_cas_certificate_path_key'] = 'Certificate path';
$string['auth_cas_create_user'] = 'Turn this on if you want to insert CAS-authenticated users in Moodle database. If not then only users who already exist in the Moodle database can log in.';
$string['auth_cas_create_user_key'] = 'Create user';
$string['auth_cas_curl_ssl_version'] = 'The SSL version (2 or 3) to use. By default PHP will try to determine this itself, although in some cases this must be set manually.';
$string['auth_cas_curl_ssl_version_default'] = 'Default';
$string['auth_cas_curl_ssl_version_key'] = 'cURL SSL Version';
$string['auth_cas_curl_ssl_version_SSLv2'] = 'SSLv2';
$string['auth_cas_curl_ssl_version_SSLv3'] = 'SSLv3';
$string['auth_cas_curl_ssl_version_TLSv1x'] = 'TLSv1.x';
$string['auth_cas_curl_ssl_version_TLSv10'] = 'TLSv1.0';
$string['auth_cas_curl_ssl_version_TLSv11'] = 'TLSv1.1';
$string['auth_cas_curl_ssl_version_TLSv12'] = 'TLSv1.2';
$string['auth_casdescription'] = 'This method uses a CAS server (Central Authentication Service) to authenticate users in a Single Sign On environment (SSO). You can also use a simple LDAP authentication. If the given username and password are valid according to CAS, Moodle creates a new user entry in its database, taking user attributes from LDAP if required. On following logins only the username and password are checked.';
$string['auth_cas_enabled'] = 'Turn this on if you want to use CAS authentication.';
$string['auth_cas_hostname'] = 'Hostname of the CAS server <br />eg: host.domain.fr';
$string['auth_cas_hostname_key'] = 'Hostname';
$string['auth_cas_changepasswordurl'] = 'Password-change URL';
$string['auth_cas_invalidcaslogin'] = 'Sorry, your login has failed - you could not be authorised';
$string['auth_cas_language'] = 'Select language for authentication pages';
$string['auth_cas_language_key'] = 'Language';
$string['auth_cas_logincas'] = 'Secure connection access';
$string['auth_cas_logout_return_url_key'] = 'Alternative logout return URL';
$string['auth_cas_logout_return_url'] = 'Provide the URL that CAS users shall be redirected to after logging out.<br />If left empty, users will be redirected to the location that moodle will redirect users to';
$string['auth_cas_logoutcas'] = 'Select \'yes\' if you want to logout from CAS when you disconnect from Moodle';
$string['auth_cas_logoutcas_key'] = 'CAS logout option';
$string['auth_cas_multiauth'] = 'Select \'yes\' if you want to have multi-authentication (CAS + other authentication)';
$string['auth_cas_multiauth_key'] = 'Multi-authentication';
$string['auth_casnotinstalled'] = 'Cannot use CAS authentication. The PHP LDAP module is not installed.';
$string['auth_cas_port'] = 'Port of the CAS server';
$string['auth_cas_port_key'] = 'Port';
$string['auth_cas_proxycas'] = 'Select \'yes\' if you use CAS in proxy-mode';
$string['auth_cas_proxycas_key'] = 'Proxy mode';
$string['auth_cas_server_settings'] = 'CAS server configuration';
$string['auth_cas_text'] = 'Secure connection';
$string['auth_cas_use_cas'] = 'Use CAS';
$string['auth_cas_version'] = 'CAS protocol version to use';
$string['CASform'] = 'Authentication choice';
$string['noldapserver'] = 'No LDAP server configured for CAS! Syncing disabled.';
$string['pluginname'] = 'CAS server (SSO)';
$string['synctask'] = 'CAS users sync job';
$string['privacy:metadata'] = 'The CAS server (SSO) authentication plugin does not store any personal data.';
+16
View File
@@ -0,0 +1,16 @@
<?php
$caslangconstprefix = 'PHPCAS_LANG_';
$caslangprefixlen = strlen('CAS_Languages_');
$CASLANGUAGES = array ();
$consts = get_defined_constants(true);
foreach ($consts['user'] as $key => $value) {
if (preg_match("/^$caslangconstprefix/", $key)) {
$CASLANGUAGES[$value] = substr($value, $caslangprefixlen);
}
}
if (empty($CASLANGUAGES)) {
$CASLANGUAGES = array (PHPCAS_LANG_ENGLISH => 'English',
PHPCAS_LANG_FRENCH => 'French');
}
+67
View File
@@ -0,0 +1,67 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Authentication Plugin: CAS Authentication.
*
* Authentication using CAS (Central Authentication Server).
*
* @package auth_cas
* @copyright 2018 Fabrice Ménard <menard.fabrice@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
/**
* Serves the logo file settings.
*
* @param stdClass $course course object
* @param stdClass $cm course module object
* @param stdClass $context context object
* @param string $filearea file area
* @param array $args extra arguments
* @param bool $forcedownload whether or not force download
* @param array $options additional options affecting the file serving
* @return bool false|void
*/
function auth_cas_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = []) {
if ($context->contextlevel != CONTEXT_SYSTEM) {
return false;
}
if ($filearea !== 'logo' ) {
return false;
}
// Extract the filename / filepath from the $args array.
$filename = array_pop($args);
if (!$args) {
$filepath = '/';
} else {
$filepath = '/' . implode('/', $args) . '/';
}
// Retrieve the file from the Files API.
$itemid = 0;
$fs = get_file_storage();
$file = $fs->get_file($context->id, 'auth_cas', $filearea, $itemid, $filepath, $filename);
if (!$file) {
return false; // The file does not exist.
}
send_stored_file($file, null, 0, $forcedownload, $options);
}
+293
View File
@@ -0,0 +1,293 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Admin settings and defaults.
*
* @package auth_cas
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
if (!function_exists('ldap_connect')) {
$notify = new \core\output\notification(get_string('auth_casnotinstalled', 'auth_cas'),
\core\output\notification::NOTIFY_WARNING);
$settings->add(new admin_setting_heading('auth_casnotinstalled', '', $OUTPUT->render($notify)));
} else {
// We use a couple of custom admin settings since we need to massage the data before it is inserted into the DB.
require_once($CFG->dirroot.'/auth/ldap/classes/admin_setting_special_lowercase_configtext.php');
require_once($CFG->dirroot.'/auth/ldap/classes/admin_setting_special_contexts_configtext.php');
// Include needed files.
require_once($CFG->dirroot.'/auth/cas/auth.php');
require_once($CFG->dirroot.'/auth/cas/languages.php');
// Introductory explanation.
$settings->add(new admin_setting_heading('auth_cas/pluginname', '',
new lang_string('auth_casdescription', 'auth_cas')));
// CAS server configuration label.
$settings->add(new admin_setting_heading('auth_cas/casserversettings',
new lang_string('auth_cas_server_settings', 'auth_cas'), ''));
// Authentication method name.
$settings->add(new admin_setting_configtext('auth_cas/auth_name',
get_string('auth_cas_auth_name', 'auth_cas'),
get_string('auth_cas_auth_name_description', 'auth_cas'),
get_string('auth_cas_auth_service', 'auth_cas'),
PARAM_RAW_TRIMMED));
// Authentication method logo.
$opts = array('accepted_types' => array('.png', '.jpg', '.gif', '.webp', '.tiff', '.svg'));
$settings->add(new admin_setting_configstoredfile('auth_cas/auth_logo',
get_string('auth_cas_auth_logo', 'auth_cas'),
get_string('auth_cas_auth_logo_description', 'auth_cas'), 'logo', 0, $opts));
// Hostname.
$settings->add(new admin_setting_configtext('auth_cas/hostname',
get_string('auth_cas_hostname_key', 'auth_cas'),
get_string('auth_cas_hostname', 'auth_cas'), '', PARAM_RAW_TRIMMED));
// Base URI.
$settings->add(new admin_setting_configtext('auth_cas/baseuri',
get_string('auth_cas_baseuri_key', 'auth_cas'),
get_string('auth_cas_baseuri', 'auth_cas'), '', PARAM_RAW_TRIMMED));
// Port.
$settings->add(new admin_setting_configtext('auth_cas/port',
get_string('auth_cas_port_key', 'auth_cas'),
get_string('auth_cas_port', 'auth_cas'), '', PARAM_INT));
// CAS Version.
$casversions = array();
$casversions[CAS_VERSION_1_0] = 'CAS 1.0';
$casversions[CAS_VERSION_2_0] = 'CAS 2.0';
$settings->add(new admin_setting_configselect('auth_cas/casversion',
new lang_string('auth_cas_casversion', 'auth_cas'),
new lang_string('auth_cas_version', 'auth_cas'), CAS_VERSION_2_0, $casversions));
// Language.
if (!isset($CASLANGUAGES) || empty($CASLANGUAGES)) {
// Prevent warnings on other admin pages.
// $CASLANGUAGES is defined in /auth/cas/languages.php.
$CASLANGUAGES = array();
$CASLANGUAGES[PHPCAS_LANG_ENGLISH] = 'English';
$CASLANGUAGES[PHPCAS_LANG_FRENCH] = 'French';
}
$settings->add(new admin_setting_configselect('auth_cas/language',
new lang_string('auth_cas_language_key', 'auth_cas'),
new lang_string('auth_cas_language', 'auth_cas'), PHPCAS_LANG_ENGLISH, $CASLANGUAGES));
// Proxy.
$yesno = array(
new lang_string('no'),
new lang_string('yes'),
);
$settings->add(new admin_setting_configselect('auth_cas/proxycas',
new lang_string('auth_cas_proxycas_key', 'auth_cas'),
new lang_string('auth_cas_proxycas', 'auth_cas'), 0 , $yesno));
// Logout option.
$settings->add(new admin_setting_configselect('auth_cas/logoutcas',
new lang_string('auth_cas_logoutcas_key', 'auth_cas'),
new lang_string('auth_cas_logoutcas', 'auth_cas'), 0 , $yesno));
// Multi-auth.
$settings->add(new admin_setting_configselect('auth_cas/multiauth',
new lang_string('auth_cas_multiauth_key', 'auth_cas'),
new lang_string('auth_cas_multiauth', 'auth_cas'), 0 , $yesno));
// Server validation.
$settings->add(new admin_setting_configselect('auth_cas/certificate_check',
new lang_string('auth_cas_certificate_check_key', 'auth_cas'),
new lang_string('auth_cas_certificate_check', 'auth_cas'), 0 , $yesno));
// Certificate path.
$settings->add(new admin_setting_configfile('auth_cas/certificate_path',
get_string('auth_cas_certificate_path_key', 'auth_cas'),
get_string('auth_cas_certificate_path', 'auth_cas'), ''));
// CURL SSL version.
$sslversions = array();
$sslversions[''] = get_string('auth_cas_curl_ssl_version_default', 'auth_cas');
if (defined('CURL_SSLVERSION_TLSv1')) {
$sslversions[CURL_SSLVERSION_TLSv1] = get_string('auth_cas_curl_ssl_version_TLSv1x', 'auth_cas');
}
if (defined('CURL_SSLVERSION_TLSv1_0')) {
$sslversions[CURL_SSLVERSION_TLSv1_0] = get_string('auth_cas_curl_ssl_version_TLSv10', 'auth_cas');
}
if (defined('CURL_SSLVERSION_TLSv1_1')) {
$sslversions[CURL_SSLVERSION_TLSv1_1] = get_string('auth_cas_curl_ssl_version_TLSv11', 'auth_cas');
}
if (defined('CURL_SSLVERSION_TLSv1_2')) {
$sslversions[CURL_SSLVERSION_TLSv1_2] = get_string('auth_cas_curl_ssl_version_TLSv12', 'auth_cas');
}
if (defined('CURL_SSLVERSION_SSLv2')) {
$sslversions[CURL_SSLVERSION_SSLv2] = get_string('auth_cas_curl_ssl_version_SSLv2', 'auth_cas');
}
if (defined('CURL_SSLVERSION_SSLv3')) {
$sslversions[CURL_SSLVERSION_SSLv3] = get_string('auth_cas_curl_ssl_version_SSLv3', 'auth_cas');
}
$settings->add(new admin_setting_configselect('auth_cas/curl_ssl_version',
new lang_string('auth_cas_curl_ssl_version_key', 'auth_cas'),
new lang_string('auth_cas_curl_ssl_version', 'auth_cas'), '' , $sslversions));
// Alt Logout URL.
$settings->add(new admin_setting_configtext('auth_cas/logout_return_url',
get_string('auth_cas_logout_return_url_key', 'auth_cas'),
get_string('auth_cas_logout_return_url', 'auth_cas'), '', PARAM_URL));
// LDAP server settings.
$settings->add(new admin_setting_heading('auth_cas/ldapserversettings',
new lang_string('auth_ldap_server_settings', 'auth_ldap'), ''));
// Host.
$settings->add(new admin_setting_configtext('auth_cas/host_url',
get_string('auth_ldap_host_url_key', 'auth_ldap'),
get_string('auth_ldap_host_url', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Version.
$versions = array();
$versions[2] = '2';
$versions[3] = '3';
$settings->add(new admin_setting_configselect('auth_cas/ldap_version',
new lang_string('auth_ldap_version_key', 'auth_ldap'),
new lang_string('auth_ldap_version', 'auth_ldap'), 3, $versions));
// Start TLS.
$settings->add(new admin_setting_configselect('auth_cas/start_tls',
new lang_string('start_tls_key', 'auth_ldap'),
new lang_string('start_tls', 'auth_ldap'), 0 , $yesno));
// Encoding.
$settings->add(new admin_setting_configtext('auth_cas/ldapencoding',
get_string('auth_ldap_ldap_encoding_key', 'auth_ldap'),
get_string('auth_ldap_ldap_encoding', 'auth_ldap'), 'utf-8', PARAM_RAW_TRIMMED));
// Page Size. (Hide if not available).
$settings->add(new admin_setting_configtext('auth_cas/pagesize',
get_string('pagesize_key', 'auth_ldap'),
get_string('pagesize', 'auth_ldap'), '250', PARAM_INT));
// Bind settings.
$settings->add(new admin_setting_heading('auth_cas/ldapbindsettings',
new lang_string('auth_ldap_bind_settings', 'auth_ldap'), ''));
// User ID.
$settings->add(new admin_setting_configtext('auth_cas/bind_dn',
get_string('auth_ldap_bind_dn_key', 'auth_ldap'),
get_string('auth_ldap_bind_dn', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Password.
$settings->add(new admin_setting_configpasswordunmask('auth_cas/bind_pw',
get_string('auth_ldap_bind_pw_key', 'auth_ldap'),
get_string('auth_ldap_bind_pw', 'auth_ldap'), ''));
// User Lookup settings.
$settings->add(new admin_setting_heading('auth_cas/ldapuserlookup',
new lang_string('auth_ldap_user_settings', 'auth_ldap'), ''));
// User Type.
$settings->add(new admin_setting_configselect('auth_cas/user_type',
new lang_string('auth_ldap_user_type_key', 'auth_ldap'),
new lang_string('auth_ldap_user_type', 'auth_ldap'), 'default', ldap_supported_usertypes()));
// Contexts.
$settings->add(new auth_ldap_admin_setting_special_contexts_configtext('auth_cas/contexts',
get_string('auth_ldap_contexts_key', 'auth_ldap'),
get_string('auth_ldap_contexts', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Search subcontexts.
$settings->add(new admin_setting_configselect('auth_cas/search_sub',
new lang_string('auth_ldap_search_sub_key', 'auth_ldap'),
new lang_string('auth_ldap_search_sub', 'auth_ldap'), 0 , $yesno));
// Dereference aliases.
$optderef = array();
$optderef[LDAP_DEREF_NEVER] = get_string('no');
$optderef[LDAP_DEREF_ALWAYS] = get_string('yes');
$settings->add(new admin_setting_configselect('auth_cas/opt_deref',
new lang_string('auth_ldap_opt_deref_key', 'auth_ldap'),
new lang_string('auth_ldap_opt_deref', 'auth_ldap'), LDAP_DEREF_NEVER , $optderef));
// User attribute.
$settings->add(new auth_ldap_admin_setting_special_lowercase_configtext('auth_cas/user_attribute',
get_string('auth_ldap_user_attribute_key', 'auth_ldap'),
get_string('auth_ldap_user_attribute', 'auth_ldap'), '', PARAM_RAW));
// Member attribute.
$settings->add(new auth_ldap_admin_setting_special_lowercase_configtext('auth_cas/memberattribute',
get_string('auth_ldap_memberattribute_key', 'auth_ldap'),
get_string('auth_ldap_memberattribute', 'auth_ldap'), '', PARAM_RAW));
// Member attribute uses dn.
$settings->add(new admin_setting_configselect('auth_cas/memberattribute_isdn',
get_string('auth_ldap_memberattribute_isdn_key', 'auth_ldap'),
get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), 0, $yesno));
// Object class.
$settings->add(new admin_setting_configtext('auth_cas/objectclass',
get_string('auth_ldap_objectclass_key', 'auth_ldap'),
get_string('auth_ldap_objectclass', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Course Creators Header.
$settings->add(new admin_setting_heading('auth_cas/coursecreators',
new lang_string('coursecreators'), ''));
// Course creators attribute field mapping.
$settings->add(new admin_setting_configtext('auth_cas/attrcreators',
get_string('auth_ldap_attrcreators_key', 'auth_ldap'),
get_string('auth_ldap_attrcreators', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Course creator group field mapping.
$settings->add(new admin_setting_configtext('auth_cas/groupecreators',
get_string('auth_ldap_groupecreators_key', 'auth_ldap'),
get_string('auth_ldap_groupecreators', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// User Account Sync.
$settings->add(new admin_setting_heading('auth_cas/syncusers',
new lang_string('auth_sync_script', 'auth'), ''));
// Remove external user.
$deleteopt = array();
$deleteopt[AUTH_REMOVEUSER_KEEP] = get_string('auth_remove_keep', 'auth');
$deleteopt[AUTH_REMOVEUSER_SUSPEND] = get_string('auth_remove_suspend', 'auth');
$deleteopt[AUTH_REMOVEUSER_FULLDELETE] = get_string('auth_remove_delete', 'auth');
$settings->add(new admin_setting_configselect('auth_cas/removeuser',
new lang_string('auth_remove_user_key', 'auth'),
new lang_string('auth_remove_user', 'auth'), AUTH_REMOVEUSER_KEEP, $deleteopt));
}
// Display locking / mapping of profile fields.
$authplugin = get_auth_plugin('cas');
$help = get_string('auth_ldapextrafields', 'auth_ldap');
$help .= get_string('auth_updatelocal_expl', 'auth');
$help .= get_string('auth_fieldlock_expl', 'auth');
$help .= get_string('auth_updateremote_expl', 'auth');
$help .= '<hr />';
$help .= get_string('auth_updateremote_ldap', 'auth');
display_auth_lock_options($settings, $authplugin->authtype, $authplugin->userfields, $help, true, true,
$authplugin->get_custom_user_profile_fields());
}
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<libraries>
<library>
<location>CAS</location>
<name>CAS</name>
<description>phpCAS library to support CAS authentication plugin.</description>
<version>1.6.0</version>
<license>Apache</license>
<licenseversion>2.0</licenseversion>
<repository>https://github.com/apereo/phpCAS</repository>
<copyrights>
<copyright>2007-2020, Apereo Foundation</copyright>
</copyrights>
</library>
</libraries>
+7
View File
@@ -0,0 +1,7 @@
This files describes API changes in /auth/cas/*,
information provided here is intended especially for developers.
=== 3.3 ===
* The config.html file was migrated to use the admin settings API.
The identifier for configuration data stored in config_plugins table was converted from 'auth/cas' to 'auth_cas'.
+33
View File
@@ -0,0 +1,33 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Version details
*
* @package auth_cas
* @author Martin Dougiamas
* @author Jerome GUTIERREZ
* @author Iñaki Arenaza
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'auth_cas'; // Full name of the plugin (used for diagnostics)
$plugin->dependencies = ['auth_ldap' => 2024041600];
+106
View File
@@ -0,0 +1,106 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains helper class for digital consent.
*
* @package core_auth
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_auth;
defined('MOODLE_INTERNAL') || die();
/**
* Helper class for digital consent.
*
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class digital_consent {
/**
* Returns true if age and location verification is enabled in the site.
*
* @return bool
*/
public static function is_age_digital_consent_verification_enabled() {
global $CFG;
return !empty($CFG->agedigitalconsentverification);
}
/**
* Checks if a user is a digital minor.
*
* @param int $age
* @param string $country The country code (ISO 3166-2)
* @return bool
*/
public static function is_minor($age, $country) {
global $CFG;
$ageconsentmap = $CFG->agedigitalconsentmap;
$agedigitalconsentmap = self::parse_age_digital_consent_map($ageconsentmap);
return array_key_exists($country, $agedigitalconsentmap) ?
$age < $agedigitalconsentmap[$country] : $age < $agedigitalconsentmap['*'];
}
/**
* Parse the agedigitalconsentmap setting into an array.
*
* @param string $ageconsentmap The value of the agedigitalconsentmap setting
* @return array $ageconsentmapparsed
*/
public static function parse_age_digital_consent_map($ageconsentmap) {
$ageconsentmapparsed = array();
$countries = get_string_manager()->get_list_of_countries(true);
$isdefaultvaluepresent = false;
$lines = preg_split('/\r|\n/', $ageconsentmap, -1, PREG_SPLIT_NO_EMPTY);
foreach ($lines as $line) {
$arr = explode(",", $line);
// Handle if there is more or less than one comma separator.
if (count($arr) != 2) {
throw new \moodle_exception('agedigitalconsentmapinvalidcomma', 'error', '', $line);
}
$country = trim($arr[0]);
$age = trim($arr[1]);
// Check if default.
if ($country == "*") {
$isdefaultvaluepresent = true;
}
// Handle if the presented value for country is not valid.
if ($country !== "*" && !array_key_exists($country, $countries)) {
throw new \moodle_exception('agedigitalconsentmapinvalidcountry', 'error', '', $country);
}
// Handle if the presented value for age is not valid.
if (!is_numeric($age)) {
throw new \moodle_exception('agedigitalconsentmapinvalidage', 'error', '', $age);
}
$ageconsentmapparsed[$country] = $age;
}
// Handle if a default value does not exist.
if (!$isdefaultvaluepresent) {
throw new \moodle_exception('agedigitalconsentmapinvaliddefault');
}
return $ageconsentmapparsed;
}
}
+435
View File
@@ -0,0 +1,435 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Auth external API
*
* @package core_auth
* @category external
* @copyright 2016 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.2
*/
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->libdir . '/authlib.php');
/**
* Auth external functions
*
* @package core_auth
* @category external
* @copyright 2016 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.2
*/
class core_auth_external extends external_api {
/**
* Describes the parameters for confirm_user.
*
* @return external_function_parameters
* @since Moodle 3.2
*/
public static function confirm_user_parameters() {
return new external_function_parameters(
array(
'username' => new external_value(core_user::get_property_type('username'), 'User name'),
'secret' => new external_value(core_user::get_property_type('secret'), 'Confirmation secret'),
)
);
}
/**
* Confirm a user account.
*
* @param string $username user name
* @param string $secret confirmation secret (random string) used for validating the confirm request
* @return array warnings and success status (true if the user was confirmed, false if he was already confirmed)
* @since Moodle 3.2
* @throws moodle_exception
*/
public static function confirm_user($username, $secret) {
global $PAGE;
$warnings = array();
$params = self::validate_parameters(
self::confirm_user_parameters(),
array(
'username' => $username,
'secret' => $secret,
)
);
$context = context_system::instance();
$PAGE->set_context($context);
if (!$authplugin = signup_get_user_confirmation_authplugin()) {
throw new moodle_exception('confirmationnotenabled');
}
$confirmed = $authplugin->user_confirm($username, $secret);
if ($confirmed == AUTH_CONFIRM_ALREADY) {
$success = false;
$warnings[] = array(
'item' => 'user',
'itemid' => 0,
'warningcode' => 'alreadyconfirmed',
'message' => s(get_string('alreadyconfirmed'))
);
} else if ($confirmed == AUTH_CONFIRM_OK) {
$success = true;
} else {
throw new moodle_exception('invalidconfirmdata');
}
$result = array(
'success' => $success,
'warnings' => $warnings,
);
return $result;
}
/**
* Describes the confirm_user return value.
*
* @return external_single_structure
* @since Moodle 3.2
*/
public static function confirm_user_returns() {
return new external_single_structure(
array(
'success' => new external_value(PARAM_BOOL, 'True if the user was confirmed, false if he was already confirmed'),
'warnings' => new external_warnings(),
)
);
}
/**
* Describes the parameters for request_password_reset.
*
* @return external_function_parameters
* @since Moodle 3.4
*/
public static function request_password_reset_parameters() {
return new external_function_parameters(
array(
'username' => new external_value(core_user::get_property_type('username'), 'User name', VALUE_DEFAULT, ''),
'email' => new external_value(core_user::get_property_type('email'), 'User email', VALUE_DEFAULT, ''),
)
);
}
/**
* Requests a password reset.
*
* @param string $username user name
* @param string $email user email
* @return array warnings and success status (including notices and errors while processing)
* @since Moodle 3.4
* @throws moodle_exception
*/
public static function request_password_reset($username = '', $email = '') {
global $CFG, $PAGE;
require_once($CFG->dirroot . '/login/lib.php');
$warnings = array();
$params = self::validate_parameters(
self::request_password_reset_parameters(),
array(
'username' => $username,
'email' => $email,
)
);
$context = context_system::instance();
$PAGE->set_context($context); // Needed by format_string calls.
// Check if an alternate forgotten password method is set.
if (!empty($CFG->forgottenpasswordurl)) {
throw new moodle_exception('cannotmailconfirm');
}
$errors = core_login_validate_forgot_password_data($params);
if (!empty($errors)) {
$status = 'dataerror';
$notice = '';
foreach ($errors as $itemname => $message) {
$warnings[] = array(
'item' => $itemname,
'itemid' => 0,
'warningcode' => 'fielderror',
'message' => s($message)
);
}
} else {
list($status, $notice, $url) = core_login_process_password_reset($params['username'], $params['email']);
}
return array(
'status' => $status,
'notice' => $notice,
'warnings' => $warnings,
);
}
/**
* Describes the request_password_reset return value.
*
* @return external_single_structure
* @since Moodle 3.4
*/
public static function request_password_reset_returns() {
return new external_single_structure(
array(
'status' => new external_value(PARAM_ALPHANUMEXT, 'The returned status of the process:
dataerror: Error in the sent data (username or email). More information in warnings field.
emailpasswordconfirmmaybesent: Email sent or not (depends on user found in database).
emailpasswordconfirmnotsent: Failure, user not found.
emailpasswordconfirmnoemail: Failure, email not found.
emailalreadysent: Email already sent.
emailpasswordconfirmsent: User pending confirmation.
emailresetconfirmsent: Email sent.
'),
'notice' => new external_value(PARAM_RAW, 'Important information for the user about the process.'),
'warnings' => new external_warnings(),
)
);
}
/**
* Describes the parameters for the digital minor check.
*
* @return external_function_parameters
* @since Moodle 3.4
*/
public static function is_minor_parameters() {
return new external_function_parameters(
array(
'age' => new external_value(PARAM_INT, 'Age'),
'country' => new external_value(PARAM_ALPHA, 'Country of residence'),
)
);
}
/**
* Requests a check if a user is digital minor.
*
* @param int $age User age
* @param string $country Country of residence
* @return array status (true if the user is a minor, false otherwise)
* @since Moodle 3.4
* @throws moodle_exception
*/
public static function is_minor($age, $country) {
global $CFG, $PAGE;
require_once($CFG->dirroot . '/login/lib.php');
$params = self::validate_parameters(
self::is_minor_parameters(),
array(
'age' => $age,
'country' => $country,
)
);
if (!array_key_exists($params['country'], get_string_manager()->get_list_of_countries())) {
throw new invalid_parameter_exception('Invalid value for country parameter (value: '.
$params['country'] .')');
}
$context = context_system::instance();
$PAGE->set_context($context);
// Check if verification of age and location (minor check) is enabled.
if (!\core_auth\digital_consent::is_age_digital_consent_verification_enabled()) {
throw new moodle_exception('nopermissions', 'error', '',
get_string('agelocationverificationdisabled', 'error'));
}
$status = \core_auth\digital_consent::is_minor($params['age'], $params['country']);
return array(
'status' => $status
);
}
/**
* Describes the is_minor return value.
*
* @return external_single_structure
* @since Moodle 3.4
*/
public static function is_minor_returns() {
return new external_single_structure(
array(
'status' => new external_value(PARAM_BOOL, 'True if the user is considered to be a digital minor,
false if not')
)
);
}
/**
* Describes the parameters for is_age_digital_consent_verification_enabled.
*
* @return external_function_parameters
* @since Moodle 3.3
*/
public static function is_age_digital_consent_verification_enabled_parameters() {
return new external_function_parameters(array());
}
/**
* Checks if age digital consent verification is enabled.
*
* @return array status (true if digital consent verification is enabled, false otherwise.)
* @since Moodle 3.3
* @throws moodle_exception
*/
public static function is_age_digital_consent_verification_enabled() {
global $PAGE;
$context = context_system::instance();
$PAGE->set_context($context);
$status = false;
// Check if verification is enabled.
if (\core_auth\digital_consent::is_age_digital_consent_verification_enabled()) {
$status = true;
}
return array(
'status' => $status
);
}
/**
* Describes the is_age_digital_consent_verification_enabled return value.
*
* @return external_single_structure
* @since Moodle 3.3
*/
public static function is_age_digital_consent_verification_enabled_returns() {
return new external_single_structure(
array(
'status' => new external_value(PARAM_BOOL, 'True if digital consent verification is enabled,
false otherwise.')
)
);
}
/**
* Describes the parameters for resend_confirmation_email.
*
* @return external_function_parameters
* @since Moodle 3.6
*/
public static function resend_confirmation_email_parameters() {
return new external_function_parameters(
array(
'username' => new external_value(core_user::get_property_type('username'), 'Username.'),
'password' => new external_value(core_user::get_property_type('password'), 'Plain text password.'),
'redirect' => new external_value(PARAM_LOCALURL, 'Redirect the user to this site url after confirmation.',
VALUE_DEFAULT, ''),
)
);
}
/**
* Requests resend the confirmation email.
*
* @param string $username user name
* @param string $password plain text password
* @param string $redirect redirect the user to this site url after confirmation
* @return array warnings and success status
* @since Moodle 3.6
* @throws moodle_exception
*/
public static function resend_confirmation_email($username, $password, $redirect = '') {
global $PAGE;
$warnings = array();
$params = self::validate_parameters(
self::resend_confirmation_email_parameters(),
array(
'username' => $username,
'password' => $password,
'redirect' => $redirect,
)
);
$context = context_system::instance();
$PAGE->set_context($context); // Need by internal APIs.
$username = trim(core_text::strtolower($params['username']));
$password = $params['password'];
if (is_restored_user($username)) {
throw new moodle_exception('restoredaccountresetpassword', 'webservice');
}
$user = authenticate_user_login($username, $password);
if (empty($user)) {
throw new moodle_exception('invalidlogin');
}
if ($user->confirmed) {
throw new moodle_exception('alreadyconfirmed');
}
// Check if we should redirect the user once the user is confirmed.
$confirmationurl = null;
if (!empty($params['redirect'])) {
// Pass via moodle_url to fix thinks like admin links.
$redirect = new moodle_url($params['redirect']);
$confirmationurl = new moodle_url('/login/confirm.php', array('redirect' => $redirect->out()));
}
$status = send_confirmation_email($user, $confirmationurl);
return array(
'status' => $status,
'warnings' => $warnings,
);
}
/**
* Describes the resend_confirmation_email return value.
*
* @return external_single_structure
* @since Moodle 3.6
*/
public static function resend_confirmation_email_returns() {
return new external_single_structure(
array(
'status' => new external_value(PARAM_BOOL, 'True if the confirmation email was sent, false otherwise.'),
'warnings' => new external_warnings(),
)
);
}
}
@@ -0,0 +1,62 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Age and location verification mform.
*
* @package core_auth
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_auth\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
use moodleform;
/**
* Age and location verification mform class.
*
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class verify_age_location_form extends moodleform {
/**
* Defines the form fields.
*/
public function definition() {
global $CFG;
$mform = $this->_form;
$mform->addElement('text', 'age', get_string('whatisyourage'), array('optional' => false));
$mform->setType('age', PARAM_RAW);
$mform->addRule('age', null, 'required', null, 'client');
$mform->addRule('age', null, 'numeric', null, 'client');
$countries = get_string_manager()->get_list_of_countries();
$defaultcountry[''] = get_string('selectacountry');
$countries = array_merge($defaultcountry, $countries);
$mform->addElement('select', 'country', get_string('wheredoyoulive'), $countries);
$mform->addRule('country', null, 'required', null, 'client');
$mform->setDefault('country', $CFG->country);
$this->add_action_buttons(true, get_string('proceed'));
}
}
@@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Digital minor renderable.
*
* @package core_auth
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_auth\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use templatable;
/**
* Digital minor renderable class.
*
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class digital_minor_page implements renderable, templatable {
/**
* Export the page data for the mustache template.
*
* @param renderer_base $output renderer to be used to render the page elements.
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
global $SITE, $CFG;
$sitename = format_string($SITE->fullname);
$supportname = $CFG->supportname;
$supportemail = $CFG->supportemail ?? null;
$context = [
'sitename' => $sitename,
'supportname' => $supportname,
'supportemail' => $supportemail,
'homelink' => new \moodle_url('/')
];
return $context;
}
}
+192
View File
@@ -0,0 +1,192 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Login renderable.
*
* @package core_auth
* @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_auth\output;
use context_system;
use help_icon;
use moodle_url;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Login renderable class.
*
* @package core_auth
* @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class login implements renderable, templatable {
/** @var bool Whether to auto focus the form fields. */
public $autofocusform;
/** @var bool Whether we can login as guest. */
public $canloginasguest;
/** @var bool Whether we can login by e-mail. */
public $canloginbyemail;
/** @var bool Whether we can sign-up. */
public $cansignup;
/** @var help_icon The cookies help icon. */
public $cookieshelpicon;
/** @var string The error message, if any. */
public $error;
/** @var string The info message, if any. */
public $info;
/** @var moodle_url Forgot password URL. */
public $forgotpasswordurl;
/** @var array Additional identify providers, contains the keys 'url', 'name' and 'icon'. */
public $identityproviders;
/** @var string Login instructions, if any. */
public $instructions;
/** @var moodle_url The form action login URL. */
public $loginurl;
/** @var moodle_url The sign-up URL. */
public $signupurl;
/** @var string The user name to pre-fill the form with. */
public $username;
/** @var string The language selector menu. */
public $languagemenu;
/** @var string The csrf token to limit login to requests that come from the login form. */
public $logintoken;
/** @var string Maintenance message, if Maintenance is enabled. */
public $maintenance;
/** @var string ReCaptcha element HTML. */
public $recaptcha;
/** @var bool Toggle the password visibility icon. */
public $togglepassword;
/** @var bool Toggle the password visibility icon for small screens only. */
public $smallscreensonly;
/**
* Constructor.
*
* @param array $authsequence The enabled sequence of authentication plugins.
* @param string $username The username to display.
*/
public function __construct(array $authsequence, $username = '') {
global $CFG, $OUTPUT, $PAGE;
$this->username = $username;
$languagedata = new \core\output\language_menu($PAGE);
$this->languagemenu = $languagedata->export_for_action_menu($OUTPUT);
$this->canloginasguest = $CFG->guestloginbutton && !isguestuser();
$this->canloginbyemail = !empty($CFG->authloginviaemail);
$this->cansignup = $CFG->registerauth == 'email' || !empty($CFG->registerauth);
if ($CFG->rememberusername == 0) {
$this->cookieshelpicon = new help_icon('cookiesenabledonlysession', 'core');
} else {
$this->cookieshelpicon = new help_icon('cookiesenabled', 'core');
}
$this->autofocusform = !empty($CFG->loginpageautofocus);
$this->forgotpasswordurl = new moodle_url('/login/forgot_password.php');
$this->loginurl = new moodle_url('/login/index.php');
$this->signupurl = new moodle_url('/login/signup.php');
// Authentication instructions.
$this->instructions = $CFG->auth_instructions;
if (is_enabled_auth('none')) {
$this->instructions = get_string('loginstepsnone');
} else if ($CFG->registerauth == 'email' && empty($this->instructions)) {
$this->instructions = get_string('loginsteps', 'core', 'signup.php');
}
if ($CFG->maintenance_enabled == true) {
if (!empty($CFG->maintenance_message)) {
$this->maintenance = $CFG->maintenance_message;
} else {
$this->maintenance = get_string('sitemaintenance', 'admin');
}
}
// Identity providers.
$this->identityproviders = \auth_plugin_base::get_identity_providers($authsequence);
$this->logintoken = \core\session\manager::get_login_token();
// ReCaptcha.
if (login_captcha_enabled()) {
require_once($CFG->libdir . '/recaptchalib_v2.php');
$this->recaptcha = recaptcha_get_challenge_html(RECAPTCHA_API_URL, $CFG->recaptchapublickey);
}
// Toggle password visibility icon.
$this->togglepassword = get_config('core', 'loginpasswordtoggle') == TOGGLE_SENSITIVE_ENABLED ||
get_config('core', 'loginpasswordtoggle') == TOGGLE_SENSITIVE_SMALL_SCREENS_ONLY;
$this->smallscreensonly = get_config('core', 'loginpasswordtoggle') == TOGGLE_SENSITIVE_SMALL_SCREENS_ONLY;
}
/**
* Set the error message.
*
* @param string $error The error message.
*/
public function set_error($error) {
$this->error = $error;
}
/**
* Set the info message.
*
* @param string $info The info message.
*/
public function set_info(string $info): void {
$this->info = $info;
}
public function export_for_template(renderer_base $output) {
$identityproviders = \auth_plugin_base::prepare_identity_providers_for_output($this->identityproviders, $output);
$data = new stdClass();
$data->autofocusform = $this->autofocusform;
$data->canloginasguest = $this->canloginasguest;
$data->canloginbyemail = $this->canloginbyemail;
$data->cansignup = $this->cansignup;
$data->cookieshelpicon = $this->cookieshelpicon->export_for_template($output);
$data->error = $this->error;
$data->info = $this->info;
$data->forgotpasswordurl = $this->forgotpasswordurl->out(false);
$data->hasidentityproviders = !empty($this->identityproviders);
$data->hasinstructions = !empty($this->instructions) || $this->cansignup;
$data->identityproviders = $identityproviders;
list($data->instructions, $data->instructionsformat) = \core_external\util::format_text($this->instructions, FORMAT_MOODLE,
context_system::instance()->id);
$data->loginurl = $this->loginurl->out(false);
$data->signupurl = $this->signupurl->out(false);
$data->username = $this->username;
$data->logintoken = $this->logintoken;
$data->maintenance = format_text($this->maintenance, FORMAT_MOODLE);
$data->languagemenu = $this->languagemenu;
$data->recaptcha = $this->recaptcha;
$data->togglepassword = $this->togglepassword;
$data->smallscreensonly = $this->smallscreensonly;
return $data;
}
}
@@ -0,0 +1,81 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Age and location verification renderable.
*
* @package core_auth
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_auth\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use templatable;
require_once($CFG->libdir.'/formslib.php');
/**
* Age and location verification renderable class.
*
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class verify_age_location_page implements renderable, templatable {
/** @var \moodleform The form object */
protected $form;
/** @var string Error message */
protected $errormessage;
/**
* Constructor
*
* @param \moodleform $form The form object
* @param string $errormessage The error message.
*/
public function __construct($form, $errormessage = null) {
$this->form = $form;
$this->errormessage = $errormessage;
}
/**
* Export the page data for the mustache template.
*
* @param renderer_base $output renderer to be used to render the page elements.
* @return \stdClass
*/
public function export_for_template(renderer_base $output) {
global $SITE;
$sitename = format_string($SITE->fullname);
$formhtml = $this->form->render();
$error = $this->errormessage;
$context = [
'sitename' => $sitename,
'formhtml' => $formhtml,
'error' => $error
];
return $context;
}
}
+103
View File
@@ -0,0 +1,103 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Data provider.
*
* @package core_auth
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_auth\privacy;
defined('MOODLE_INTERNAL') || die();
use context;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
/**
* Data provider class.
*
* @package core_auth
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\user_preference_provider {
/**
* Returns metadata.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_user_preference('auth_forcepasswordchange', 'privacy:metadata:userpref:forcepasswordchange');
$collection->add_user_preference('create_password', 'privacy:metadata:userpref:createpassword');
$collection->add_user_preference('login_failed_count', 'privacy:metadata:userpref:loginfailedcount');
$collection->add_user_preference('login_failed_count_since_success',
'privacy:metadata:userpref:loginfailedcountsincesuccess');
$collection->add_user_preference('login_failed_last', 'privacy:metadata:userpref:loginfailedlast');
$collection->add_user_preference('login_lockout', 'privacy:metadata:userpref:loginlockout');
$collection->add_user_preference('login_lockout_ignored', 'privacy:metadata:userpref:loginlockoutignored');
$collection->add_user_preference('login_lockout_secret', 'privacy:metadata:userpref:loginlockoutsecret');
return $collection;
}
/**
* Export all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$yesno = function($v) {
return transform::yesno($v);
};
$datetime = function($v) {
return $v ? transform::datetime($v) : null;
};
$prefs = [
['auth_forcepasswordchange', 'forcepasswordchange', $yesno],
['create_password', 'createpassword', $yesno],
['login_failed_count', 'loginfailedcount', null],
['login_failed_count_since_success', 'loginfailedcountsincesuccess', null],
['login_failed_last', 'loginfailedlast', $datetime],
['login_lockout', 'loginlockout', $datetime],
['login_lockout_ignored', 'loginlockoutignored', $yesno],
['login_lockout_secret', 'loginlockoutsecret', null],
];
foreach ($prefs as $prefdata) {
list($prefname, $langkey, $transformer) = $prefdata;
$value = get_user_preferences($prefname, null, $userid);
if ($value === null) {
continue;
}
writer::export_user_preference('core_auth', $prefname, $transformer ? $transformer($value) : $value,
get_string("privacy:metadata:userpref:{$langkey}", 'core_auth'));
}
}
}
+788
View File
@@ -0,0 +1,788 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Authentication Plugin: External Database Authentication
*
* Checks against an external database.
*
* @package auth_db
* @author Martin Dougiamas
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/authlib.php');
/**
* External database authentication plugin.
*/
class auth_plugin_db extends auth_plugin_base {
/**
* Constructor.
*/
function __construct() {
global $CFG;
require_once($CFG->libdir.'/adodb/adodb.inc.php');
$this->authtype = 'db';
$this->config = get_config('auth_db');
$this->errorlogtag = '[AUTH DB] ';
if (empty($this->config->extencoding)) {
$this->config->extencoding = 'utf-8';
}
}
/**
* Returns true if the username and password work and false if they are
* wrong or don't exist.
*
* @param string $username The username
* @param string $password The password
* @return bool Authentication success or failure.
*/
function user_login($username, $password) {
global $CFG, $DB;
if ($this->is_configured() === false) {
debugging(get_string('auth_notconfigured', 'auth', $this->authtype));
return false;
}
$extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
$extpassword = core_text::convert($password, 'utf-8', $this->config->extencoding);
if ($this->is_internal()) {
// Lookup username externally, but resolve
// password locally -- to support backend that
// don't track passwords.
if (isset($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_KEEP) {
// No need to connect to external database in this case because users are never removed and we verify password locally.
if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
return validate_internal_user_password($user, $password);
} else {
return false;
}
}
$authdb = $this->db_init();
$rs = $authdb->Execute("SELECT *
FROM {$this->config->table}
WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'");
if (!$rs) {
$authdb->Close();
debugging(get_string('auth_dbcantconnect','auth_db'));
return false;
}
if (!$rs->EOF) {
$rs->Close();
$authdb->Close();
// User exists externally - check username/password internally.
if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
return validate_internal_user_password($user, $password);
}
} else {
$rs->Close();
$authdb->Close();
// User does not exist externally.
return false;
}
} else {
// Normal case: use external db for both usernames and passwords.
$authdb = $this->db_init();
$rs = $authdb->Execute("SELECT {$this->config->fieldpass}
FROM {$this->config->table}
WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'");
if (!$rs) {
$authdb->Close();
debugging(get_string('auth_dbcantconnect','auth_db'));
return false;
}
if ($rs->EOF) {
$authdb->Close();
return false;
}
$fields = array_change_key_case($rs->fields, CASE_LOWER);
$fromdb = $fields[strtolower($this->config->fieldpass)];
$rs->Close();
$authdb->Close();
if ($this->config->passtype === 'plaintext') {
return ($fromdb === $extpassword);
} else if ($this->config->passtype === 'md5') {
return (strtolower($fromdb) === md5($extpassword));
} else if ($this->config->passtype === 'sha1') {
return (strtolower($fromdb) === sha1($extpassword));
} else if ($this->config->passtype === 'saltedcrypt') {
return password_verify($extpassword, $fromdb);
} else {
return false;
}
}
}
/**
* Connect to external database.
*
* @return ADOConnection
* @throws moodle_exception
*/
function db_init() {
if ($this->is_configured() === false) {
throw new moodle_exception('auth_dbcantconnect', 'auth_db');
}
// Connect to the external database (forcing new connection).
$authdb = ADONewConnection($this->config->type);
if (!empty($this->config->debugauthdb)) {
$authdb->debug = true;
ob_start(); //Start output buffer to allow later use of the page headers.
}
$authdb->Connect($this->config->host, $this->config->user, $this->config->pass, $this->config->name, true);
$authdb->SetFetchMode(ADODB_FETCH_ASSOC);
if (!empty($this->config->setupsql)) {
$authdb->Execute($this->config->setupsql);
}
return $authdb;
}
/**
* Returns user attribute mappings between moodle and the external database.
*
* @return array
*/
function db_attributes() {
$moodleattributes = array();
// If we have custom fields then merge them with user fields.
$customfields = $this->get_custom_user_profile_fields();
if (!empty($customfields) && !empty($this->userfields)) {
$userfields = array_merge($this->userfields, $customfields);
} else {
$userfields = $this->userfields;
}
foreach ($userfields as $field) {
if (!empty($this->config->{"field_map_$field"})) {
$moodleattributes[$field] = $this->config->{"field_map_$field"};
}
}
$moodleattributes['username'] = $this->config->fielduser;
return $moodleattributes;
}
/**
* Reads any other information for a user from external database,
* then returns it in an array.
*
* @param string $username
* @return array
*/
function get_userinfo($username) {
global $CFG;
$extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
$authdb = $this->db_init();
// Array to map local fieldnames we want, to external fieldnames.
$selectfields = $this->db_attributes();
$result = array();
// If at least one field is mapped from external db, get that mapped data.
if ($selectfields) {
$select = array();
$fieldcount = 0;
foreach ($selectfields as $localname=>$externalname) {
// Without aliasing, multiple occurrences of the same external
// name can coalesce in only occurrence in the result.
$select[] = "$externalname AS F".$fieldcount;
$fieldcount++;
}
$select = implode(', ', $select);
$sql = "SELECT $select
FROM {$this->config->table}
WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'";
if ($rs = $authdb->Execute($sql)) {
if (!$rs->EOF) {
$fields = $rs->FetchRow();
// Convert the associative array to an array of its values so we don't have to worry about the case of its keys.
$fields = array_values($fields);
foreach (array_keys($selectfields) as $index => $localname) {
$value = $fields[$index];
$result[$localname] = core_text::convert($value, $this->config->extencoding, 'utf-8');
}
}
$rs->Close();
}
}
$authdb->Close();
return $result;
}
/**
* Change a user's password.
*
* @param stdClass $user User table object
* @param string $newpassword Plaintext password
* @return bool True on success
*/
function user_update_password($user, $newpassword) {
global $DB;
if ($this->is_internal()) {
$puser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
// This will also update the stored hash to the latest algorithm
// if the existing hash is using an out-of-date algorithm (or the
// legacy md5 algorithm).
if (update_internal_user_password($puser, $newpassword)) {
$user->password = $puser->password;
return true;
} else {
return false;
}
} else {
// We should have never been called!
return false;
}
}
/**
* Synchronizes user from external db to moodle user table.
*
* Sync should be done by using idnumber attribute, not username.
* You need to pass firstsync parameter to function to fill in
* idnumbers if they don't exists in moodle user table.
*
* Syncing users removes (disables) users that don't exists anymore in external db.
* Creates new users and updates coursecreator status of users.
*
* This implementation is simpler but less scalable than the one found in the LDAP module.
*
* @param progress_trace $trace
* @param bool $do_updates Optional: set to true to force an update of existing accounts
* @return int 0 means success, 1 means failure
*/
function sync_users(progress_trace $trace, $do_updates=false) {
global $CFG, $DB;
require_once($CFG->dirroot . '/user/lib.php');
// List external users.
$userlist = $this->get_userlist();
// Delete obsolete internal users.
if (!empty($this->config->removeuser)) {
$suspendselect = "";
if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
$suspendselect = "AND u.suspended = 0";
}
// Find obsolete users.
if (count($userlist)) {
$removeusers = array();
$params['authtype'] = $this->authtype;
$sql = "SELECT u.id, u.username
FROM {user} u
WHERE u.auth=:authtype
AND u.deleted=0
AND u.mnethostid=:mnethostid
$suspendselect";
$params['mnethostid'] = $CFG->mnet_localhost_id;
$internalusersrs = $DB->get_recordset_sql($sql, $params);
$usernamelist = array_flip($userlist);
foreach ($internalusersrs as $internaluser) {
if (!array_key_exists($internaluser->username, $usernamelist)) {
$removeusers[] = $internaluser;
}
}
$internalusersrs->close();
} else {
$sql = "SELECT u.id, u.username
FROM {user} u
WHERE u.auth=:authtype AND u.deleted=0 AND u.mnethostid=:mnethostid $suspendselect";
$params = array();
$params['authtype'] = $this->authtype;
$params['mnethostid'] = $CFG->mnet_localhost_id;
$removeusers = $DB->get_records_sql($sql, $params);
}
if (!empty($removeusers)) {
$trace->output(get_string('auth_dbuserstoremove', 'auth_db', count($removeusers)));
foreach ($removeusers as $user) {
if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) {
delete_user($user);
$trace->output(get_string('auth_dbdeleteuser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
} else if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
$updateuser = new stdClass();
$updateuser->id = $user->id;
$updateuser->suspended = 1;
user_update_user($updateuser, false);
$trace->output(get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
}
}
}
unset($removeusers);
}
if (!count($userlist)) {
// Exit right here, nothing else to do.
$trace->finished();
return 0;
}
// Update existing accounts.
if ($do_updates) {
// Narrow down what fields we need to update.
$all_keys = array_keys(get_object_vars($this->config));
$updatekeys = array();
foreach ($all_keys as $key) {
if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) {
if ($this->config->{$key} === 'onlogin') {
array_push($updatekeys, $match[1]); // The actual key name.
}
}
}
unset($all_keys); unset($key);
// Only go ahead if we actually have fields to update locally.
if (!empty($updatekeys)) {
$update_users = array();
// All the drivers can cope with chunks of 10,000. See line 4491 of lib/dml/tests/dml_est.php
$userlistchunks = array_chunk($userlist , 10000);
foreach($userlistchunks as $userlistchunk) {
list($in_sql, $params) = $DB->get_in_or_equal($userlistchunk, SQL_PARAMS_NAMED, 'u', true);
$params['authtype'] = $this->authtype;
$params['mnethostid'] = $CFG->mnet_localhost_id;
$sql = "SELECT u.id, u.username, u.suspended
FROM {user} u
WHERE u.auth = :authtype AND u.deleted = 0 AND u.mnethostid = :mnethostid AND u.username {$in_sql}";
$update_users = $update_users + $DB->get_records_sql($sql, $params);
}
if ($update_users) {
$trace->output("User entries to update: ".count($update_users));
foreach ($update_users as $user) {
if ($this->update_user_record($user->username, $updatekeys, false, (bool) $user->suspended)) {
$trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
} else {
$trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id))." - ".get_string('skipped'), 1);
}
}
unset($update_users);
}
}
}
// Create missing accounts.
// NOTE: this is very memory intensive and generally inefficient.
$suspendselect = "";
if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
$suspendselect = "AND u.suspended = 0";
}
$sql = "SELECT u.id, u.username
FROM {user} u
WHERE u.auth=:authtype AND u.deleted='0' AND mnethostid=:mnethostid $suspendselect";
$users = $DB->get_records_sql($sql, array('authtype'=>$this->authtype, 'mnethostid'=>$CFG->mnet_localhost_id));
// Simplify down to usernames.
$usernames = array();
if (!empty($users)) {
foreach ($users as $user) {
array_push($usernames, $user->username);
}
unset($users);
}
$add_users = array_diff($userlist, $usernames);
unset($usernames);
if (!empty($add_users)) {
$trace->output(get_string('auth_dbuserstoadd','auth_db',count($add_users)));
// Do not use transactions around this foreach, we want to skip problematic users, not revert everything.
foreach($add_users as $user) {
$username = $user;
if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
if ($olduser = $DB->get_record('user', array('username' => $username, 'deleted' => 0, 'suspended' => 1,
'mnethostid' => $CFG->mnet_localhost_id, 'auth' => $this->authtype))) {
$updateuser = new stdClass();
$updateuser->id = $olduser->id;
$updateuser->suspended = 0;
user_update_user($updateuser);
$trace->output(get_string('auth_dbreviveduser', 'auth_db', array('name' => $username,
'id' => $olduser->id)), 1);
continue;
}
}
// Do not try to undelete users here, instead select suspending if you ever expect users will reappear.
// Prep a few params.
$user = $this->get_userinfo_asobj($user);
$user->username = $username;
$user->confirmed = 1;
$user->auth = $this->authtype;
$user->mnethostid = $CFG->mnet_localhost_id;
if ($collision = $DB->get_record_select('user', "username = :username AND mnethostid = :mnethostid AND auth <> :auth", array('username'=>$user->username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype), 'id,username,auth')) {
$trace->output(get_string('auth_dbinsertuserduplicate', 'auth_db', array('username'=>$user->username, 'auth'=>$collision->auth)), 1);
continue;
}
try {
$id = user_create_user($user, false, false); // It is truly a new user.
$trace->output(get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)), 1);
} catch (moodle_exception $e) {
$trace->output(get_string('auth_dbinsertusererror', 'auth_db', $user->username), 1);
continue;
}
// If relevant, tag for password generation.
if ($this->is_internal()) {
set_user_preference('auth_forcepasswordchange', 1, $id);
set_user_preference('create_password', 1, $id);
}
// Save custom profile fields here.
require_once($CFG->dirroot . '/user/profile/lib.php');
$user->id = $id;
profile_save_data($user);
// Make sure user context is present.
context_user::instance($id);
\core\event\user_created::create_from_userid($id)->trigger();
}
unset($add_users);
}
$trace->finished();
return 0;
}
function user_exists($username) {
// Init result value.
$result = false;
$extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
$authdb = $this->db_init();
$rs = $authdb->Execute("SELECT *
FROM {$this->config->table}
WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' ");
if (!$rs) {
throw new \moodle_exception('auth_dbcantconnect', 'auth_db');
} else if (!$rs->EOF) {
// User exists externally.
$result = true;
}
$authdb->Close();
return $result;
}
function get_userlist() {
// Init result value.
$result = array();
$authdb = $this->db_init();
// Fetch userlist.
$rs = $authdb->Execute("SELECT {$this->config->fielduser}
FROM {$this->config->table} ");
if (!$rs) {
throw new \moodle_exception('auth_dbcantconnect', 'auth_db');
} else if (!$rs->EOF) {
while ($rec = $rs->FetchRow()) {
$rec = array_change_key_case((array)$rec, CASE_LOWER);
array_push($result, $rec[strtolower($this->config->fielduser)]);
}
}
$authdb->Close();
return $result;
}
/**
* Reads user information from DB and return it in an object.
*
* @param string $username username
* @return stdClass
*/
function get_userinfo_asobj($username) {
$user_array = truncate_userinfo($this->get_userinfo($username));
$user = new stdClass();
foreach($user_array as $key=>$value) {
$user->{$key} = $value;
}
return $user;
}
/**
* Called when the user record is updated.
* Modifies user in external database. It takes olduser (before changes) and newuser (after changes)
* compares information saved modified information to external db.
*
* @param stdClass $olduser Userobject before modifications
* @param stdClass $newuser Userobject new modified userobject
* @return boolean result
*
*/
function user_update($olduser, $newuser) {
if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) {
error_log("ERROR:User renaming not allowed in ext db");
return false;
}
if (isset($olduser->auth) and $olduser->auth != $this->authtype) {
return true; // Just change auth and skip update.
}
$curruser = $this->get_userinfo($olduser->username);
if (empty($curruser)) {
error_log("ERROR:User $olduser->username found in ext db");
return false;
}
$extusername = core_text::convert($olduser->username, 'utf-8', $this->config->extencoding);
$authdb = $this->db_init();
$update = array();
foreach($curruser as $key=>$value) {
if ($key == 'username') {
continue; // Skip this.
}
if (empty($this->config->{"field_updateremote_$key"})) {
continue; // Remote update not requested.
}
if (!isset($newuser->$key)) {
continue;
}
$nuvalue = $newuser->$key;
// Support for textarea fields.
if (isset($nuvalue['text'])) {
$nuvalue = $nuvalue['text'];
}
if ($nuvalue != $value) {
$update[] = $this->config->{"field_map_$key"}."='".$this->ext_addslashes(core_text::convert($nuvalue, 'utf-8', $this->config->extencoding))."'";
}
}
if (!empty($update)) {
$sql = "UPDATE {$this->config->table}
SET ".implode(',', $update)."
WHERE {$this->config->fielduser} = ?";
if (!$authdb->Execute($sql, array($this->ext_addslashes($extusername)))) {
throw new \moodle_exception('auth_dbupdateerror', 'auth_db');
}
}
$authdb->Close();
return true;
}
function prevent_local_passwords() {
return !$this->is_internal();
}
/**
* Returns true if this authentication plugin is "internal".
*
* Internal plugins use password hashes from Moodle user table for authentication.
*
* @return bool
*/
function is_internal() {
if (!isset($this->config->passtype)) {
return true;
}
return ($this->config->passtype === 'internal');
}
/**
* Returns false if this plugin is enabled but not configured.
*
* @return bool
*/
public function is_configured() {
if (!empty($this->config->type)) {
return true;
}
return false;
}
/**
* Indicates if moodle should automatically update internal user
* records with data from external sources using the information
* from auth_plugin_base::get_userinfo().
*
* @return bool true means automatically copy data from ext to user table
*/
function is_synchronised_with_external() {
return true;
}
/**
* Returns true if this authentication plugin can change the user's
* password.
*
* @return bool
*/
function can_change_password() {
return ($this->is_internal() or !empty($this->config->changepasswordurl));
}
/**
* Returns the URL for changing the user's pw, or empty if the default can
* be used.
*
* @return moodle_url
*/
function change_password_url() {
if ($this->is_internal() || empty($this->config->changepasswordurl)) {
// Standard form.
return null;
} else {
// Use admin defined custom url.
return new moodle_url($this->config->changepasswordurl);
}
}
/**
* Returns true if plugin allows resetting of internal password.
*
* @return bool
*/
function can_reset_password() {
return $this->is_internal();
}
/**
* Add slashes, we can not use placeholders or system functions.
*
* @param string $text
* @return string
*/
function ext_addslashes($text) {
if (empty($this->config->sybasequoting)) {
$text = str_replace('\\', '\\\\', $text);
$text = str_replace(array('\'', '"', "\0"), array('\\\'', '\\"', '\\0'), $text);
} else {
$text = str_replace("'", "''", $text);
}
return $text;
}
/**
* Test if settings are ok, print info to output.
* @private
*/
public function test_settings() {
global $CFG, $OUTPUT;
// NOTE: this is not localised intentionally, admins are supposed to understand English at least a bit...
raise_memory_limit(MEMORY_HUGE);
if (empty($this->config->table)) {
echo $OUTPUT->notification(get_string('auth_dbnoexttable', 'auth_db'), 'notifyproblem');
return;
}
if (empty($this->config->fielduser)) {
echo $OUTPUT->notification(get_string('auth_dbnouserfield', 'auth_db'), 'notifyproblem');
return;
}
$olddebug = $CFG->debug;
$olddisplay = ini_get('display_errors');
ini_set('display_errors', '1');
$CFG->debug = DEBUG_DEVELOPER;
$olddebugauthdb = $this->config->debugauthdb;
$this->config->debugauthdb = 1;
error_reporting($CFG->debug);
$adodb = $this->db_init();
if (!$adodb or !$adodb->IsConnected()) {
$this->config->debugauthdb = $olddebugauthdb;
$CFG->debug = $olddebug;
ini_set('display_errors', $olddisplay);
error_reporting($CFG->debug);
ob_end_flush();
echo $OUTPUT->notification(get_string('auth_dbcannotconnect', 'auth_db'), 'notifyproblem');
return;
}
$rs = $adodb->Execute("SELECT *
FROM {$this->config->table}
WHERE {$this->config->fielduser} <> 'random_unlikely_username'"); // Any unlikely name is ok here.
if (!$rs) {
echo $OUTPUT->notification(get_string('auth_dbcannotreadtable', 'auth_db'), 'notifyproblem');
} else if ($rs->EOF) {
echo $OUTPUT->notification(get_string('auth_dbtableempty', 'auth_db'), 'notifyproblem');
$rs->close();
} else {
$columns = array_keys($rs->fetchRow());
echo $OUTPUT->notification(get_string('auth_dbcolumnlist', 'auth_db', implode(', ', $columns)), 'notifysuccess');
$rs->close();
}
$adodb->Close();
$this->config->debugauthdb = $olddebugauthdb;
$CFG->debug = $olddebug;
ini_set('display_errors', $olddisplay);
error_reporting($CFG->debug);
ob_end_flush();
}
/**
* Clean the user data that comes from an external database.
* @deprecated since 3.1, please use core_user::clean_data() instead.
* @param array $user the user data to be validated against properties definition.
* @return stdClass $user the cleaned user data.
*/
public function clean_data($user) {
debugging('The method clean_data() has been deprecated, please use core_user::clean_data() instead.',
DEBUG_DEVELOPER);
return core_user::clean_data($user);
}
}
@@ -0,0 +1,51 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Special settings for auth_db password_link.
*
* @package auth_db
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Special settings for auth_db password_link.
*
* @package auth_db
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class auth_db_admin_setting_special_auth_configtext extends admin_setting_configtext {
/**
* We need to overwrite the global "alternate login url" setting if wayf is enabled.
*
* @param string $data Form data.
* @return string Empty when no errors.
*/
public function write_setting($data) {
if (get_config('auth_db', 'passtype') === 'internal') {
// We need to clear the auth_db change password link.
$data = '';
}
return parent::write_setting($data);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy Subsystem implementation for auth_db.
*
* @package auth_db
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_db\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for auth_db implementing null_provider.
*
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Sync users task
* @package auth_db
* @author Guy Thomas <gthomas@moodlerooms.com>
* @copyright Copyright (c) 2017 Blackboard Inc.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_db\task;
defined('MOODLE_INTERNAL') || die();
/**
* Sync users task class
* @package auth_db
* @author Guy Thomas <gthomas@moodlerooms.com>
* @copyright Copyright (c) 2017 Blackboard Inc.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sync_users extends \core\task\scheduled_task {
/**
* Name for this task.
*
* @return string
*/
public function get_name() {
return get_string('auth_dbsyncuserstask', 'auth_db');
}
/**
* Run task for synchronising users.
*/
public function execute() {
if (!is_enabled_auth('db')) {
mtrace('auth_db plugin is disabled, synchronisation stopped', 2);
return;
}
$dbauth = get_auth_plugin('db');
$config = get_config('auth_db');
$trace = new \text_progress_trace();
$update = !empty($config->updateusers);
$dbauth->sync_users($trace, $update);
}
}
+104
View File
@@ -0,0 +1,104 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Extdb user sync script.
*
* This script is meant to be called from a system cronjob to
* sync moodle user accounts with external database.
* It is required when using internal passwords (== passwords not defined in external database).
*
* Sample cron entry:
* # 5 minutes past 4am
* 5 4 * * * sudo -u www-data /usr/bin/php /var/www/moodle/auth/db/cli/sync_users.php
*
* Notes:
* - it is required to use the web server account when executing PHP CLI scripts
* - you need to change the "www-data" to match the apache user account
* - use "su" if "sudo" not available
* - If you have a large number of users, you may want to raise the memory limits
* by passing -d memory_limit=256M
* - For debugging & better logging, you are encouraged to use in the command line:
* -d log_errors=1 -d error_reporting=E_ALL -d display_errors=0 -d html_errors=0
*
* Performance notes:
* + The code is simpler, but not as optimized as its LDAP counterpart.
*
* @package auth_db
* @copyright 2006 Martin Langhoff
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('CLI_SCRIPT', true);
require(__DIR__.'/../../../config.php');
require_once("$CFG->libdir/clilib.php");
// Now get cli options.
list($options, $unrecognized) = cli_get_params(array('noupdate'=>false, 'verbose'=>false, 'help'=>false), array('n'=>'noupdate', 'v'=>'verbose', 'h'=>'help'));
if ($unrecognized) {
$unrecognized = implode("\n ", $unrecognized);
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
}
if ($options['help']) {
$help =
"Execute user account sync with external database.
The auth_db plugin must be enabled and properly configured.
Options:
-n, --noupdate Skip update of existing users
-v, --verbose Print verbose progress information
-h, --help Print out this help
Example:
\$ sudo -u www-data /usr/bin/php auth/db/cli/sync_users.php
Sample cron entry:
# 5 minutes past 4am
5 4 * * * sudo -u www-data /usr/bin/php /var/www/moodle/auth/db/cli/sync_users.php
";
echo $help;
die;
}
if (!is_enabled_auth('db')) {
cli_error('auth_db plugin is disabled, synchronisation stopped', 2);
}
cli_problem('[AUTH DB] The sync users cron has been deprecated. Please use the scheduled task instead.');
// Abort execution of the CLI script if the \auth_db\task\sync_users is enabled.
$task = \core\task\manager::get_scheduled_task('auth_db\task\sync_users');
if (!$task->get_disabled()) {
cli_error('[AUTH DB] The scheduled task sync_users is enabled, the cron execution has been aborted.');
}
if (empty($options['verbose'])) {
$trace = new null_progress_trace();
} else {
$trace = new text_progress_trace();
}
$update = empty($options['noupdate']);
/** @var auth_plugin_db $dbauth */
$dbauth = get_auth_plugin('db');
$result = $dbauth->sync_users($trace, $update);
exit($result);
+28
View File
@@ -0,0 +1,28 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* auth_db installer script.
*
* @package auth_db
* @copyright 2009 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
function xmldb_auth_db_install() {
global $CFG, $DB;
}
+38
View File
@@ -0,0 +1,38 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Task definition for auth_db.
* @author Guy Thomas <gthomas@moodlerooms.com>
* @copyright Copyright (c) 2017 Blackboard Inc.
* @package auth_db
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$tasks = array(
array(
'classname' => '\auth_db\task\sync_users',
'blocking' => 0,
'minute' => 'R',
'hour' => 'R',
'day' => '*',
'month' => '*',
'dayofweek' => '*',
'disabled' => 1
)
);
+44
View File
@@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* DB authentication plugin upgrade code
*
* @package auth_db
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Function to upgrade auth_db.
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_auth_db_upgrade($oldversion) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+79
View File
@@ -0,0 +1,79 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'auth_db', language 'en'.
*
* @package auth_db
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['auth_dbcantconnect'] = 'Could not connect to the specified authentication database...';
$string['auth_dbdebugauthdb'] = 'Debug ADOdb';
$string['auth_dbdebugauthdbhelp'] = 'Debug ADOdb connection to external database - use when getting empty page during login. Not suitable for production sites.';
$string['auth_dbdeleteuser'] = 'Deleted user {$a->name} id {$a->id}';
$string['auth_dbdeleteusererror'] = 'Error deleting user {$a}';
$string['auth_dbdescription'] = 'This method uses an external database table to check whether a given username and password is valid. If the account is a new one, then information from other fields may also be copied across into Moodle.';
$string['auth_dbextencoding'] = 'External db encoding';
$string['auth_dbextencodinghelp'] = 'Encoding used in external database';
$string['auth_dbextrafields'] = 'These fields are optional. You can choose to pre-fill some Moodle user fields with information from the <b>external database fields</b> that you specify here. <p>If you leave these blank, then defaults will be used.</p><p>In either case, the user will be able to edit all of these fields after they log in.</p>';
$string['auth_dbfieldpass'] = 'Name of the field containing passwords';
$string['auth_dbfieldpass_key'] = 'Password field';
$string['auth_dbfielduser'] = 'Name of the field containing usernames. This field must be a varchar data type.';
$string['auth_dbfielduser_key'] = 'Username field';
$string['auth_dbhost'] = 'The computer hosting the database server. Use a system DSN entry if using ODBC. Use a PDO DSN entry if using PDO.';
$string['auth_dbhost_key'] = 'Host';
$string['auth_dbchangepasswordurl_key'] = 'Password-change URL';
$string['auth_dbinsertuser'] = 'Inserted user {$a->name} id {$a->id}';
$string['auth_dbinsertuserduplicate'] = 'Error inserting user {$a->username} - user with this username was already created through \'{$a->auth}\' plugin.';
$string['auth_dbinsertusererror'] = 'Error inserting user {$a}';
$string['auth_dbname'] = 'Name of the database itself. Leave empty if using an ODBC DSN. Leave empty if your PDO DSN already contains the database name.';
$string['auth_dbname_key'] = 'DB name';
$string['auth_dbpass'] = 'Password matching the above username';
$string['auth_dbpass_key'] = 'Password';
$string['auth_dbpasstype'] = '<p>Specify the format that the password field is using.</p> <p>Use \'internal\' if you want the external database to manage usernames and email addresses, but Moodle to manage passwords. If you use \'internal\', you must provide a populated email address field in the external database, and you must enable the \auth_db\task\sync_users scheduled task. Moodle will send an email to new users with a temporary password.</p>';
$string['auth_dbpasstype_key'] = 'Password format';
$string['auth_dbreviveduser'] = 'Revived user {$a->name} id {$a->id}';
$string['auth_dbrevivedusererror'] = 'Error reviving user {$a}';
$string['auth_dbsaltedcrypt'] = 'Crypt one-way string hashing';
$string['auth_dbsetupsql'] = 'SQL setup command';
$string['auth_dbsetupsqlhelp'] = 'SQL command for special database setup, often used to setup communication encoding - example for MySQL and PostgreSQL: <em>SET NAMES \'utf8\'</em>';
$string['auth_dbsuspenduser'] = 'Suspended user {$a->name} id {$a->id}';
$string['auth_dbsuspendusererror'] = 'Error suspending user {$a}';
$string['auth_dbsybasequoting'] = 'Use sybase quotes';
$string['auth_dbsybasequotinghelp'] = 'Sybase style single quote escaping - needed for Oracle, MS SQL and some other databases. Do not use for MySQL!';
$string['auth_dbsyncuserstask'] = 'Synchronise users task';
$string['auth_dbtable'] = 'Name of the table in the database';
$string['auth_dbtable_key'] = 'Table';
$string['auth_dbtype'] = 'The database type (see the documentation <a href="http://adodb.org/dokuwiki/doku.php" target="_blank">ADOdb - Database Abstraction Layer for PHP</a> for details).';
$string['auth_dbtype_key'] = 'Database';
$string['auth_dbupdateusers'] = 'Update users';
$string['auth_dbupdateusers_description'] = 'As well as inserting new users, update existing users.';
$string['auth_dbupdatinguser'] = 'Updating user {$a->name} id {$a->id}';
$string['auth_dbuser'] = 'Username with read access to the database';
$string['auth_dbuser_key'] = 'DB user';
$string['auth_dbuserstoadd'] = 'User entries to add: {$a}';
$string['auth_dbuserstoremove'] = 'User entries to remove: {$a}';
$string['auth_dbnoexttable'] = 'External table not specified.';
$string['auth_dbnouserfield'] = 'External user field not specified.';
$string['auth_dbcannotconnect'] = 'Cannot connect to external database.';
$string['auth_dbcannotreadtable'] = 'Cannot read external table.';
$string['auth_dbtableempty'] = 'External table is empty.';
$string['auth_dbcolumnlist'] = 'External table contains the following columns:<br />{$a}';
$string['auth_dbupdateerror'] = 'Error updating external database.';
$string['pluginname'] = 'External database';
$string['privacy:metadata'] = 'The External database authentication plugin does not store any personal data.';
+143
View File
@@ -0,0 +1,143 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Admin settings and defaults.
*
* @package auth_db
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
// We use a couple of custom admin settings since we need to massage the data before it is inserted into the DB.
require_once($CFG->dirroot.'/auth/db/classes/admin_setting_special_auth_configtext.php');
// Needed for constants.
require_once($CFG->libdir.'/authlib.php');
// Introductory explanation.
$settings->add(new admin_setting_heading('auth_db/pluginname', '', new lang_string('auth_dbdescription', 'auth_db')));
// Host.
$settings->add(new admin_setting_configtext('auth_db/host', get_string('auth_dbhost_key', 'auth_db'),
get_string('auth_dbhost', 'auth_db') . ' ' .get_string('auth_multiplehosts', 'auth'),
'127.0.0.1', PARAM_RAW));
// Type.
$dboptions = array();
$dbtypes = array("access", "ado_access", "ado", "ado_mssql", "borland_ibase", "csv", "db2",
"fbsql", "firebird", "ibase", "informix72", "informix", "mssql", "mssql_n", "mssqlnative",
"mysql", "mysqli", "mysqlt", "oci805", "oci8", "oci8po", "odbc", "odbc_mssql", "odbc_oracle",
"oracle", "pdo", "postgres64", "postgres7", "postgres", "proxy", "sqlanywhere", "sybase", "vfp");
foreach ($dbtypes as $dbtype) {
$dboptions[$dbtype] = $dbtype;
}
$settings->add(new admin_setting_configselect('auth_db/type',
new lang_string('auth_dbtype_key', 'auth_db'),
new lang_string('auth_dbtype', 'auth_db'), 'mysqli', $dboptions));
// Sybase quotes.
$yesno = array(
new lang_string('no'),
new lang_string('yes'),
);
$settings->add(new admin_setting_configselect('auth_db/sybasequoting',
new lang_string('auth_dbsybasequoting', 'auth_db'), new lang_string('auth_dbsybasequotinghelp', 'auth_db'), 0, $yesno));
// DB Name.
$settings->add(new admin_setting_configtext('auth_db/name', get_string('auth_dbname_key', 'auth_db'),
get_string('auth_dbname', 'auth_db'), '', PARAM_RAW_TRIMMED));
// DB Username.
$settings->add(new admin_setting_configtext('auth_db/user', get_string('auth_dbuser_key', 'auth_db'),
get_string('auth_dbuser', 'auth_db'), '', PARAM_RAW_TRIMMED));
// Password.
$settings->add(new admin_setting_configpasswordunmask('auth_db/pass', get_string('auth_dbpass_key', 'auth_db'),
get_string('auth_dbpass', 'auth_db'), ''));
// DB Table.
$settings->add(new admin_setting_configtext('auth_db/table', get_string('auth_dbtable_key', 'auth_db'),
get_string('auth_dbtable', 'auth_db'), '', PARAM_RAW_TRIMMED));
// DB User field.
$settings->add(new admin_setting_configtext('auth_db/fielduser', get_string('auth_dbfielduser_key', 'auth_db'),
get_string('auth_dbfielduser', 'auth_db'), '', PARAM_RAW_TRIMMED));
// DB User password.
$settings->add(new admin_setting_configtext('auth_db/fieldpass', get_string('auth_dbfieldpass_key', 'auth_db'),
get_string('auth_dbfieldpass', 'auth_db'), '', PARAM_RAW_TRIMMED));
// DB Password Type.
$passtype = array();
$passtype["plaintext"] = get_string("plaintext", "auth");
$passtype["md5"] = get_string("md5", "auth");
$passtype["sha1"] = get_string("sha1", "auth");
$passtype["saltedcrypt"] = get_string("auth_dbsaltedcrypt", "auth_db");
$passtype["internal"] = get_string("internal", "auth");
$settings->add(new admin_setting_configselect('auth_db/passtype',
new lang_string('auth_dbpasstype_key', 'auth_db'), new lang_string('auth_dbpasstype', 'auth_db'), 'plaintext', $passtype));
// Encoding.
$settings->add(new admin_setting_configtext('auth_db/extencoding', get_string('auth_dbextencoding', 'auth_db'),
get_string('auth_dbextencodinghelp', 'auth_db'), 'utf-8', PARAM_RAW_TRIMMED));
// DB SQL SETUP.
$settings->add(new admin_setting_configtext('auth_db/setupsql', get_string('auth_dbsetupsql', 'auth_db'),
get_string('auth_dbsetupsqlhelp', 'auth_db'), '', PARAM_RAW_TRIMMED));
// Debug ADOOB.
$settings->add(new admin_setting_configselect('auth_db/debugauthdb',
new lang_string('auth_dbdebugauthdb', 'auth_db'), new lang_string('auth_dbdebugauthdbhelp', 'auth_db'), 0, $yesno));
// Password change URL.
$settings->add(new auth_db_admin_setting_special_auth_configtext('auth_db/changepasswordurl',
get_string('auth_dbchangepasswordurl_key', 'auth_db'),
get_string('changepasswordhelp', 'auth'), '', PARAM_URL));
// Label and Sync Options.
$settings->add(new admin_setting_heading('auth_db/usersync', new lang_string('auth_sync_script', 'auth'), ''));
// Sync Options.
$deleteopt = array();
$deleteopt[AUTH_REMOVEUSER_KEEP] = get_string('auth_remove_keep', 'auth');
$deleteopt[AUTH_REMOVEUSER_SUSPEND] = get_string('auth_remove_suspend', 'auth');
$deleteopt[AUTH_REMOVEUSER_FULLDELETE] = get_string('auth_remove_delete', 'auth');
$settings->add(new admin_setting_configselect('auth_db/removeuser',
new lang_string('auth_remove_user_key', 'auth'),
new lang_string('auth_remove_user', 'auth'), AUTH_REMOVEUSER_KEEP, $deleteopt));
// Update users.
$settings->add(new admin_setting_configselect('auth_db/updateusers',
new lang_string('auth_dbupdateusers', 'auth_db'),
new lang_string('auth_dbupdateusers_description', 'auth_db'), 0, $yesno));
// Display locking / mapping of profile fields.
$authplugin = get_auth_plugin('db');
display_auth_lock_options($settings, $authplugin->authtype, $authplugin->userfields,
get_string('auth_dbextrafields', 'auth_db'),
true, true, $authplugin->get_custom_user_profile_fields());
}
+556
View File
@@ -0,0 +1,556 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_db;
/**
* External database auth sync tests, this also tests adodb drivers
* that are matching our four supported Moodle database drivers.
*
* @package auth_db
* @category phpunit
* @copyright 2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class db_test extends \advanced_testcase {
/** @var string Original error log */
protected $oldlog;
/** @var int The amount of users to create for the large user set deletion test */
protected $largedeletionsetsize = 128;
public static function tearDownAfterClass(): void {
global $DB;
// Apply sqlsrv native driver error and logging default
// settings while finishing the AdoDB tests.
if ($DB->get_dbfamily() === 'mssql') {
sqlsrv_configure("WarningsReturnAsErrors", false);
sqlsrv_configure("LogSubsystems", SQLSRV_LOG_SYSTEM_OFF);
sqlsrv_configure("LogSeverity", SQLSRV_LOG_SEVERITY_ERROR);
}
}
protected function init_auth_database() {
global $DB, $CFG;
require_once("$CFG->dirroot/auth/db/auth.php");
// Discard error logs from AdoDB.
$this->oldlog = ini_get('error_log');
ini_set('error_log', "$CFG->dataroot/testlog.log");
$dbman = $DB->get_manager();
set_config('extencoding', 'utf-8', 'auth_db');
set_config('host', $CFG->dbhost, 'auth_db');
set_config('user', $CFG->dbuser, 'auth_db');
set_config('pass', $CFG->dbpass, 'auth_db');
set_config('name', $CFG->dbname, 'auth_db');
if (!empty($CFG->dboptions['dbport'])) {
set_config('host', $CFG->dbhost.':'.$CFG->dboptions['dbport'], 'auth_db');
}
switch ($DB->get_dbfamily()) {
case 'mysql':
set_config('type', 'mysqli', 'auth_db');
set_config('setupsql', "SET NAMES 'UTF-8'", 'auth_db');
set_config('sybasequoting', '0', 'auth_db');
if (!empty($CFG->dboptions['dbsocket'])) {
$dbsocket = $CFG->dboptions['dbsocket'];
if ((strpos($dbsocket, '/') === false and strpos($dbsocket, '\\') === false)) {
$dbsocket = ini_get('mysqli.default_socket');
}
set_config('type', 'mysqli://'.rawurlencode($CFG->dbuser).':'.rawurlencode($CFG->dbpass).'@'.rawurlencode($CFG->dbhost).'/'.rawurlencode($CFG->dbname).'?socket='.rawurlencode($dbsocket), 'auth_db');
}
break;
case 'oracle':
set_config('type', 'oci8po', 'auth_db');
set_config('sybasequoting', '1', 'auth_db');
break;
case 'postgres':
set_config('type', 'postgres7', 'auth_db');
$setupsql = "SET NAMES 'UTF-8'";
if (!empty($CFG->dboptions['dbschema'])) {
$setupsql .= "; SET search_path = '".$CFG->dboptions['dbschema']."'";
}
set_config('setupsql', $setupsql, 'auth_db');
set_config('sybasequoting', '0', 'auth_db');
if (!empty($CFG->dboptions['dbsocket']) and ($CFG->dbhost === 'localhost' or $CFG->dbhost === '127.0.0.1')) {
if (strpos($CFG->dboptions['dbsocket'], '/') !== false) {
$socket = $CFG->dboptions['dbsocket'];
if (!empty($CFG->dboptions['dbport'])) {
$socket .= ':' . $CFG->dboptions['dbport'];
}
set_config('host', $socket, 'auth_db');
} else {
set_config('host', '', 'auth_db');
}
}
break;
case 'mssql':
set_config('type', 'mssqlnative', 'auth_db');
set_config('sybasequoting', '1', 'auth_db');
// The native sqlsrv driver uses a comma as separator between host and port.
$dbhost = $CFG->dbhost;
if (!empty($dboptions['dbport'])) {
$dbhost .= ',' . $dboptions['dbport'];
}
set_config('host', $dbhost, 'auth_db');
break;
default:
throw new exception('Unknown database family ' . $DB->get_dbfamily());
}
$table = new \xmldb_table('auth_db_users');
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null);
$table->add_field('pass', XMLDB_TYPE_CHAR, '255', null, null, null);
$table->add_field('email', XMLDB_TYPE_CHAR, '255', null, null, null);
$table->add_field('firstname', XMLDB_TYPE_CHAR, '255', null, null, null);
$table->add_field('lastname', XMLDB_TYPE_CHAR, '255', null, null, null);
$table->add_field('animal', XMLDB_TYPE_CHAR, '255', null, null, null);
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
if ($dbman->table_exists($table)) {
$dbman->drop_table($table);
}
$dbman->create_table($table);
set_config('table', $CFG->prefix.'auth_db_users', 'auth_db');
set_config('fielduser', 'name', 'auth_db');
set_config('fieldpass', 'pass', 'auth_db');
set_config('field_map_lastname', 'lastname', 'auth_db');
set_config('field_updatelocal_lastname', 'oncreate', 'auth_db');
set_config('field_lock_lastname', 'unlocked', 'auth_db');
// Setu up field mappings.
set_config('field_map_email', 'email', 'auth_db');
set_config('field_updatelocal_email', 'oncreate', 'auth_db');
set_config('field_updateremote_email', '0', 'auth_db');
set_config('field_lock_email', 'unlocked', 'auth_db');
// Create a user profile field and add mapping to it.
$this->getDataGenerator()->create_custom_profile_field(['shortname' => 'pet', 'name' => 'Pet', 'datatype' => 'text']);
set_config('field_map_profile_field_pet', 'animal', 'auth_db');
set_config('field_updatelocal_profile_field_pet', 'oncreate', 'auth_db');
set_config('field_updateremote_profile_field_pet', '0', 'auth_db');
set_config('field_lock_profile_field_pet', 'unlocked', 'auth_db');
// Init the rest of settings.
set_config('passtype', 'plaintext', 'auth_db');
set_config('changepasswordurl', '', 'auth_db');
set_config('debugauthdb', 0, 'auth_db');
set_config('removeuser', AUTH_REMOVEUSER_KEEP, 'auth_db');
}
protected function cleanup_auth_database() {
global $DB;
$dbman = $DB->get_manager();
$table = new \xmldb_table('auth_db_users');
$dbman->drop_table($table);
ini_set('error_log', $this->oldlog);
}
public function test_plugin(): void {
global $DB, $CFG;
require_once($CFG->dirroot . '/user/profile/lib.php');
$this->resetAfterTest(true);
// NOTE: It is strongly discouraged to create new tables in advanced_testcase classes,
// but there is no other simple way to test ext database enrol sync, so let's
// disable transactions are try to cleanup after the tests.
$this->preventResetByRollback();
$this->init_auth_database();
/** @var auth_plugin_db $auth */
$auth = get_auth_plugin('db');
$authdb = $auth->db_init();
// Test adodb may access the table.
$user1 = (object)array('name'=>'u1', 'pass'=>'heslo', 'email'=>'u1@example.com');
$user1->id = $DB->insert_record('auth_db_users', $user1);
$sql = "SELECT * FROM {$auth->config->table}";
$rs = $authdb->Execute($sql);
$this->assertInstanceOf('ADORecordSet', $rs);
$this->assertFalse($rs->EOF);
$fields = $rs->FetchRow();
$this->assertTrue(is_array($fields));
$this->assertTrue($rs->EOF);
$rs->Close();
$authdb->Close();
// Test bulk user account creation.
$user2 = (object)['name' => 'u2', 'pass' => 'heslo', 'email' => 'u2@example.com', 'animal' => 'cat'];
$user2->id = $DB->insert_record('auth_db_users', $user2);
$user3 = (object)array('name'=>'admin', 'pass'=>'heslo', 'email'=>'admin@example.com'); // Should be skipped.
$user3->id = $DB->insert_record('auth_db_users', $user3);
$this->assertCount(2, $DB->get_records('user'));
$trace = new \null_progress_trace();
// Sync users and make sure that two events user_created werer triggered.
$sink = $this->redirectEvents();
$auth->sync_users($trace, false);
$events = $sink->get_events();
$sink->close();
$this->assertCount(2, $events);
$this->assertTrue($events[0] instanceof \core\event\user_created);
$this->assertTrue($events[1] instanceof \core\event\user_created);
// Assert the two users were created.
$this->assertEquals(4, $DB->count_records('user'));
$u1 = $DB->get_record('user', array('username'=>$user1->name, 'auth'=>'db'));
$this->assertSame($user1->email, $u1->email);
$this->assertEmpty(profile_user_record($u1->id)->pet);
$u2 = $DB->get_record('user', array('username'=>$user2->name, 'auth'=>'db'));
$this->assertSame($user2->email, $u2->email);
$this->assertSame($user2->animal, profile_user_record($u2->id)->pet);
$admin = $DB->get_record('user', array('username'=>'admin', 'auth'=>'manual'));
$this->assertNotEmpty($admin);
// Test sync updates.
$user2b = clone($user2);
$user2b->email = 'u2b@example.com';
$user2b->animal = 'dog';
$DB->update_record('auth_db_users', $user2b);
$auth->sync_users($trace, false);
$this->assertEquals(4, $DB->count_records('user'));
$u2 = $DB->get_record('user', array('username'=>$user2->name));
$this->assertSame($user2->email, $u2->email);
$this->assertSame($user2->animal, profile_user_record($u2->id)->pet);
$auth->sync_users($trace, true);
$this->assertEquals(4, $DB->count_records('user'));
$u2 = $DB->get_record('user', array('username'=>$user2->name));
$this->assertSame($user2->email, $u2->email);
set_config('field_updatelocal_email', 'onlogin', 'auth_db');
$auth->config->field_updatelocal_email = 'onlogin';
set_config('field_updatelocal_profile_field_pet', 'onlogin', 'auth_db');
$auth->config->field_updatelocal_profile_field_pet = 'onlogin';
$auth->sync_users($trace, false);
$this->assertEquals(4, $DB->count_records('user'));
$u2 = $DB->get_record('user', array('username'=>$user2->name));
$this->assertSame($user2->email, $u2->email);
$auth->sync_users($trace, true);
$this->assertEquals(4, $DB->count_records('user'));
$u2 = $DB->get_record('user', array('username'=>$user2->name));
$this->assertSame($user2b->email, $u2->email);
$this->assertSame($user2b->animal, profile_user_record($u2->id)->pet);
// Test sync deletes and suspends.
$DB->delete_records('auth_db_users', array('id'=>$user2->id));
$this->assertCount(2, $DB->get_records('auth_db_users'));
unset($user2);
unset($user2b);
$auth->sync_users($trace, false);
$this->assertEquals(4, $DB->count_records('user'));
$this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
$this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
set_config('removeuser', AUTH_REMOVEUSER_SUSPEND, 'auth_db');
$auth->config->removeuser = AUTH_REMOVEUSER_SUSPEND;
$auth->sync_users($trace, false);
$this->assertEquals(4, $DB->count_records('user'));
$this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
$this->assertEquals(1, $DB->count_records('user', array('suspended'=>1)));
$user2 = (object)array('name'=>'u2', 'pass'=>'heslo', 'email'=>'u2@example.com');
$user2->id = $DB->insert_record('auth_db_users', $user2);
$auth->sync_users($trace, false);
$this->assertEquals(4, $DB->count_records('user'));
$this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
$this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
$DB->delete_records('auth_db_users', array('id'=>$user2->id));
set_config('removeuser', AUTH_REMOVEUSER_FULLDELETE, 'auth_db');
$auth->config->removeuser = AUTH_REMOVEUSER_FULLDELETE;
$auth->sync_users($trace, false);
$this->assertEquals(4, $DB->count_records('user'));
$this->assertEquals(1, $DB->count_records('user', array('deleted'=>1)));
$this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
$user2 = (object)array('name'=>'u2', 'pass'=>'heslo', 'email'=>'u2@example.com');
$user2->id = $DB->insert_record('auth_db_users', $user2);
$auth->sync_users($trace, false);
$this->assertEquals(5, $DB->count_records('user'));
$this->assertEquals(1, $DB->count_records('user', array('deleted'=>1)));
$this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
// Test user_login().
$user3 = (object)array('name'=>'u3', 'pass'=>'heslo', 'email'=>'u3@example.com');
$user3->id = $DB->insert_record('auth_db_users', $user3);
$this->assertFalse($auth->user_login('u4', 'heslo'));
$this->assertTrue($auth->user_login('u1', 'heslo'));
$this->assertFalse($DB->record_exists('user', array('username'=>'u3', 'auth'=>'db')));
$this->assertTrue($auth->user_login('u3', 'heslo'));
$this->assertFalse($DB->record_exists('user', array('username'=>'u3', 'auth'=>'db')));
set_config('passtype', 'md5', 'auth_db');
$auth->config->passtype = 'md5';
$user3->pass = md5('heslo');
$DB->update_record('auth_db_users', $user3);
$this->assertTrue($auth->user_login('u3', 'heslo'));
// Test user created to see if the checking happens strictly.
$usermd5 = (object)['name' => 'usermd5', 'pass' => '0e462097431906509019562988736854'];
$usermd5->id = $DB->insert_record('auth_db_users', $usermd5);
// md5('240610708') === '0e462097431906509019562988736854'.
$this->assertTrue($auth->user_login('usermd5', '240610708'));
$this->assertFalse($auth->user_login('usermd5', 'QNKCDZO'));
set_config('passtype', 'sh1', 'auth_db');
$auth->config->passtype = 'sha1';
$user3->pass = sha1('heslo');
$DB->update_record('auth_db_users', $user3);
$this->assertTrue($auth->user_login('u3', 'heslo'));
// Test user created to see if the checking happens strictly.
$usersha1 = (object)['name' => 'usersha1', 'pass' => '0e66507019969427134894567494305185566735'];
$usersha1->id = $DB->insert_record('auth_db_users', $usersha1);
// sha1('aaroZmOk') === '0e66507019969427134894567494305185566735'.
$this->assertTrue($auth->user_login('usersha1', 'aaroZmOk'));
$this->assertFalse($auth->user_login('usersha1', 'aaK1STfY'));
set_config('passtype', 'saltedcrypt', 'auth_db');
$auth->config->passtype = 'saltedcrypt';
$user3->pass = password_hash('heslo', PASSWORD_BCRYPT);
$DB->update_record('auth_db_users', $user3);
$this->assertTrue($auth->user_login('u3', 'heslo'));
set_config('passtype', 'internal', 'auth_db');
$auth->config->passtype = 'internal';
create_user_record('u3', 'heslo', 'db');
$this->assertTrue($auth->user_login('u3', 'heslo'));
$DB->delete_records('auth_db_users', array('id'=>$user3->id));
set_config('removeuser', AUTH_REMOVEUSER_KEEP, 'auth_db');
$auth->config->removeuser = AUTH_REMOVEUSER_KEEP;
$this->assertTrue($auth->user_login('u3', 'heslo'));
set_config('removeuser', AUTH_REMOVEUSER_SUSPEND, 'auth_db');
$auth->config->removeuser = AUTH_REMOVEUSER_SUSPEND;
$this->assertFalse($auth->user_login('u3', 'heslo'));
set_config('removeuser', AUTH_REMOVEUSER_FULLDELETE, 'auth_db');
$auth->config->removeuser = AUTH_REMOVEUSER_FULLDELETE;
$this->assertFalse($auth->user_login('u3', 'heslo'));
set_config('passtype', 'sh1', 'auth_db');
$auth->config->passtype = 'sha1';
$this->assertFalse($auth->user_login('u3', 'heslo'));
// Test login create and update.
$user4 = (object)array('name'=>'u4', 'pass'=>'heslo', 'email'=>'u4@example.com');
$user4->id = $DB->insert_record('auth_db_users', $user4);
set_config('passtype', 'plaintext', 'auth_db');
$auth->config->passtype = 'plaintext';
$iuser4 = create_user_record('u4', 'heslo', 'db');
$this->assertNotEmpty($iuser4);
$this->assertSame($user4->name, $iuser4->username);
$this->assertSame($user4->email, $iuser4->email);
$this->assertSame('db', $iuser4->auth);
$this->assertSame($CFG->mnet_localhost_id, $iuser4->mnethostid);
$user4b = clone($user4);
$user4b->email = 'u4b@example.com';
$DB->update_record('auth_db_users', $user4b);
set_config('field_updatelocal_email', 'oncreate', 'auth_db');
$auth->config->field_updatelocal_email = 'oncreate';
update_user_record('u4');
$iuser4 = $DB->get_record('user', array('id'=>$iuser4->id));
$this->assertSame($user4->email, $iuser4->email);
set_config('field_updatelocal_email', 'onlogin', 'auth_db');
$auth->config->field_updatelocal_email = 'onlogin';
update_user_record('u4');
$iuser4 = $DB->get_record('user', array('id'=>$iuser4->id));
$this->assertSame($user4b->email, $iuser4->email);
// Test user_exists()
$this->assertTrue($auth->user_exists('u1'));
$this->assertTrue($auth->user_exists('admin'));
$this->assertFalse($auth->user_exists('u3'));
$this->assertTrue($auth->user_exists('u4'));
$this->cleanup_auth_database();
}
/**
* Testing the function _colonscope() from ADOdb.
*/
public function test_adodb_colonscope(): void {
global $CFG;
require_once($CFG->libdir.'/adodb/adodb.inc.php');
require_once($CFG->libdir.'/adodb/drivers/adodb-odbc.inc.php');
require_once($CFG->libdir.'/adodb/drivers/adodb-db2ora.inc.php');
$this->resetAfterTest(false);
$sql = "select * from table WHERE column=:1 AND anothercolumn > :0";
$arr = array('b', 1);
list($sqlout, $arrout) = _colonscope($sql,$arr);
$this->assertEquals("select * from table WHERE column=? AND anothercolumn > ?", $sqlout);
$this->assertEquals(array(1, 'b'), $arrout);
}
/**
* Testing the clean_data() method.
*/
public function test_clean_data(): void {
global $DB;
$this->resetAfterTest(false);
$this->preventResetByRollback();
$this->init_auth_database();
$auth = get_auth_plugin('db');
$auth->db_init();
// Create users on external table.
$extdbuser1 = (object)array('name'=>'u1', 'pass'=>'heslo', 'email'=>'u1@example.com');
$extdbuser1->id = $DB->insert_record('auth_db_users', $extdbuser1);
// User with malicious data on the name (won't be imported).
$extdbuser2 = (object)array('name'=>'user<script>alert(1);</script>xss', 'pass'=>'heslo', 'email'=>'xssuser@example.com');
$extdbuser2->id = $DB->insert_record('auth_db_users', $extdbuser2);
$extdbuser3 = (object)array('name'=>'u3', 'pass'=>'heslo', 'email'=>'u3@example.com',
'lastname' => 'user<script>alert(1);</script>xss');
$extdbuser3->id = $DB->insert_record('auth_db_users', $extdbuser3);
$trace = new \null_progress_trace();
// Let's test user sync make sure still works as expected..
$auth->sync_users($trace, true);
$this->assertDebuggingCalled("The property 'lastname' has invalid data and has been cleaned.");
// User with correct data, should be equal to external db.
$user1 = $DB->get_record('user', array('email'=> $extdbuser1->email, 'auth'=>'db'));
$this->assertEquals($extdbuser1->name, $user1->username);
$this->assertEquals($extdbuser1->email, $user1->email);
// Get the user on moodle user table.
$user2 = $DB->get_record('user', array('email'=> $extdbuser2->email, 'auth'=>'db'));
$user3 = $DB->get_record('user', array('email'=> $extdbuser3->email, 'auth'=>'db'));
$this->assertEmpty($user2);
$this->assertEquals($extdbuser3->name, $user3->username);
$this->assertEquals('useralert(1);xss', $user3->lastname);
$this->cleanup_auth_database();
}
/**
* Testing the deletion of a user when there are many users in the external DB.
*/
public function test_deleting_with_many_users(): void {
global $DB;
$this->resetAfterTest(true);
$this->preventResetByRollback();
$this->init_auth_database();
$auth = get_auth_plugin('db');
$auth->db_init();
// Set to delete from moodle when missing from DB.
set_config('removeuser', AUTH_REMOVEUSER_FULLDELETE, 'auth_db');
$auth->config->removeuser = AUTH_REMOVEUSER_FULLDELETE;
// Create users.
$users = [];
for ($i = 0; $i < $this->largedeletionsetsize; $i++) {
$user = (object)array('username' => "u$i", 'name' => "u$i", 'pass' => 'heslo', 'email' => "u$i@example.com");
$user->id = $DB->insert_record('auth_db_users', $user);
$users[] = $user;
}
// Sync to moodle.
$trace = new \null_progress_trace();
$auth->sync_users($trace, true);
// Check user is there.
$user = array_shift($users);
$moodleuser = $DB->get_record('user', array('email' => $user->email, 'auth' => 'db'));
$this->assertNotNull($moodleuser);
$this->assertEquals($user->username, $moodleuser->username);
// Delete a user.
$DB->delete_records('auth_db_users', array('id' => $user->id));
// Sync again.
$auth->sync_users($trace, true);
// Check user is no longer there.
$moodleuser = $DB->get_record('user', array('id' => $moodleuser->id));
$this->assertFalse($auth->user_login($user->username, 'heslo'));
$this->assertEquals(1, $moodleuser->deleted);
// Make sure it was the only user deleted.
$numberdeleted = $DB->count_records('user', array('deleted' => 1, 'auth' => 'db'));
$this->assertEquals(1, $numberdeleted);
$this->cleanup_auth_database();
}
}
+21
View File
@@ -0,0 +1,21 @@
This files describes API changes in /auth/db/*,
information provided here is intended especially for developers.
=== 3.3 ===
* The config.html file was migrated to use the admin settings API.
The identifier for configuration data stored in config_plugins table was converted from 'auth/db' to 'auth_db'.
=== 3.1 ===
* The auth_plugin_db::clean_data() has been deprecated and will be removed
in a future version. Please update to use core_user::clean_data()
instead.
=== 2.9 ===
Some alterations have been made to the handling of case sensitity handling of passwords
and password hashes which previously varied depending on database configuration:
* Plain text password matching is now always case sensitive
* sha1/md5 hash comparisons are now enforced case insensitive (as underlying they are hexidecimal values)
+29
View File
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Version details
*
* @package auth_db
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'auth_db'; // Full name of the plugin (used for diagnostics)
+259
View File
@@ -0,0 +1,259 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Authentication Plugin: Email Authentication
*
* @author Martin Dougiamas
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package auth_email
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/authlib.php');
/**
* Email authentication plugin.
*/
class auth_plugin_email extends auth_plugin_base {
/**
* Constructor.
*/
public function __construct() {
$this->authtype = 'email';
$this->config = get_config('auth_email');
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function auth_plugin_email() {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct();
}
/**
* Returns true if the username and password work and false if they are
* wrong or don't exist.
*
* @param string $username The username
* @param string $password The password
* @return bool Authentication success or failure.
*/
function user_login($username, $password) {
global $CFG, $DB;
if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id))) {
return validate_internal_user_password($user, $password);
}
return false;
}
/**
* Updates the user's password.
*
* called when the user password is updated.
*
* @param object $user User table object (with system magic quotes)
* @param string $newpassword Plaintext password (with system magic quotes)
* @return boolean result
*
*/
function user_update_password($user, $newpassword) {
$user = get_complete_user_data('id', $user->id);
// This will also update the stored hash to the latest algorithm
// if the existing hash is using an out-of-date algorithm (or the
// legacy md5 algorithm).
return update_internal_user_password($user, $newpassword);
}
function can_signup() {
return true;
}
/**
* Sign up a new user ready for confirmation.
* Password is passed in plaintext.
*
* @param object $user new user object
* @param boolean $notify print notice with link and terminate
*/
function user_signup($user, $notify=true) {
// Standard signup, without custom confirmatinurl.
return $this->user_signup_with_confirmation($user, $notify);
}
/**
* Sign up a new user ready for confirmation.
*
* Password is passed in plaintext.
* A custom confirmationurl could be used.
*
* @param object $user new user object
* @param boolean $notify print notice with link and terminate
* @param string $confirmationurl user confirmation URL
* @return boolean true if everything well ok and $notify is set to true
* @throws moodle_exception
* @since Moodle 3.2
*/
public function user_signup_with_confirmation($user, $notify=true, $confirmationurl = null) {
global $CFG, $DB, $SESSION;
require_once($CFG->dirroot.'/user/profile/lib.php');
require_once($CFG->dirroot.'/user/lib.php');
$plainpassword = $user->password;
$user->password = hash_internal_user_password($user->password);
if (empty($user->calendartype)) {
$user->calendartype = $CFG->calendartype;
}
$user->id = user_create_user($user, false, false);
user_add_password_history($user->id, $plainpassword);
// Save any custom profile field information.
profile_save_data($user);
// Save wantsurl against user's profile, so we can return them there upon confirmation.
if (!empty($SESSION->wantsurl)) {
set_user_preference('auth_email_wantsurl', $SESSION->wantsurl, $user);
}
// Trigger event.
\core\event\user_created::create_from_userid($user->id)->trigger();
if (! send_confirmation_email($user, $confirmationurl)) {
throw new \moodle_exception('auth_emailnoemail', 'auth_email');
}
if ($notify) {
global $CFG, $PAGE, $OUTPUT;
$emailconfirm = get_string('emailconfirm');
$PAGE->navbar->add($emailconfirm);
$PAGE->set_title($emailconfirm);
$PAGE->set_heading($PAGE->course->fullname);
echo $OUTPUT->header();
notice(get_string('emailconfirmsent', '', $user->email), "$CFG->wwwroot/index.php");
} else {
return true;
}
}
/**
* Returns true if plugin allows confirming of new users.
*
* @return bool
*/
function can_confirm() {
return true;
}
/**
* Confirm the new user as registered.
*
* @param string $username
* @param string $confirmsecret
*/
function user_confirm($username, $confirmsecret) {
global $DB, $SESSION;
$user = get_complete_user_data('username', $username);
if (!empty($user)) {
if ($user->auth != $this->authtype) {
return AUTH_CONFIRM_ERROR;
} else if ($user->secret === $confirmsecret && $user->confirmed) {
return AUTH_CONFIRM_ALREADY;
} else if ($user->secret === $confirmsecret) { // They have provided the secret key to get in
$DB->set_field("user", "confirmed", 1, array("id"=>$user->id));
if ($wantsurl = get_user_preferences('auth_email_wantsurl', false, $user)) {
// Ensure user gets returned to page they were trying to access before signing up.
$SESSION->wantsurl = $wantsurl;
unset_user_preference('auth_email_wantsurl', $user);
}
return AUTH_CONFIRM_OK;
}
} else {
return AUTH_CONFIRM_ERROR;
}
}
function prevent_local_passwords() {
return false;
}
/**
* Returns true if this authentication plugin is 'internal'.
*
* @return bool
*/
function is_internal() {
return true;
}
/**
* Returns true if this authentication plugin can change the user's
* password.
*
* @return bool
*/
function can_change_password() {
return true;
}
/**
* Returns the URL for changing the user's pw, or empty if the default can
* be used.
*
* @return moodle_url
*/
function change_password_url() {
return null; // use default internal method
}
/**
* Returns true if plugin allows resetting of internal password.
*
* @return bool
*/
function can_reset_password() {
return true;
}
/**
* Returns true if plugin can be manually set.
*
* @return bool
*/
function can_be_manually_set() {
return true;
}
/**
* Returns whether or not the captcha element is enabled.
* @return bool
*/
function is_captcha_enabled() {
return get_config("auth_{$this->authtype}", 'recaptcha');
}
}
+384
View File
@@ -0,0 +1,384 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Auth e-mail external API
*
* @package auth_email
* @category external
* @copyright 2016 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.2
*/
use core_external\external_api;
use core_external\external_format_value;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->libdir . '/authlib.php');
require_once($CFG->dirroot . '/user/editlib.php');
require_once($CFG->dirroot . '/user/profile/lib.php');
/**
* Auth e-mail external functions
*
* @package auth_email
* @category external
* @copyright 2016 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.2
*/
class auth_email_external extends external_api {
/**
* Check if registration is enabled in this site.
*
* @throws moodle_exception
* @since Moodle 3.2
*/
protected static function check_signup_enabled() {
global $CFG;
if (empty($CFG->registerauth) or $CFG->registerauth != 'email') {
throw new moodle_exception('registrationdisabled', 'error');
}
}
/**
* Describes the parameters for get_signup_settings.
*
* @return external_function_parameters
* @since Moodle 3.2
*/
public static function get_signup_settings_parameters() {
return new external_function_parameters(array());
}
/**
* Get the signup required settings and profile fields.
*
* @return array settings and possible warnings
* @since Moodle 3.2
* @throws moodle_exception
*/
public static function get_signup_settings() {
global $CFG, $PAGE;
$context = context_system::instance();
// We need this to make work the format text functions.
$PAGE->set_context($context);
self::check_signup_enabled();
$result = array();
$result['namefields'] = useredit_get_required_name_fields();
if (!empty($CFG->passwordpolicy)) {
$result['passwordpolicy'] = print_password_policy();
}
$manager = new \core_privacy\local\sitepolicy\manager();
if ($sitepolicy = $manager->get_embed_url()) {
$result['sitepolicy'] = $sitepolicy->out(false);
}
if (!empty($CFG->sitepolicyhandler)) {
$result['sitepolicyhandler'] = $CFG->sitepolicyhandler;
}
if (!empty($CFG->defaultcity)) {
$result['defaultcity'] = $CFG->defaultcity;
}
if (!empty($CFG->country)) {
$result['country'] = $CFG->country;
}
$result['extendedusernamechars'] = !empty($CFG->extendedusernamechars);
if ($fields = profile_get_signup_fields()) {
$result['profilefields'] = array();
foreach ($fields as $field) {
$fielddata = $field->object->get_field_config_for_external();
$fielddata['categoryname'] = \core_external\util::format_string($field->categoryname, $context->id);
$fielddata['name'] = \core_external\util::format_string($fielddata['name'], $context->id);
list($fielddata['defaultdata'], $fielddata['defaultdataformat']) =
\core_external\util::format_text($fielddata['defaultdata'], $fielddata['defaultdataformat'], $context->id);
$result['profilefields'][] = $fielddata;
}
}
if (signup_captcha_enabled()) {
// With reCAPTCHA v2 the captcha will be rendered by the mobile client using just the publickey.
$result['recaptchapublickey'] = $CFG->recaptchapublickey;
}
$result['warnings'] = array();
return $result;
}
/**
* Describes the get_signup_settings return value.
*
* @return external_single_structure
* @since Moodle 3.2
*/
public static function get_signup_settings_returns() {
return new external_single_structure(
[
'namefields' => new external_multiple_structure(
new external_value(PARAM_NOTAGS, 'The order of the name fields')
),
'passwordpolicy' => new external_value(PARAM_RAW, 'Password policy', VALUE_OPTIONAL),
'sitepolicy' => new external_value(PARAM_RAW, 'Site policy', VALUE_OPTIONAL),
'sitepolicyhandler' => new external_value(PARAM_PLUGIN, 'Site policy handler', VALUE_OPTIONAL),
'defaultcity' => new external_value(PARAM_NOTAGS, 'Default city', VALUE_OPTIONAL),
'country' => new external_value(PARAM_ALPHA, 'Default country', VALUE_OPTIONAL),
'extendedusernamechars' => new external_value(
PARAM_BOOL, 'Extended characters in usernames or not', VALUE_OPTIONAL
),
'profilefields' => new external_multiple_structure(
new external_single_structure(
[
'id' => new external_value(PARAM_INT, 'Profile field id', VALUE_OPTIONAL),
'shortname' => new external_value(PARAM_ALPHANUMEXT, 'Profile field shortname', VALUE_OPTIONAL),
'name' => new external_value(PARAM_RAW, 'Profield field name', VALUE_OPTIONAL),
'datatype' => new external_value(PARAM_ALPHANUMEXT, 'Profield field datatype', VALUE_OPTIONAL),
'description' => new external_value(PARAM_RAW, 'Profield field description', VALUE_OPTIONAL),
'descriptionformat' => new external_format_value('description'),
'categoryid' => new external_value(PARAM_INT, 'Profield field category id', VALUE_OPTIONAL),
'categoryname' => new external_value(PARAM_RAW, 'Profield field category name', VALUE_OPTIONAL),
'sortorder' => new external_value(PARAM_INT, 'Profield field sort order', VALUE_OPTIONAL),
'required' => new external_value(PARAM_INT, 'Profield field required', VALUE_OPTIONAL),
'locked' => new external_value(PARAM_INT, 'Profield field locked', VALUE_OPTIONAL),
'visible' => new external_value(PARAM_INT, 'Profield field visible', VALUE_OPTIONAL),
'forceunique' => new external_value(PARAM_INT, 'Profield field unique', VALUE_OPTIONAL),
'signup' => new external_value(PARAM_INT, 'Profield field in signup form', VALUE_OPTIONAL),
'defaultdata' => new external_value(PARAM_RAW, 'Profield field default data', VALUE_OPTIONAL),
'defaultdataformat' => new external_format_value('defaultdata'),
'param1' => new external_value(PARAM_RAW, 'Profield field settings', VALUE_OPTIONAL),
'param2' => new external_value(PARAM_RAW, 'Profield field settings', VALUE_OPTIONAL),
'param3' => new external_value(PARAM_RAW, 'Profield field settings', VALUE_OPTIONAL),
'param4' => new external_value(PARAM_RAW, 'Profield field settings', VALUE_OPTIONAL),
'param5' => new external_value(PARAM_RAW, 'Profield field settings', VALUE_OPTIONAL),
]
), 'Required profile fields', VALUE_OPTIONAL
),
'recaptchapublickey' => new external_value(PARAM_RAW, 'Recaptcha public key', VALUE_OPTIONAL),
'recaptchachallengehash' => new external_value(PARAM_RAW, 'Recaptcha challenge hash', VALUE_OPTIONAL),
'recaptchachallengeimage' => new external_value(PARAM_URL, 'Recaptcha challenge noscript image', VALUE_OPTIONAL),
'recaptchachallengejs' => new external_value(PARAM_URL, 'Recaptcha challenge js url', VALUE_OPTIONAL),
'warnings' => new external_warnings(),
]
);
}
/**
* Describes the parameters for signup_user.
*
* @return external_function_parameters
* @since Moodle 3.2
*/
public static function signup_user_parameters() {
return new external_function_parameters(
array(
'username' => new external_value(core_user::get_property_type('username'), 'Username'),
'password' => new external_value(core_user::get_property_type('password'), 'Plain text password'),
'firstname' => new external_value(core_user::get_property_type('firstname'), 'The first name(s) of the user'),
'lastname' => new external_value(core_user::get_property_type('lastname'), 'The family name of the user'),
'email' => new external_value(core_user::get_property_type('email'), 'A valid and unique email address'),
'city' => new external_value(core_user::get_property_type('city'), 'Home city of the user', VALUE_DEFAULT, ''),
'country' => new external_value(core_user::get_property_type('country'), 'Home country code', VALUE_DEFAULT, ''),
'recaptchachallengehash' => new external_value(PARAM_RAW, 'Recaptcha challenge hash', VALUE_DEFAULT, ''),
'recaptcharesponse' => new external_value(PARAM_NOTAGS, 'Recaptcha response', VALUE_DEFAULT, ''),
'customprofilefields' => new external_multiple_structure(
new external_single_structure(
array(
'type' => new external_value(PARAM_ALPHANUMEXT, 'The type of the custom field'),
'name' => new external_value(PARAM_ALPHANUMEXT, 'The name of the custom field'),
'value' => new external_value(PARAM_RAW, 'Custom field value, can be an encoded json if required')
)
), 'User custom fields (also known as user profile fields)', VALUE_DEFAULT, array()
),
'redirect' => new external_value(PARAM_LOCALURL, 'Redirect the user to this site url after confirmation.',
VALUE_DEFAULT, ''),
)
);
}
/**
* Get the signup required settings and profile fields.
*
* @param string $username username
* @param string $password plain text password
* @param string $firstname the first name(s) of the user
* @param string $lastname the family name of the user
* @param string $email a valid and unique email address
* @param string $city home city of the user
* @param string $country home country code
* @param string $recaptchachallengehash recaptcha challenge hash
* @param string $recaptcharesponse recaptcha response
* @param array $customprofilefields user custom fields (also known as user profile fields)
* @param string $redirect Site url to redirect the user after confirmation
* @return array settings and possible warnings
* @since Moodle 3.2
* @throws moodle_exception
* @throws invalid_parameter_exception
*/
public static function signup_user($username, $password, $firstname, $lastname, $email, $city = '', $country = '',
$recaptchachallengehash = '', $recaptcharesponse = '', $customprofilefields = array(),
$redirect = '') {
global $CFG, $PAGE;
$warnings = array();
$params = self::validate_parameters(
self::signup_user_parameters(),
array(
'username' => $username,
'password' => $password,
'firstname' => $firstname,
'lastname' => $lastname,
'email' => $email,
'city' => $city,
'country' => $country,
'recaptchachallengehash' => $recaptchachallengehash,
'recaptcharesponse' => $recaptcharesponse,
'customprofilefields' => $customprofilefields,
'redirect' => $redirect,
)
);
// We need this to make work the format text functions.
$context = context_system::instance();
$PAGE->set_context($context);
self::check_signup_enabled();
// Validate profile fields param types.
$allowedfields = profile_get_signup_fields();
$fieldproperties = array();
$fieldsrequired = array();
foreach ($allowedfields as $field) {
$fieldproperties[$field->object->inputname] = $field->object->get_field_properties();
if ($field->object->is_required()) {
$fieldsrequired[$field->object->inputname] = true;
}
}
foreach ($params['customprofilefields'] as $profilefield) {
if (!array_key_exists($profilefield['name'], $fieldproperties)) {
throw new invalid_parameter_exception('Invalid field' . $profilefield['name']);
}
list($type, $allownull) = $fieldproperties[$profilefield['name']];
validate_param($profilefield['value'], $type, $allownull);
// Remove from the potential required list.
if (isset($fieldsrequired[$profilefield['name']])) {
unset($fieldsrequired[$profilefield['name']]);
}
}
if (!empty($fieldsrequired)) {
throw new invalid_parameter_exception('Missing required parameters: ' . implode(',', array_keys($fieldsrequired)));
}
// Validate the data sent.
$data = $params;
$data['email2'] = $data['email'];
// Force policy agreed if a site policy is set. The client is responsible of implementing the interface check.
$manager = new \core_privacy\local\sitepolicy\manager();
if ($manager->is_defined()) {
$data['policyagreed'] = 1;
}
unset($data['recaptcharesponse']);
unset($data['customprofilefields']);
// Add profile fields data.
foreach ($params['customprofilefields'] as $profilefield) {
// First, check if the value is a json (some profile fields like text area uses an array for sending data).
$datadecoded = json_decode($profilefield['value'], true);
if (is_array($datadecoded) && (json_last_error() == JSON_ERROR_NONE)) {
$data[$profilefield['name']] = $datadecoded;
} else {
$data[$profilefield['name']] = $profilefield['value'];
}
}
$errors = signup_validate_data($data, array());
// Validate recaptcha.
if (signup_captcha_enabled()) {
require_once($CFG->libdir . '/recaptchalib_v2.php');
$response = recaptcha_check_response(RECAPTCHA_VERIFY_URL, $CFG->recaptchaprivatekey,
getremoteaddr(), $params['recaptcharesponse']);
if (!$response['isvalid']) {
$errors['recaptcharesponse'] = $response['error'];
}
}
if (!empty($errors)) {
foreach ($errors as $itemname => $message) {
$warnings[] = array(
'item' => $itemname,
'itemid' => 0,
'warningcode' => 'fielderror',
'message' => s($message)
);
}
$result = array(
'success' => false,
'warnings' => $warnings,
);
} else {
// Save the user.
$user = signup_setup_new_user((object) $data);
$authplugin = get_auth_plugin('email');
// Check if we should redirect the user once the user is confirmed.
$confirmationurl = null;
if (!empty($params['redirect'])) {
// Pass via moodle_url to fix thinks like admin links.
$redirect = new moodle_url($params['redirect']);
$confirmationurl = new moodle_url('/login/confirm.php', array('redirect' => $redirect->out()));
}
$authplugin->user_signup_with_confirmation($user, false, $confirmationurl);
$result = array(
'success' => true,
'warnings' => array(),
);
}
return $result;
}
/**
* Describes the signup_user return value.
*
* @return external_single_structure
* @since Moodle 3.2
*/
public static function signup_user_returns() {
return new external_single_structure(
array(
'success' => new external_value(PARAM_BOOL, 'True if the user was created false otherwise'),
'warnings' => new external_warnings(),
)
);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy Subsystem implementation for auth_email.
*
* @package auth_email
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_email\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for auth_email implementing null_provider.
*
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Auth email webservice definitions.
*
* @package auth_email
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$functions = array(
'auth_email_get_signup_settings' => array(
'classname' => 'auth_email_external',
'methodname' => 'get_signup_settings',
'description' => 'Get the signup required settings and profile fields.',
'type' => 'read',
'ajax' => true,
'loginrequired' => false,
),
'auth_email_signup_user' => array(
'classname' => 'auth_email_external',
'methodname' => 'signup_user',
'description' => 'Adds a new user (pendingto be confirmed) in the site.',
'type' => 'write',
'ajax' => true,
'loginrequired' => false,
),
);
+44
View File
@@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* No authentication plugin upgrade code
*
* @package auth_email
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Function to upgrade auth_email.
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_auth_email_upgrade($oldversion) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+31
View File
@@ -0,0 +1,31 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'auth_email', language 'en'.
*
* @package auth_email
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['auth_emaildescription'] = '<p>Email-based self-registration enables a user to create their own account via a \'Create new account\' button on the login page. The user then receives an email containing a secure link to a page where they can confirm their account. Future logins just check the username and password against the stored values in the Moodle database.</p><p>Note: In addition to enabling the plugin, email-based self-registration must also be selected from the self registration drop-down menu on the \'Manage authentication\' page.</p>';
$string['auth_emailnoemail'] = 'Tried to send you an email but failed!';
$string['auth_emailrecaptcha'] = 'Adds a visual/audio confirmation form element to the sign-up page for email self-registering users. This protects your site against spammers and contributes to a worthwhile cause. See https://www.google.com/recaptcha for more details.';
$string['auth_emailrecaptcha_key'] = 'Enable reCAPTCHA element';
$string['auth_emailsettings'] = 'Settings';
$string['pluginname'] = 'Email-based self-registration';
$string['privacy:metadata'] = 'The Email-based self-registration authentication plugin does not store any personal data.';
+46
View File
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Admin settings and defaults.
*
* @package auth_email
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
// Introductory explanation.
$settings->add(new admin_setting_heading('auth_email/pluginname', '',
new lang_string('auth_emaildescription', 'auth_email')));
$options = array(
new lang_string('no'),
new lang_string('yes'),
);
$settings->add(new admin_setting_configselect('auth_email/recaptcha',
new lang_string('auth_emailrecaptcha_key', 'auth_email'),
new lang_string('auth_emailrecaptcha', 'auth_email'), 0, $options));
// Display locking / mapping of profile fields.
$authplugin = get_auth_plugin('email');
display_auth_lock_options($settings, $authplugin->authtype, $authplugin->userfields,
get_string('auth_fieldlocks_help', 'auth'), false, false);
}
@@ -0,0 +1,54 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Step definition for auth_email
*
* @package auth_email
* @category test
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
/**
* Step definition for auth_email.
*
* @package auth_email
* @category test
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_auth_email extends behat_base {
/**
* Emulate clicking on confirmation link from the email
*
* @When /^I confirm email for "(?P<username>(?:[^"]|\\")*)"$/
*
* @param string $username
*/
public function i_confirm_email_for($username) {
global $DB;
$secret = $DB->get_field('user', 'secret', ['username' => $username], MUST_EXIST);
$confirmationurl = new moodle_url('/login/confirm.php');
$confirmationpath = $confirmationurl->out_as_local_url(false);
$url = $confirmationpath . '?' . 'data='. $secret .'/'. $username;
$this->execute('behat_general::i_visit', [$url]);
}
}
+98
View File
@@ -0,0 +1,98 @@
@auth @auth_email
Feature: User must accept policy when logging in and signing up
In order to record user agreement to use the site
As a user
I need to be able to accept site policy during sign up
Scenario: Accept policy on sign up, no site policy
Given the following config values are set as admin:
| registerauth | email |
| passwordpolicy | 0 |
And I am on site homepage
And I follow "Log in"
When I click on "Create new account" "link"
Then I should not see "I understand and agree"
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| First name | User1 |
| Last name | L1 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I confirm email for "user1"
And I should see "Thanks, User1 L1"
And I should see "Your registration has been confirmed"
And I open my profile in edit mode
And the field "First name" matches value "User1"
And I log out
# Confirm that user can login and browse the site (edit their profile).
And I log in as "user1"
And I open my profile in edit mode
And the field "First name" matches value "User1"
Scenario: Accept policy on sign up, with site policy
Given the following config values are set as admin:
| registerauth | email |
| passwordpolicy | 0 |
| sitepolicy | https://moodle.org |
And I am on site homepage
And I follow "Log in"
When I click on "Create new account" "link"
Then the field "I understand and agree" matches value "0"
And I set the following fields to these values:
| Username | user1 |
| Password | user1 |
| Email address | user1@address.invalid |
| Email (again) | user1@address.invalid |
| First name | User1 |
| Last name | L1 |
| I understand and agree | 1 |
And I press "Create my new account"
And I should see "Confirm your account"
And I should see "An email should have been sent to your address at user1@address.invalid"
And I confirm email for "user1"
And I should see "Thanks, User1 L1"
And I should see "Your registration has been confirmed"
And I open my profile in edit mode
And the field "First name" matches value "User1"
And I log out
# Confirm that user is not asked to agree to site policy again after the next login.
And I log in as "user1"
And I open my profile in edit mode
And the field "First name" matches value "User1"
Scenario Outline: Email validation during email registration
Given the following config values are set as admin:
| allowaccountssameemail | <allowsameemail> |
| registerauth | email |
| passwordpolicy | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| s1 | John | Doe | s1@example.com |
And I am on site homepage
And I follow "Log in"
When I click on "Create new account" "link"
And I set the following fields to these values:
| Username | s2 |
| Password | test |
| Email address | <email1> |
| Email (again) | <email2> |
| First name | Jane |
| Last name | Doe |
And I press "Create my new account"
Then I should <expect> "This email address is already registered. Perhaps you created an account in the past?"
And I should <expect2> "Invalid email address"
Examples:
| allowsameemail | email1 | email2 | expect | expect2 |
| 0 | s1@example.com | s1@example.com | see | not see |
| 0 | S1@EXAMPLE.COM | S1@EXAMPLE.COM | see | not see |
| 0 | s1@example.com | S1@EXAMPLE.COM | see | not see |
| 0 | s2@example.com | s1@example.com | not see | see |
| 1 | s1@example.com | s1@example.com | not see | not see |
| 1 | S1@EXAMPLE.COM | S1@EXAMPLE.COM | not see | not see |
| 1 | s1@example.com | S1@EXAMPLE.COM | not see | not see |
| 1 | s1@example.com | s2@example.com | not see | see |
+220
View File
@@ -0,0 +1,220 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Auth email external functions tests.
*
* @package auth_email
* @category external
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.2
*/
namespace auth_email\external;
use auth_email_external;
use externallib_advanced_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
* External auth email API tests.
*
* @package auth_email
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.2
*/
class external_test extends externallib_advanced_testcase {
/** @var int custom profile field1 ID. */
protected $field1;
/** @var int custom profile field2 ID. */
protected $field2;
/**
* Set up for every test
*/
public function setUp(): void {
global $CFG;
$this->resetAfterTest(true);
$CFG->registerauth = 'email';
$this->field1 = $this->getDataGenerator()->create_custom_profile_field(array(
'shortname' => 'frogname', 'name' => 'Name of frog',
'datatype' => 'text', 'signup' => 1, 'visible' => 1, 'required' => 1, 'sortorder' => 1))->id;
$this->field2 = $this->getDataGenerator()->create_custom_profile_field(array(
'shortname' => 'sometext', 'name' => 'Some text in textarea',
'datatype' => 'textarea', 'signup' => 1, 'visible' => 1, 'required' => 1, 'sortorder' => 2))->id;
}
public function test_get_signup_settings(): void {
global $CFG;
$CFG->defaultcity = 'Bcn';
$CFG->country = 'ES';
$CFG->sitepolicy = 'https://moodle.org';
$result = auth_email_external::get_signup_settings();
$result = \core_external\external_api::clean_returnvalue(auth_email_external::get_signup_settings_returns(), $result);
// Check expected data.
$this->assertEquals(array('firstname', 'lastname'), $result['namefields']);
$this->assertEquals($CFG->defaultcity, $result['defaultcity']);
$this->assertEquals($CFG->country, $result['country']);
$this->assertEquals($CFG->sitepolicy, $result['sitepolicy']);
$this->assertEquals(print_password_policy(), $result['passwordpolicy']);
$this->assertNotContains('recaptchachallengehash', $result);
$this->assertNotContains('recaptchachallengeimage', $result);
// Check if the extended username chars is returning false when is not set.
$this->assertFalse($result['extendedusernamechars']);
// Whip up a array with named entries to easily check against.
$namedarray = array();
foreach ($result['profilefields'] as $key => $value) {
$namedarray[$value['shortname']] = array(
'datatype' => $value['datatype']
);
}
// Just check if we have the fields from this test. If a plugin adds fields we'll let it slide.
$this->assertArrayHasKey('frogname', $namedarray);
$this->assertArrayHasKey('sometext', $namedarray);
$this->assertEquals('text', $namedarray['frogname']['datatype']);
$this->assertEquals('textarea', $namedarray['sometext']['datatype']);
$CFG->extendedusernamechars = true;
$result = auth_email_external::get_signup_settings();
$result = \core_external\external_api::clean_returnvalue(auth_email_external::get_signup_settings_returns(), $result);
$this->assertTrue($result['extendedusernamechars']);
}
/**
* Test get_signup_settings with mathjax in a profile field.
*/
public function test_get_signup_settings_with_mathjax_in_profile_fields(): void {
// Enable MathJax filter in content and headings.
$this->configure_filters([
['name' => 'mathjaxloader', 'state' => TEXTFILTER_ON, 'move' => -1, 'applytostrings' => true],
]);
// Create category with MathJax and a new field with MathJax.
$categoryname = 'Cat $$(a+b)=2$$';
$fieldname = 'Some text $$(a+b)=2$$';
$categoryid = $this->getDataGenerator()->create_custom_profile_field_category(['name' => $categoryname])->id;
$this->getDataGenerator()->create_custom_profile_field(array(
'shortname' => 'mathjaxname', 'name' => $fieldname, 'categoryid' => $categoryid,
'datatype' => 'textarea', 'signup' => 1, 'visible' => 1, 'required' => 1, 'sortorder' => 2));
$result = auth_email_external::get_signup_settings();
$result = \core_external\external_api::clean_returnvalue(auth_email_external::get_signup_settings_returns(), $result);
// Format the original data.
$sitecontext = \context_system::instance();
$categoryname = \core_external\util::format_string($categoryname, $sitecontext->id);
$fieldname = \core_external\util::format_string($fieldname, $sitecontext->id);
// Whip up a array with named entries to easily check against.
$namedarray = array();
foreach ($result['profilefields'] as $key => $value) {
$namedarray[$value['shortname']] = $value;
}
// Check the new profile field.
$this->assertArrayHasKey('mathjaxname', $namedarray);
$this->assertStringContainsString('<span class="filter_mathjaxloader_equation">',
$namedarray['mathjaxname']['categoryname']);
$this->assertStringContainsString('<span class="filter_mathjaxloader_equation">',
$namedarray['mathjaxname']['name']);
$this->assertEquals($categoryname, $namedarray['mathjaxname']['categoryname']);
$this->assertEquals($fieldname, $namedarray['mathjaxname']['name']);
}
public function test_signup_user(): void {
global $DB;
$username = 'pepe';
$password = 'abcdefAª.ªª!!3';
$firstname = 'Pepe';
$lastname = 'Pérez';
$email = 'myemail@no.zbc';
$city = 'Bcn';
$country = 'ES';
$customprofilefields = array(
array(
'type' => 'text',
'name' => 'profile_field_frogname',
'value' => 'random text',
),
array(
'type' => 'textarea',
'name' => 'profile_field_sometext',
'value' => json_encode(
array(
'text' => 'blah blah',
'format' => FORMAT_HTML
)
),
)
);
// Create new user.
$result = auth_email_external::signup_user($username, $password, $firstname, $lastname, $email, $city, $country,
'', '', $customprofilefields);
$result = \core_external\external_api::clean_returnvalue(auth_email_external::signup_user_returns(), $result);
$this->assertTrue($result['success']);
$this->assertEmpty($result['warnings']);
$user = $DB->get_record('user', array('username' => $username));
$this->assertEquals($firstname, $user->firstname);
$this->assertEquals($lastname, $user->lastname);
$this->assertEquals($email, $user->email);
$this->assertEquals($city, $user->city);
$this->assertEquals($country, $user->country);
$this->assertEquals(0, $user->confirmed);
$this->assertEquals(current_language(), $user->lang);
$this->assertEquals('email', $user->auth);
$infofield = $DB->get_record('user_info_data', array('userid' => $user->id, 'fieldid' => $this->field1));
$this->assertEquals($customprofilefields[0]['value'], $infofield->data);
$infofield = $DB->get_record('user_info_data', array('userid' => $user->id, 'fieldid' => $this->field2));
$this->assertEquals(json_decode($customprofilefields[1]['value'])->text, $infofield->data);
// Try to create a user with the same username, email and password. We ommit also the profile fields.
$password = 'abc';
$result = auth_email_external::signup_user($username, $password, $firstname, $lastname, $email, $city, $country,
'', '', $customprofilefields);
$result = \core_external\external_api::clean_returnvalue(auth_email_external::signup_user_returns(), $result);
$this->assertFalse($result['success']);
$this->assertCount(3, $result['warnings']);
$expectederrors = array('username', 'email', 'password');
$finalerrors = [];
foreach ($result['warnings'] as $warning) {
$finalerrors[] = $warning['item'];
}
$this->assertEquals($expectederrors, $finalerrors);
// Do not pass the required profile fields.
$this->expectException('invalid_parameter_exception');
$result = auth_email_external::signup_user($username, $password, $firstname, $lastname, $email, $city, $country);
}
}
+10
View File
@@ -0,0 +1,10 @@
This files describes API changes in /auth/email/*,
information provided here is intended especially for developers.
=== 4.4 ===
* External function auth_email_external::get_signup_settings() now returns the field "extendedusernamechars".
=== 3.3 ===
* The config.html file was migrated to use the admin settings API.
The identifier for configuration data stored in config_plugins table was converted from 'auth/email' to 'auth_email'.
+29
View File
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Version details
*
* @package auth_email
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'auth_email'; // Full name of the plugin (used for diagnostics)
View File
+3
View File
@@ -0,0 +1,3 @@
LDAP-module README
Please read comments from auth.php in this directory.
+2277
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,51 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Special setting for auth_ldap that cleans up context values on save..
*
* @package auth_ldap
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Special setting for auth_ldap that cleans up context values on save..
*
* @package auth_ldap
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class auth_ldap_admin_setting_special_contexts_configtext extends admin_setting_configtext {
/**
* We need to remove duplicates on save to prevent issues in other areas of Moodle.
*
* @param string $data Form data.
* @return string Empty when no errors.
*/
public function write_setting($data) {
// Try to remove duplicates before storing the contexts (to avoid problems in sync_users()).
$data = explode(';', $data);
$data = array_map(function($x) {
return core_text::strtolower(trim($x));
}, $data);
$data = implode(';', array_unique($data));
return parent::write_setting($data);
}
}
@@ -0,0 +1,45 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Special setting for auth_ldap that lowercases values on save..
*
* @package auth_ldap
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Special setting for auth_ldap that lowercases values on save..
*
* @package auth_ldap
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class auth_ldap_admin_setting_special_lowercase_configtext extends admin_setting_configtext {
/**
* We need to convert the data to lowercase prior to save.
*
* @param string $data Form data.
* @return string Empty when no errors.
*/
public function write_setting($data) {
return parent::write_setting(core_text::strtolower($data));
}
}
@@ -0,0 +1,53 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Special admin setting for auth_ldap that validates ntlm usernames.
*
* @package auth_ldap
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Special admin setting for auth_ldap that validates ntlm usernames.
*
* @package auth_ldap
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class auth_ldap_admin_setting_special_ntlm_configtext extends admin_setting_configtext {
/**
* We need to validate the username format when using NTLM.
*
* @param string $data Form data.
* @return string Empty when no errors.
*/
public function validate($data) {
if (get_config('auth_ldap', 'ntlmsso_type') === 'ntlm') {
$format = trim($data);
if (!empty($format) && !preg_match('/%username%/i', $format)) {
return get_string('auth_ntlmsso_missing_username', 'auth_ldap');
}
}
return parent::validate($data);
}
}
@@ -0,0 +1,61 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_ldap\adminpresets;
use core_adminpresets\local\setting\adminpresets_setting;
/**
* Basic text setting, cleans the param using the admin_setting paramtext attribute.
*
* @package auth_ldap
* @copyright 2021 Pimenko <support@pimenko.com><pimenko.com>
* @author Jordan Kesraoui | Sylvain Revenu | Pimenko based on David Monllaó <david.monllao@urv.cat> code
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class adminpresets_auth_ldap_admin_setting_special_contexts_configtext extends adminpresets_setting {
/**
* Validates the value using paramtype attribute
*
* @param string $value
* @return boolean Returned value is always true, whenever the value has been successfully cleaned or not.
*/
protected function set_value($value): bool {
$this->value = $value;
if (empty($this->settingdata->paramtype)) {
// For configfile, configpasswordunmask....
$this->settingdata->paramtype = 'RAW';
}
$paramtype = 'PARAM_' . strtoupper($this->settingdata->paramtype);
// Regexp.
if (!defined($paramtype)) {
$this->value = preg_replace($this->settingdata->paramtype, '', $this->value);
// Standard moodle param type.
} else {
$this->value = clean_param($this->value, constant($paramtype));
}
$this->set_visiblevalue();
return true;
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy Subsystem implementation for auth_ldap.
*
* @package auth_ldap
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_ldap\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for auth_ldap implementing null_provider.
*
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
@@ -0,0 +1,59 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Adhoc task for LDAP user sync.
*
* @package auth_ldap
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_ldap\task;
use core\task\adhoc_task;
/**
* Adhoc task class for LDAP user sync.
*
* @package auth_ldap
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class asynchronous_sync_task extends adhoc_task {
/** @var string Message prefix for mtrace */
protected const MTRACE_MSG = 'Synced ldap users';
/**
* Constructor
*/
public function __construct() {
$this->set_component('auth_ldap');
}
/**
* Run users sync.
*/
public function execute() {
$data = $this->get_custom_data();
/** @var auth_plugin_ldap $auth */
$auth = get_auth_plugin('ldap');
$auth->update_users($data->users, $data->updatekeys);
mtrace(sprintf(" %s (%d)", self::MTRACE_MSG, count($data->users)));
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A scheduled task for LDAP roles sync.
*
* @package auth_ldap
* @author David Balch <david.balch@conted.ox.ac.uk>
* @copyright 2017 The Chancellor Masters and Scholars of the University of Oxford {@link http://www.tall.ox.ac.uk}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_ldap\task;
defined('MOODLE_INTERNAL') || die();
/**
* A scheduled task class for LDAP roles sync.
*
* @author David Balch <david.balch@conted.ox.ac.uk>
* @copyright 2017 The Chancellor Masters and Scholars of the University of Oxford {@link http://www.tall.ox.ac.uk}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sync_roles extends \core\task\scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('syncroles', 'auth_ldap');
}
/**
* Synchronise role assignments from LDAP.
*/
public function execute() {
global $DB;
if (is_enabled_auth('ldap')) {
$auth = get_auth_plugin('ldap');
$users = $DB->get_records('user', array('auth' => 'ldap'));
foreach ($users as $user) {
$auth->sync_roles($user);
}
}
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A scheduled task for LDAP user sync.
*
* @package auth_ldap
* @copyright 2015 Vadim Dvorovenko <Vadimon@mail.ru>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_ldap\task;
/**
* A scheduled task class for LDAP user sync.
*
* @copyright 2015 Vadim Dvorovenko <Vadimon@mail.ru>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sync_task extends \core\task\scheduled_task {
/** @var string Message prefix for mtrace */
protected const MTRACE_MSG = 'Synced ldap users';
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('synctask', 'auth_ldap');
}
/**
* Run users sync.
*/
public function execute() {
if (is_enabled_auth('ldap')) {
/** @var auth_plugin_ldap $auth */
$auth = get_auth_plugin('ldap');
$count = 0;
$auth->sync_users_update_callback(function ($users, $updatekeys) use (&$count) {
$asynctask = new asynchronous_sync_task();
$asynctask->set_custom_data([
'users' => $users,
'updatekeys' => $updatekeys,
]);
\core\task\manager::queue_adhoc_task($asynctask);
$count++;
mtrace(sprintf(" %s (%d)", self::MTRACE_MSG, $count));
sleep(1);
});
}
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* CAS user sync script.
*
* This script is meant to be called from a cronjob to sync moodle with the LDAP
* backend in those setups where the LDAP backend acts as 'master'.
*
* Notes:
* - it is required to use the web server account when executing PHP CLI scripts
* - you need to change the "www-data" to match the apache user account
* - use "su" if "sudo" not available
* - If you have a large number of users, you may want to raise the memory limits
* by passing -d momory_limit=256M
* - For debugging & better logging, you are encouraged to use in the command line:
* -d log_errors=1 -d error_reporting=E_ALL -d display_errors=0 -d html_errors=0
* - If you have a large number of users, you may want to raise the memory limits
* by passing -d momory_limit=256M
* - For debugging & better logging, you are encouraged to use in the command line:
* -d log_errors=1 -d error_reporting=E_ALL -d display_errors=0 -d html_errors=0
*
* Performance notes:
* We have optimized it as best as we could for PostgreSQL and MySQL, with 27K students
* we have seen this take 10 minutes.
*
* @package auth_ldap
* @copyright 2004 Martin Langhoff
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 3.0 MDL-51824 - please do not use this CLI script any more, use scheduled task instead.
* @todo MDL-50264 This will be deleted in Moodle 3.2.
*/
define('CLI_SCRIPT', true);
require(__DIR__.'/../../../config.php'); // global moodle config file.
require_once($CFG->dirroot.'/course/lib.php');
require_once($CFG->libdir.'/clilib.php');
// Ensure errors are well explained
set_debugging(DEBUG_DEVELOPER, true);
if (!is_enabled_auth('ldap')) {
error_log('[AUTH LDAP] '.get_string('pluginnotenabled', 'auth_ldap'));
die;
}
cli_problem('[AUTH LDAP] The users sync cron has been deprecated. Please use the scheduled task instead.');
// Abort execution of the CLI script if the auth_ldap\task\sync_task is enabled.
$taskdisabled = \core\task\manager::get_scheduled_task('auth_ldap\task\sync_task');
if (!$taskdisabled->get_disabled()) {
cli_error('[AUTH LDAP] The scheduled task sync_task is enabled, the cron execution has been aborted.');
}
$ldapauth = get_auth_plugin('ldap');
$ldapauth->sync_users(true);
+6
View File
@@ -0,0 +1,6 @@
<?php
function xmldb_auth_ldap_install() {
global $CFG, $DB;
}
+49
View File
@@ -0,0 +1,49 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Definition of auth_ldap tasks.
*
* @package auth_ldap
* @category task
* @copyright 2015 Vadim Dvorovenko <Vadimon@mail.ru>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$tasks = array(
array(
'classname' => 'auth_ldap\task\sync_roles',
'blocking' => 0,
'minute' => '0',
'hour' => '0',
'day' => '*',
'month' => '*',
'dayofweek' => '*',
'disabled' => 1
),
array(
'classname' => 'auth_ldap\task\sync_task',
'blocking' => 0,
'minute' => '0',
'hour' => '0',
'day' => '*',
'month' => '*',
'dayofweek' => '*',
'disabled' => 1
)
);
+44
View File
@@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* LDAP authentication plugin upgrade code
*
* @package auth_ldap
* @copyright 2013 Iñaki Arenaza
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Function to upgrade auth_ldap.
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_auth_ldap_upgrade($oldversion) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+170
View File
@@ -0,0 +1,170 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'auth_ldap', language 'en'.
*
* @package auth_ldap
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['auth_ldap_ad_create_req'] = 'Cannot create the new account in Active Directory. Make sure you meet all the requirements for this to work (LDAPS connection, bind user with adequate rights, etc.)';
$string['auth_ldap_attrcreators'] = 'List of groups or contexts whose members are allowed to create attributes. Separate multiple groups with \';\'. Usually something like \'cn=teachers,ou=staff,o=myorg\'';
$string['auth_ldap_attrcreators_key'] = 'Attribute creators';
$string['auth_ldap_auth_user_create_key'] = 'Create users externally';
$string['auth_ldap_bind_dn'] = 'If you want to use bind-user to search users, specify it here. Something like \'cn=ldapuser,ou=public,o=org\'';
$string['auth_ldap_bind_dn_key'] = 'Distinguished name';
$string['auth_ldap_bind_pw'] = 'Password for bind-user.';
$string['auth_ldap_bind_pw_key'] = 'Password';
$string['auth_ldap_bind_settings'] = 'Bind settings';
$string['auth_ldap_contexts'] = 'List of contexts where users are located. Separate different contexts with \';\'. For example: \'ou=users,o=org; ou=others,o=org\'';
$string['auth_ldap_contexts_key'] = 'Contexts';
$string['auth_ldap_create_context'] = 'If you enable user creation with email confirmation, specify the context where users are created. This context should be different from other users to prevent security issues. You don\'t need to add this context to ldap_context-variable, Moodle will search for users from this context automatically.<br /><b>Note!</b> You have to modify the method user_create() in file auth/ldap/auth.php to make user creation work';
$string['auth_ldap_create_context_key'] = 'Context for new users';
$string['auth_ldap_create_error'] = 'Error creating user in LDAP.';
$string['auth_ldapdescription'] = 'This method provides authentication against an external LDAP server. If the given username and password are valid, Moodle creates a new user entry in its database. This plugin can read user attributes from LDAP and prefill wanted fields in Moodle. For following logins only the username and password are checked.';
$string['auth_ldap_expiration_desc'] = 'Select \'{$a->no}\' to disable expired password checking or \'{$a->ldapserver}\' to read the password expiry time directly from the LDAP server.';
$string['auth_ldap_expiration_key'] = 'Expiry';
$string['auth_ldap_expiration_warning_desc'] = 'Number of days before password expiry warning is issued.';
$string['auth_ldap_expiration_warning_key'] = 'Expiry warning';
$string['auth_ldap_expireattr_desc'] = 'Optional: Overrides the LDAP attribute that stores password expiry time.';
$string['auth_ldap_expireattr_key'] = 'Expiry attribute';
$string['auth_ldapextrafields'] = 'These fields are optional. You can choose to pre-fill some Moodle user fields with information from the <b>LDAP fields</b> that you specify here. <p>If you leave these fields blank, then nothing will be transferred from LDAP and Moodle defaults will be used instead.</p><p>In either case, the user will be able to edit all of these fields after they log in.</p>';
$string['auth_ldap_graceattr_desc'] = 'Optional: Overrides grace login attribute';
$string['auth_ldap_gracelogin_key'] = 'Grace login attribute';
$string['auth_ldap_gracelogins_desc'] = 'Enable LDAP grace login support. After password has expired, user can log in until grace login count is 0. Enabling this setting displays grace login message if password has expired.';
$string['auth_ldap_gracelogins_key'] = 'Grace logins';
$string['auth_ldap_groupecreators'] = 'List of groups or contexts whose members are allowed to create groups. Separate multiple groups with \';\'. Usually something like \'cn=teachers,ou=staff,o=myorg\'';
$string['auth_ldap_groupecreators_key'] = 'Group creators';
$string['auth_ldap_host_url'] = 'Specify LDAP host in URL-form like \'ldap://ldap.myorg.com/\' or \'ldaps://ldap.myorg.com/\'. Separate multiple servers with \';\' to get failover support.';
$string['auth_ldap_host_url_key'] = 'Host URL';
$string['auth_ldap_changepasswordurl_key'] = 'Password-change URL';
$string['auth_ldap_ldap_encoding'] = 'Encoding used by the LDAP server, most likely utf-8. If LDAP v2 is selected, Active Directory uses its configured encoding, such as cp1252 or cp1250.';
$string['auth_ldap_ldap_encoding_key'] = 'LDAP encoding';
$string['auth_ldap_login_settings'] = 'Login settings';
$string['auth_ldap_memberattribute'] = 'Optional: Overrides user member attribute, when users belongs to a group. Usually \'member\'';
$string['auth_ldap_memberattribute_isdn'] = 'Overrides handling of member attribute values';
$string['auth_ldap_memberattribute_isdn_key'] = 'Member attribute uses dn';
$string['auth_ldap_memberattribute_key'] = 'Member attribute';
$string['auth_ldap_noconnect'] = 'LDAP-module cannot connect to server: {$a}';
$string['auth_ldap_noconnect_all'] = 'LDAP-module cannot connect to any servers: {$a}';
$string['auth_ldap_noextension'] = 'The PHP LDAP module does not seem to be present. Please ensure it is installed and enabled if you want to use this authentication plugin.';
$string['auth_ldap_no_mbstring'] = 'You need the mbstring extension to create users in Active Directory.';
$string['auth_ldapnotinstalled'] = 'Cannot use LDAP authentication. The PHP LDAP module is not installed.';
$string['auth_ldap_objectclass'] = 'Optional: Overrides objectClass used to name/search users on ldap_user_type. Usually you don\'t need to change this.';
$string['auth_ldap_objectclass_key'] = 'Object class';
$string['auth_ldap_opt_deref'] = 'Determines how aliases are handled during search. Select one of the following values: "No" (LDAP_DEREF_NEVER) or "Yes" (LDAP_DEREF_ALWAYS)';
$string['auth_ldap_opt_deref_key'] = 'Dereference aliases';
$string['auth_ldap_passtype'] = 'Specify the format of new or changed passwords in LDAP server.';
$string['auth_ldap_passtype_key'] = 'Password format';
$string['auth_ldap_passwdexpire_settings'] = 'LDAP password expiry settings';
$string['auth_ldap_preventpassindb'] = 'Select yes to prevent passwords from being stored in Moodle\'s DB.';
$string['auth_ldap_preventpassindb_key'] = 'Prevent password caching';
$string['auth_ldap_rolecontext'] = '{$a->localname} context';
$string['auth_ldap_rolecontext_help'] = 'LDAP context used to select for <i>{$a->localname}</i> mapping. Separate multiple groups with \';\'. Usually something like "cn={$a->shortname},ou=first-ou-with-role-groups,o=myorg; cn={$a->shortname},ou=second-ou-with-role-groups,o=myorg".';
$string['auth_ldap_search_sub'] = 'Search users from subcontexts.';
$string['auth_ldap_search_sub_key'] = 'Search subcontexts';
$string['auth_ldap_server_settings'] = 'LDAP server settings';
$string['auth_ldap_unsupportedusertype'] = 'auth: ldap user_create() does not support selected usertype: {$a}';
$string['auth_ldap_update_userinfo'] = 'Update user information (firstname, lastname, address..) from LDAP to Moodle. Specify "Data mapping" settings as you need.';
$string['auth_ldap_user_attribute'] = 'Optional: Overrides the attribute used to name/search users. Usually \'cn\'.';
$string['auth_ldap_user_attribute_key'] = 'User attribute';
$string['auth_ldap_suspended_attribute'] = 'Optional: When provided this attribute will be used to enable/suspend the locally created user account.';
$string['auth_ldap_suspended_attribute_key'] = 'Suspended attribute';
$string['auth_ldap_user_exists'] = 'LDAP username already exists.';
$string['auth_ldap_user_settings'] = 'User lookup settings';
$string['auth_ldap_user_type'] = 'Select how users are stored in LDAP. This setting also specifies how login expiry, grace logins and user creation will work.';
$string['auth_ldap_user_type_key'] = 'User type';
$string['auth_ldap_usertypeundefined'] = 'config.user_type not defined or function ldap_expirationtime2unix does not support selected type!';
$string['auth_ldap_usertypeundefined2'] = 'config.user_type not defined or function ldap_unixi2expirationtime does not support selected type!';
$string['auth_ldap_version'] = 'The version of the LDAP protocol your server is using.';
$string['auth_ldap_version_key'] = 'Version';
$string['auth_ntlmsso'] = 'NTLM SSO';
$string['auth_ntlmsso_enabled'] = 'Set to yes to attempt Single Sign On with the NTLM domain. Note that this requires additional setup on the server to work. For further details, see the documentation <a href="https://docs.moodle.org/en/NTLM_authentication">NTLM authentication</a>.';
$string['auth_ntlmsso_enabled_key'] = 'Enable';
$string['auth_ntlmsso_ie_fastpath'] = 'Set to enable the NTLM SSO fast path (bypasses certain steps if the client\'s browser is MS Internet Explorer).';
$string['auth_ntlmsso_ie_fastpath_key'] = 'MS IE fast path?';
$string['auth_ntlmsso_ie_fastpath_yesform'] = 'Yes, all other browsers use standard login form';
$string['auth_ntlmsso_ie_fastpath_yesattempt'] = 'Yes, attempt NTLM other browsers';
$string['auth_ntlmsso_ie_fastpath_attempt'] = 'Attempt NTLM with all browsers';
$string['auth_ntlmsso_maybeinvalidformat'] = 'Unable to extract the username from the REMOTE_USER header. Is the configured format right?';
$string['auth_ntlmsso_missing_username'] = 'You need to specify at least %username% in the remote username format';
$string['auth_ntlmsso_remoteuserformat_key'] = 'Remote username format';
$string['auth_ntlmsso_remoteuserformat'] = 'If you have chosen \'NTLM\' in \'Authentication type\', you can specify the remote username format here. If you leave this empty, the default DOMAIN\\username format will be used. You can use the optional <b>%domain%</b> placeholder to specify where the domain name appears, and the mandatory <b>%username%</b> placeholder to specify where the username appears. <br /><br />Some widely used formats are <tt>%domain%\\%username%</tt> (MS Windows default), <tt>%domain%/%username%</tt>, <tt>%domain%+%username%</tt> and just <tt>%username%</tt> (if there is no domain part).';
$string['auth_ntlmsso_subnet'] = 'If set, it will only attempt SSO with clients in this subnet. Format: xxx.xxx.xxx.xxx/bitmask. Separate multiple subnets with \',\' (comma).';
$string['auth_ntlmsso_subnet_key'] = 'Subnet';
$string['auth_ntlmsso_type_key'] = 'Authentication type';
$string['auth_ntlmsso_type'] = 'The authentication method configured in the web server to authenticate the users (if in doubt, choose NTLM)';
$string['cannotmaprole'] = 'The role "{$a->rolename}" cannot be mapped because its short name "{$a->shortname}" is too long and/or contains hyphens. To allow it to be mapped, the short name needs to be reduced to a maximum of {$a->charlimit} characters and any hyphens removed. <a href="{$a->link}">Edit the role</a>';
$string['connectingldap'] = "Connecting to LDAP server...\n";
$string['connectingldapsuccess'] = "Connecting to your LDAP server was successful";
$string['creatingtemptable'] = "Creating temporary table {\$a}\n";
$string['didntfindexpiretime'] = 'password_expire() didn\'t find expiration time.';
$string['didntgetusersfromldap'] = "Did not get any users from LDAP -- error? -- exiting\n";
$string['gotcountrecordsfromldap'] = "Got {\$a} records from LDAP\n";
$string['invalidusererrors'] = "Warning: Skipped creation of {\$a} user accounts.\n\n";
$string['invaliduserexception'] = "\nError: Cannot create new user account. Details and reason:\n{\$a}\nSkipping this user.\n\n";
$string['ldapnotconfigured'] = 'The LDAP host url is currently not configured';
$string['morethanoneuser'] = 'More than one user record found in LDAP. Using only the first one.';
$string['needbcmath'] = 'You need the BCMath extension to use expired password checking with Active Directory.';
$string['needmbstring'] = 'You need the mbstring extension to change passwords in Active Directory';
$string['nodnforusername'] = 'Error in user_update_password(). No DN for: {$a->username}';
$string['noemail'] = 'Tried to send you an email but failed!';
$string['notcalledfromserver'] = 'Should not be called from the web server!';
$string['noupdatestobedone'] = "No updates to be done\n";
$string['nouserentriestoremove'] = "No user entries to be removed\n";
$string['nouserentriestorevive'] = "No user entries to be revived\n";
$string['nouserstobeadded'] = 'No user entries to be added';
$string['ntlmsso_attempting'] = 'Attempting Single Sign On via NTLM...';
$string['ntlmsso_failed'] = 'Auto-login failed, try the normal login page...';
$string['ntlmsso_isdisabled'] = 'NTLM SSO is disabled.';
$string['ntlmsso_unknowntype'] = 'Unknown ntlmsso type!';
$string['pagedresultsnotsupp'] = 'LDAP paged results not supported (either your PHP version lacks support, you have configured Moodle to use LDAP protocol version 2 or Moodle cannot contact your LDAP server to see if paged support is available.)';
$string['pagesize'] = 'Make sure this value is smaller than your LDAP server result set size limit (the maximum number of entries that can be returned in a single query)';
$string['pagesize_key'] = 'Page size';
$string['pluginname'] = 'LDAP server';
$string['pluginnotenabled'] = 'Plugin not enabled!';
$string['renamingnotallowed'] = 'User renaming not allowed in LDAP';
$string['rootdseerror'] = 'Error querying rootDSE for Active Directory';
$string['syncroles'] = 'Synchronise system roles from LDAP';
$string['synctask'] = 'LDAP users sync job';
$string['sync_updateuserchunk'] = 'Set this value to the number of users you want updated per transaction. Setting this to 0 will update all users in one transaction.';
$string['sync_updateuserchunk_key'] = 'Sync update users chunk size';
$string['systemrolemapping'] = 'System role mapping';
$string['start_tls'] = 'Use regular LDAP service (port 389) with TLS encryption';
$string['start_tls_key'] = 'Use TLS';
$string['updateremfail'] = 'Error updating LDAP record. Error code: {$a->errno}; Error string: {$a->errstring}<br/>Key ({$a->key}) - old moodle value: \'{$a->ouvalue}\' new value: \'{$a->nuvalue}\'';
$string['updateremfailamb'] = 'Failed to update LDAP with ambiguous field {$a->key}; old moodle value: \'{$a->ouvalue}\', new value: \'{$a->nuvalue}\'';
$string['updatepasserror'] = 'Error in user_update_password(). Error code: {$a->errno}; Error string: {$a->errstring}';
$string['updatepasserrorexpire'] = 'Error in user_update_password() when reading password expiry time. Error code: {$a->errno}; Error string: {$a->errstring}';
$string['updatepasserrorexpiregrace'] = 'Error in user_update_password() when modifying expiry time and/or grace logins. Error code: {$a->errno}; Error string: {$a->errstring}';
$string['updateusernotfound'] = 'Could not find user while updating externally. Details follow: search base: \'{$a->userdn}\'; search filter: \'(objectClass=*)\'; search attributes: {$a->attribs}';
$string['user_activatenotsupportusertype'] = 'auth: ldap user_activate() does not support selected usertype: {$a}';
$string['user_disablenotsupportusertype'] = 'auth: ldap user_disable() does not support selected usertype: {$a}';
$string['userentriestoadd'] = "User entries to be added: {\$a}\n";
$string['userentriestoremove'] = "User entries to be removed: {\$a}\n";
$string['userentriestorevive'] = "User entries to be revived: {\$a}\n";
$string['userentriestoupdate'] = "User entries to be updated: {\$a}\n";
$string['usernotfound'] = 'User not found in LDAP';
$string['useracctctrlerror'] = 'Error getting userAccountControl for {$a}';
$string['diag_genericerror'] = 'LDAP error {$a->code} reading {$a->subject}: {$a->message}.';
$string['diag_toooldversion'] = 'It is very unlikely a modern LDAP server uses LDAPv2 protocol. Wrong settings can corrupt values in user fields. Check with your LDAP administrator.';
$string['diag_emptycontext'] = 'Empty context found.';
$string['diag_contextnotfound'] = 'Context {$a} doesn\'t exist or can\'t be read by bind DN.';
$string['diag_rolegroupnotfound'] = 'Group {$a->group} for role {$a->localname} doesn\'t exist or can\'t be read by bind DN.';
$string['privacy:metadata'] = 'The LDAP server authentication plugin does not store any personal data.';
+53
View File
@@ -0,0 +1,53 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Internal library of functions for module auth_ldap
*
* @package auth_ldap
* @author David Balch <david.balch@conted.ox.ac.uk>
* @copyright 2017 The Chancellor Masters and Scholars of the University of Oxford {@link http://www.tall.ox.ac.uk/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Get a list of system roles assignable by the current or a specified user, including their localised names.
*
* @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
* @return array $roles, each role as an array with id, shortname, localname, and settingname for the config value.
*/
function get_ldap_assignable_role_names($user = null) {
$roles = array();
if ($assignableroles = get_assignable_roles(context_system::instance(), ROLENAME_SHORT, false, $user)) {
$systemroles = role_fix_names(get_all_roles(), context_system::instance(), ROLENAME_ORIGINAL);
foreach ($assignableroles as $shortname) {
foreach ($systemroles as $systemrole) {
if ($systemrole->shortname == $shortname) {
$roles[] = array('id' => $systemrole->id,
'shortname' => $shortname,
'localname' => $systemrole->localname,
'settingname' => $shortname . 'context');
break;
}
}
}
}
return $roles;
}
+37
View File
@@ -0,0 +1,37 @@
<?php
require(__DIR__.'/../../config.php');
$PAGE->set_url('/auth/ldap/ntlmsso_attempt.php');
$PAGE->set_context(context_system::instance());
// Define variables used in page
$site = get_site();
$authsequence = get_enabled_auth_plugins(); // Auths, in sequence.
if (!in_array('ldap', $authsequence, true)) {
throw new \moodle_exception('ldap_isdisabled', 'auth');
}
$authplugin = get_auth_plugin('ldap');
if (empty($authplugin->config->ntlmsso_enabled)) {
throw new \moodle_exception('ntlmsso_isdisabled', 'auth_ldap');
}
$sesskey = sesskey();
// Display the page header. This makes redirect respect the timeout we specify
// here (and not add 3 more secs) which in turn prevents a bug in both IE 6.x
// and FF 3.x (Windows version at least) where javascript timers fire up even
// when we've already left the page that set the timer.
$loginsite = get_string("loginsite");
$PAGE->navbar->add($loginsite);
$PAGE->set_title($loginsite);
$PAGE->set_heading($site->fullname);
echo $OUTPUT->header();
$msg = '<p>'.get_string('ntlmsso_attempting', 'auth_ldap').'</p>'
. '<img width="1", height="1" '
. ' src="' . $CFG->wwwroot . '/auth/ldap/ntlmsso_magic.php?sesskey='
. $sesskey . '" />';
redirect($CFG->wwwroot . '/auth/ldap/ntlmsso_finish.php', $msg, 3);
+34
View File
@@ -0,0 +1,34 @@
<?php
require(__DIR__.'/../../config.php');
$PAGE->set_url('/auth/ldap/ntlmsso_finish.php');
$PAGE->set_context(context_system::instance());
// Define variables used in page
$site = get_site();
$authsequence = get_enabled_auth_plugins(); // Auths, in sequence.
if (!in_array('ldap', $authsequence, true)) {
throw new \moodle_exception('ldap_isdisabled', 'auth');
}
$authplugin = get_auth_plugin('ldap');
if (empty($authplugin->config->ntlmsso_enabled)) {
throw new \moodle_exception('ntlmsso_isdisabled', 'auth_ldap');
}
// If ntlmsso_finish() succeeds, then the code never returns,
// so we only worry about failure.
if (!$authplugin->ntlmsso_finish()) {
// Redirect to login, saying "don't try again!"
// Display the page header. This makes redirect respect the timeout we specify
// here (and not add 3 more secs).
$loginsite = get_string("loginsite");
$PAGE->navbar->add($loginsite);
$PAGE->set_title($loginsite);
$PAGE->set_heading($site->fullname);
echo $OUTPUT->header();
redirect($CFG->wwwroot . '/login/index.php?authldap_skipntlmsso=1',
get_string('ntlmsso_failed','auth_ldap'), 3);
}
+47
View File
@@ -0,0 +1,47 @@
<?php
// Don't let lib/setup.php set any cookies
// as we will be executing under the OS security
// context of the user we are trying to login, rather than
// of the webserver.
define('NO_MOODLE_COOKIES', true);
require(__DIR__.'/../../config.php');
$PAGE->set_context(context_system::instance());
$authsequence = get_enabled_auth_plugins(); // Auths, in sequence.
if (!in_array('ldap', $authsequence, true)) {
throw new \moodle_exception('ldap_isdisabled', 'auth');
}
$authplugin = get_auth_plugin('ldap');
if (empty($authplugin->config->ntlmsso_enabled)) {
throw new \moodle_exception('ntlmsso_isdisabled', 'auth_ldap');
}
$sesskey = required_param('sesskey', PARAM_RAW);
$file = $CFG->dirroot.'/pix/spacer.gif';
if ($authplugin->ntlmsso_magic($sesskey) && file_exists($file)) {
if (!empty($authplugin->config->ntlmsso_ie_fastpath)) {
if (core_useragent::is_ie()) {
redirect($CFG->wwwroot.'/auth/ldap/ntlmsso_finish.php');
}
}
// Serve GIF
// Type
header('Content-Type: image/gif');
header('Content-Length: '.filesize($file));
// Output file
$handle = fopen($file, 'r');
fpassthru($handle);
fclose($handle);
exit;
} else {
throw new \moodle_exception('ntlmsso_iwamagicnotenabled', 'auth_ldap');
}
+347
View File
@@ -0,0 +1,347 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Admin settings and defaults.
*
* @package auth_ldap
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
if (!function_exists('ldap_connect')) {
$notify = new \core\output\notification(get_string('auth_ldap_noextension', 'auth_ldap'),
\core\output\notification::NOTIFY_WARNING);
$settings->add(new admin_setting_heading('auth_ldap_noextension', '', $OUTPUT->render($notify)));
} else {
// We use a couple of custom admin settings since we need to massage the data before it is inserted into the DB.
require_once($CFG->dirroot.'/auth/ldap/classes/admin_setting_special_lowercase_configtext.php');
require_once($CFG->dirroot.'/auth/ldap/classes/admin_setting_special_contexts_configtext.php');
require_once($CFG->dirroot.'/auth/ldap/classes/admin_setting_special_ntlm_configtext.php');
// We need to use some of the Moodle LDAP constants / functions to create the list of options.
require_once($CFG->dirroot.'/auth/ldap/auth.php');
// Introductory explanation.
$settings->add(new admin_setting_heading('auth_ldap/pluginname', '',
new lang_string('auth_ldapdescription', 'auth_ldap')));
// LDAP server settings.
$settings->add(new admin_setting_heading('auth_ldap/ldapserversettings',
new lang_string('auth_ldap_server_settings', 'auth_ldap'), ''));
// Host.
$settings->add(new admin_setting_configtext('auth_ldap/host_url',
get_string('auth_ldap_host_url_key', 'auth_ldap'),
get_string('auth_ldap_host_url', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Version.
$versions = array();
$versions[2] = '2';
$versions[3] = '3';
$settings->add(new admin_setting_configselect('auth_ldap/ldap_version',
new lang_string('auth_ldap_version_key', 'auth_ldap'),
new lang_string('auth_ldap_version', 'auth_ldap'), 3, $versions));
// Start TLS.
$yesno = array(
new lang_string('no'),
new lang_string('yes'),
);
$settings->add(new admin_setting_configselect('auth_ldap/start_tls',
new lang_string('start_tls_key', 'auth_ldap'),
new lang_string('start_tls', 'auth_ldap'), 0 , $yesno));
// Encoding.
$settings->add(new admin_setting_configtext('auth_ldap/ldapencoding',
get_string('auth_ldap_ldap_encoding_key', 'auth_ldap'),
get_string('auth_ldap_ldap_encoding', 'auth_ldap'), 'utf-8', PARAM_RAW_TRIMMED));
// Page Size. (Hide if not available).
$settings->add(new admin_setting_configtext('auth_ldap/pagesize',
get_string('pagesize_key', 'auth_ldap'),
get_string('pagesize', 'auth_ldap'), '250', PARAM_INT));
// Bind settings.
$settings->add(new admin_setting_heading('auth_ldap/ldapbindsettings',
new lang_string('auth_ldap_bind_settings', 'auth_ldap'), ''));
// Store Password in DB.
$settings->add(new admin_setting_configselect('auth_ldap/preventpassindb',
new lang_string('auth_ldap_preventpassindb_key', 'auth_ldap'),
new lang_string('auth_ldap_preventpassindb', 'auth_ldap'), 0 , $yesno));
// User ID.
$settings->add(new admin_setting_configtext('auth_ldap/bind_dn',
get_string('auth_ldap_bind_dn_key', 'auth_ldap'),
get_string('auth_ldap_bind_dn', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Password.
$settings->add(new admin_setting_configpasswordunmask('auth_ldap/bind_pw',
get_string('auth_ldap_bind_pw_key', 'auth_ldap'),
get_string('auth_ldap_bind_pw', 'auth_ldap'), ''));
// User Lookup settings.
$settings->add(new admin_setting_heading('auth_ldap/ldapuserlookup',
new lang_string('auth_ldap_user_settings', 'auth_ldap'), ''));
// User Type.
$settings->add(new admin_setting_configselect('auth_ldap/user_type',
new lang_string('auth_ldap_user_type_key', 'auth_ldap'),
new lang_string('auth_ldap_user_type', 'auth_ldap'), 'default', ldap_supported_usertypes()));
// Contexts.
$settings->add(new auth_ldap_admin_setting_special_contexts_configtext('auth_ldap/contexts',
get_string('auth_ldap_contexts_key', 'auth_ldap'),
get_string('auth_ldap_contexts', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Search subcontexts.
$settings->add(new admin_setting_configselect('auth_ldap/search_sub',
new lang_string('auth_ldap_search_sub_key', 'auth_ldap'),
new lang_string('auth_ldap_search_sub', 'auth_ldap'), 0 , $yesno));
// Dereference aliases.
$optderef = array();
$optderef[LDAP_DEREF_NEVER] = get_string('no');
$optderef[LDAP_DEREF_ALWAYS] = get_string('yes');
$settings->add(new admin_setting_configselect('auth_ldap/opt_deref',
new lang_string('auth_ldap_opt_deref_key', 'auth_ldap'),
new lang_string('auth_ldap_opt_deref', 'auth_ldap'), LDAP_DEREF_NEVER , $optderef));
// User attribute.
$settings->add(new auth_ldap_admin_setting_special_lowercase_configtext('auth_ldap/user_attribute',
get_string('auth_ldap_user_attribute_key', 'auth_ldap'),
get_string('auth_ldap_user_attribute', 'auth_ldap'), '', PARAM_RAW));
// Suspended attribute.
$settings->add(new auth_ldap_admin_setting_special_lowercase_configtext('auth_ldap/suspended_attribute',
get_string('auth_ldap_suspended_attribute_key', 'auth_ldap'),
get_string('auth_ldap_suspended_attribute', 'auth_ldap'), '', PARAM_RAW));
// Member attribute.
$settings->add(new auth_ldap_admin_setting_special_lowercase_configtext('auth_ldap/memberattribute',
get_string('auth_ldap_memberattribute_key', 'auth_ldap'),
get_string('auth_ldap_memberattribute', 'auth_ldap'), '', PARAM_RAW));
// Member attribute uses dn.
$settings->add(new admin_setting_configselect('auth_ldap/memberattribute_isdn',
get_string('auth_ldap_memberattribute_isdn_key', 'auth_ldap'),
get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), 0, $yesno));
// Object class.
$settings->add(new admin_setting_configtext('auth_ldap/objectclass',
get_string('auth_ldap_objectclass_key', 'auth_ldap'),
get_string('auth_ldap_objectclass', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// Force Password change Header.
$settings->add(new admin_setting_heading('auth_ldap/ldapforcepasswordchange',
new lang_string('forcechangepassword', 'auth'), ''));
// Force Password change.
$settings->add(new admin_setting_configselect('auth_ldap/forcechangepassword',
new lang_string('forcechangepassword', 'auth'),
new lang_string('forcechangepasswordfirst_help', 'auth'), 0 , $yesno));
// Standard Password Change.
$settings->add(new admin_setting_configselect('auth_ldap/stdchangepassword',
new lang_string('stdchangepassword', 'auth'), new lang_string('stdchangepassword_expl', 'auth') .' '.
get_string('stdchangepassword_explldap', 'auth'), 0 , $yesno));
// Password Type.
$passtype = array();
$passtype['plaintext'] = get_string('plaintext', 'auth');
$passtype['md5'] = get_string('md5', 'auth');
$passtype['sha1'] = get_string('sha1', 'auth');
$settings->add(new admin_setting_configselect('auth_ldap/passtype',
new lang_string('auth_ldap_passtype_key', 'auth_ldap'),
new lang_string('auth_ldap_passtype', 'auth_ldap'), 'plaintext', $passtype));
// Password change URL.
$settings->add(new admin_setting_configtext('auth_ldap/changepasswordurl',
get_string('auth_ldap_changepasswordurl_key', 'auth_ldap'),
get_string('changepasswordhelp', 'auth'), '', PARAM_URL));
// Password Expiration Header.
$settings->add(new admin_setting_heading('auth_ldap/passwordexpire',
new lang_string('auth_ldap_passwdexpire_settings', 'auth_ldap'), ''));
// Password Expiration.
// Create the description lang_string object.
$strno = get_string('no');
$strldapserver = get_string('pluginname', 'auth_ldap');
$langobject = new stdClass();
$langobject->no = $strno;
$langobject->ldapserver = $strldapserver;
$description = new lang_string('auth_ldap_expiration_desc', 'auth_ldap', $langobject);
// Now create the options.
$expiration = array();
$expiration['0'] = $strno;
$expiration['1'] = $strldapserver;
// Add the setting.
$settings->add(new admin_setting_configselect('auth_ldap/expiration',
new lang_string('auth_ldap_expiration_key', 'auth_ldap'),
$description, 0 , $expiration));
// Password Expiration warning.
$settings->add(new admin_setting_configtext('auth_ldap/expiration_warning',
get_string('auth_ldap_expiration_warning_key', 'auth_ldap'),
get_string('auth_ldap_expiration_warning_desc', 'auth_ldap'), '', PARAM_RAW));
// Password Expiration attribute.
$settings->add(new auth_ldap_admin_setting_special_lowercase_configtext('auth_ldap/expireattr',
get_string('auth_ldap_expireattr_key', 'auth_ldap'),
get_string('auth_ldap_expireattr_desc', 'auth_ldap'), '', PARAM_RAW));
// Grace Logins.
$settings->add(new admin_setting_configselect('auth_ldap/gracelogins',
new lang_string('auth_ldap_gracelogins_key', 'auth_ldap'),
new lang_string('auth_ldap_gracelogins_desc', 'auth_ldap'), 0 , $yesno));
// Grace logins attribute.
$settings->add(new auth_ldap_admin_setting_special_lowercase_configtext('auth_ldap/graceattr',
get_string('auth_ldap_gracelogin_key', 'auth_ldap'),
get_string('auth_ldap_graceattr_desc', 'auth_ldap'), '', PARAM_RAW));
// User Creation.
$settings->add(new admin_setting_heading('auth_ldap/usercreation',
new lang_string('auth_user_create', 'auth'), ''));
// Create users externally.
$settings->add(new admin_setting_configselect('auth_ldap/auth_user_create',
new lang_string('auth_ldap_auth_user_create_key', 'auth_ldap'),
new lang_string('auth_user_creation', 'auth'), 0 , $yesno));
// Context for new users.
$settings->add(new admin_setting_configtext('auth_ldap/create_context',
get_string('auth_ldap_create_context_key', 'auth_ldap'),
get_string('auth_ldap_create_context', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// System roles mapping header.
$settings->add(new admin_setting_heading('auth_ldap/systemrolemapping',
new lang_string('systemrolemapping', 'auth_ldap'), ''));
// Create system role mapping field for each assignable system role.
$roles = get_ldap_assignable_role_names();
foreach ($roles as $role) {
// Before we can add this setting we need to check a few things.
// A) It does not exceed 100 characters otherwise it will break the DB as the 'name' field
// in the 'config_plugins' table is a varchar(100).
// B) The setting name does not contain hyphens. If it does then it will fail the check
// in parse_setting_name() and everything will explode. Role short names are validated
// against PARAM_ALPHANUMEXT which is similar to the regex used in parse_setting_name()
// except it also allows hyphens.
// Instead of shortening the name and removing/replacing the hyphens we are showing a warning.
// If we were to manipulate the setting name by removing the hyphens we may get conflicts, eg
// 'thisisashortname' and 'this-is-a-short-name'. The same applies for shortening the setting name.
if (core_text::strlen($role['settingname']) > 100 || !preg_match('/^[a-zA-Z0-9_]+$/', $role['settingname'])) {
$url = new moodle_url('/admin/roles/define.php', array('action' => 'edit', 'roleid' => $role['id']));
$a = (object)['rolename' => $role['localname'], 'shortname' => $role['shortname'], 'charlimit' => 93,
'link' => $url->out()];
$settings->add(new admin_setting_heading('auth_ldap/role_not_mapped_' . sha1($role['settingname']), '',
get_string('cannotmaprole', 'auth_ldap', $a)));
} else {
$settings->add(new admin_setting_configtext('auth_ldap/' . $role['settingname'],
get_string('auth_ldap_rolecontext', 'auth_ldap', $role),
get_string('auth_ldap_rolecontext_help', 'auth_ldap', $role), '', PARAM_RAW_TRIMMED));
}
}
// User Account Sync.
$settings->add(new admin_setting_heading('auth_ldap/syncusers',
new lang_string('auth_sync_script', 'auth'), ''));
// Remove external user.
$deleteopt = array();
$deleteopt[AUTH_REMOVEUSER_KEEP] = get_string('auth_remove_keep', 'auth');
$deleteopt[AUTH_REMOVEUSER_SUSPEND] = get_string('auth_remove_suspend', 'auth');
$deleteopt[AUTH_REMOVEUSER_FULLDELETE] = get_string('auth_remove_delete', 'auth');
$settings->add(new admin_setting_configselect('auth_ldap/removeuser',
new lang_string('auth_remove_user_key', 'auth'),
new lang_string('auth_remove_user', 'auth'), AUTH_REMOVEUSER_KEEP, $deleteopt));
// Sync Suspension.
$settings->add(new admin_setting_configselect('auth_ldap/sync_suspended',
new lang_string('auth_sync_suspended_key', 'auth'),
new lang_string('auth_sync_suspended', 'auth'), 0 , $yesno));
// Sync update users chunk size.
$settings->add(new admin_setting_configtext('auth_ldap/sync_updateuserchunk',
new lang_string('sync_updateuserchunk_key', 'auth_ldap'),
new lang_string('sync_updateuserchunk', 'auth_ldap'), 1000, PARAM_INT));
// NTLM SSO Header.
$settings->add(new admin_setting_heading('auth_ldap/ntlm',
new lang_string('auth_ntlmsso', 'auth_ldap'), ''));
// Enable NTLM.
$settings->add(new admin_setting_configselect('auth_ldap/ntlmsso_enabled',
new lang_string('auth_ntlmsso_enabled_key', 'auth_ldap'),
new lang_string('auth_ntlmsso_enabled', 'auth_ldap'), 0 , $yesno));
// Subnet.
$settings->add(new admin_setting_configtext('auth_ldap/ntlmsso_subnet',
get_string('auth_ntlmsso_subnet_key', 'auth_ldap'),
get_string('auth_ntlmsso_subnet', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
// NTLM Fast Path.
$fastpathoptions = array();
$fastpathoptions[AUTH_NTLM_FASTPATH_YESFORM] = get_string('auth_ntlmsso_ie_fastpath_yesform', 'auth_ldap');
$fastpathoptions[AUTH_NTLM_FASTPATH_YESATTEMPT] = get_string('auth_ntlmsso_ie_fastpath_yesattempt', 'auth_ldap');
$fastpathoptions[AUTH_NTLM_FASTPATH_ATTEMPT] = get_string('auth_ntlmsso_ie_fastpath_attempt', 'auth_ldap');
$settings->add(new admin_setting_configselect('auth_ldap/ntlmsso_ie_fastpath',
new lang_string('auth_ntlmsso_ie_fastpath_key', 'auth_ldap'),
new lang_string('auth_ntlmsso_ie_fastpath', 'auth_ldap'),
AUTH_NTLM_FASTPATH_ATTEMPT, $fastpathoptions));
// Authentication type.
$types = array();
$types['ntlm'] = 'NTLM';
$types['kerberos'] = 'Kerberos';
$settings->add(new admin_setting_configselect('auth_ldap/ntlmsso_type',
new lang_string('auth_ntlmsso_type_key', 'auth_ldap'),
new lang_string('auth_ntlmsso_type', 'auth_ldap'), 'ntlm', $types));
// Remote Username format.
$settings->add(new auth_ldap_admin_setting_special_ntlm_configtext('auth_ldap/ntlmsso_remoteuserformat',
get_string('auth_ntlmsso_remoteuserformat_key', 'auth_ldap'),
get_string('auth_ntlmsso_remoteuserformat', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
}
// Display locking / mapping of profile fields.
$authplugin = get_auth_plugin('ldap');
$help = get_string('auth_ldapextrafields', 'auth_ldap');
$help .= get_string('auth_updatelocal_expl', 'auth');
$help .= get_string('auth_fieldlock_expl', 'auth');
$help .= get_string('auth_updateremote_expl', 'auth');
$help .= '<hr />';
$help .= get_string('auth_updateremote_ldap', 'auth');
display_auth_lock_options($settings, $authplugin->authtype, $authplugin->userfields,
$help, true, true, $authplugin->get_custom_user_profile_fields());
}
+622
View File
@@ -0,0 +1,622 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_ldap;
/**
* LDAP authentication plugin tests.
*
* NOTE: in order to execute this test you need to set up
* OpenLDAP server with core, cosine, nis and internet schemas
* and add configuration constants to config.php or phpunit.xml configuration file:
*
* define('TEST_AUTH_LDAP_HOST_URL', 'ldap://127.0.0.1');
* define('TEST_AUTH_LDAP_BIND_DN', 'cn=someuser,dc=example,dc=local');
* define('TEST_AUTH_LDAP_BIND_PW', 'somepassword');
* define('TEST_AUTH_LDAP_DOMAIN', 'dc=example,dc=local');
*
* @package auth_ldap
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use auth_plugin_ldap;
use auth_ldap\task\{
sync_task,
asynchronous_sync_task,
};
/**
* LDAP authentication plugin tests.
*
* @package auth_ldap
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class auth_ldap_test extends \advanced_testcase {
public static function setUpBeforeClass(): void {
global $CFG;
parent::setUpBeforeClass();
require_once($CFG->dirroot . '/auth/ldap/auth.php');
require_once($CFG->libdir . '/ldaplib.php');
}
/**
* Data provider for auth_ldap tests
*
* Used to ensure that all the paged stuff works properly, irrespectively
* of the pagesize configured (that implies all the chunking and paging
* built in the plugis is doing its work consistently). Both searching and
* not searching within subcontexts.
*
* @return array[]
*/
public function auth_ldap_provider() {
$pagesizes = [1, 3, 5, 1000];
$subcontexts = [0, 1];
$combinations = [];
foreach ($pagesizes as $pagesize) {
foreach ($subcontexts as $subcontext) {
$combinations["pagesize {$pagesize}, subcontexts {$subcontext}"] = [$pagesize, $subcontext];
}
}
return $combinations;
}
/**
* General auth_ldap testcase
*
* @dataProvider auth_ldap_provider
* @param int $pagesize Value to be configured in settings controlling page size.
* @param int $subcontext Value to be configured in settings controlling searching in subcontexts.
*/
public function test_auth_ldap(int $pagesize, int $subcontext): void {
global $DB;
if (!extension_loaded('ldap')) {
$this->markTestSkipped('LDAP extension is not loaded.');
}
$this->resetAfterTest();
if (!defined('TEST_AUTH_LDAP_HOST_URL') or !defined('TEST_AUTH_LDAP_BIND_DN') or !defined('TEST_AUTH_LDAP_BIND_PW') or !defined('TEST_AUTH_LDAP_DOMAIN')) {
$this->markTestSkipped('External LDAP test server not configured.');
}
// Make sure we can connect the server.
$debuginfo = '';
if (!$connection = ldap_connect_moodle(TEST_AUTH_LDAP_HOST_URL, 3, 'rfc2307', TEST_AUTH_LDAP_BIND_DN, TEST_AUTH_LDAP_BIND_PW, LDAP_DEREF_NEVER, $debuginfo, false)) {
$this->markTestSkipped('Can not connect to LDAP test server: '.$debuginfo);
}
$this->enable_plugin();
// Create new empty test container.
$topdn = 'dc=moodletest,'.TEST_AUTH_LDAP_DOMAIN;
$this->recursive_delete($connection, TEST_AUTH_LDAP_DOMAIN, 'dc=moodletest');
$o = array();
$o['objectClass'] = array('dcObject', 'organizationalUnit');
$o['dc'] = 'moodletest';
$o['ou'] = 'MOODLETEST';
if (!ldap_add($connection, 'dc=moodletest,'.TEST_AUTH_LDAP_DOMAIN, $o)) {
$this->markTestSkipped('Can not create test LDAP container.');
}
// Create a few users.
$o = array();
$o['objectClass'] = array('organizationalUnit');
$o['ou'] = 'users';
ldap_add($connection, 'ou='.$o['ou'].','.$topdn, $o);
$createdusers = array();
for ($i=1; $i<=5; $i++) {
$this->create_ldap_user($connection, $topdn, $i);
$createdusers[] = 'username' . $i;
}
// Set up creators group.
$assignedroles = array('username1', 'username2');
$o = array();
$o['objectClass'] = array('posixGroup');
$o['cn'] = 'creators';
$o['gidNumber'] = 1;
$o['memberUid'] = $assignedroles;
ldap_add($connection, 'cn='.$o['cn'].','.$topdn, $o);
$creatorrole = $DB->get_record('role', array('shortname'=>'coursecreator'));
$this->assertNotEmpty($creatorrole);
// Configure the plugin a bit.
set_config('host_url', TEST_AUTH_LDAP_HOST_URL, 'auth_ldap');
set_config('start_tls', 0, 'auth_ldap');
set_config('ldap_version', 3, 'auth_ldap');
set_config('ldapencoding', 'utf-8', 'auth_ldap');
set_config('pagesize', $pagesize, 'auth_ldap');
set_config('bind_dn', TEST_AUTH_LDAP_BIND_DN, 'auth_ldap');
set_config('bind_pw', TEST_AUTH_LDAP_BIND_PW, 'auth_ldap');
set_config('user_type', 'rfc2307', 'auth_ldap');
set_config('contexts', 'ou=users,'.$topdn, 'auth_ldap');
set_config('search_sub', $subcontext, 'auth_ldap');
set_config('opt_deref', LDAP_DEREF_NEVER, 'auth_ldap');
set_config('user_attribute', 'cn', 'auth_ldap');
set_config('memberattribute', 'memberuid', 'auth_ldap');
set_config('memberattribute_isdn', 0, 'auth_ldap');
set_config('coursecreatorcontext', 'cn=creators,'.$topdn, 'auth_ldap');
set_config('removeuser', AUTH_REMOVEUSER_KEEP, 'auth_ldap');
set_config('field_map_email', 'mail', 'auth_ldap');
set_config('field_updatelocal_email', 'oncreate', 'auth_ldap');
set_config('field_updateremote_email', '0', 'auth_ldap');
set_config('field_lock_email', 'unlocked', 'auth_ldap');
set_config('field_map_firstname', 'givenName', 'auth_ldap');
set_config('field_updatelocal_firstname', 'oncreate', 'auth_ldap');
set_config('field_updateremote_firstname', '0', 'auth_ldap');
set_config('field_lock_firstname', 'unlocked', 'auth_ldap');
set_config('field_map_lastname', 'sn', 'auth_ldap');
set_config('field_updatelocal_lastname', 'oncreate', 'auth_ldap');
set_config('field_updateremote_lastname', '0', 'auth_ldap');
set_config('field_lock_lastname', 'unlocked', 'auth_ldap');
$this->assertEquals(2, $DB->count_records('user'));
$this->assertEquals(0, $DB->count_records('role_assignments'));
/** @var \auth_plugin_ldap $auth */
$auth = get_auth_plugin('ldap');
ob_start();
$sink = $this->redirectEvents();
$auth->sync_users(true);
$events = $sink->get_events();
$sink->close();
ob_end_clean();
// Check events, 5 users created with 2 users having roles.
$this->assertCount(7, $events);
foreach ($events as $index => $event) {
$username = $DB->get_field('user', 'username', array('id' => $event->relateduserid)); // Get username.
if ($event->eventname === '\core\event\user_created') {
$this->assertContains($username, $createdusers);
unset($events[$index]); // Remove matching event.
} else if ($event->eventname === '\core\event\role_assigned') {
$this->assertContains($username, $assignedroles);
unset($events[$index]); // Remove matching event.
} else {
$this->fail('Unexpected event found: ' . $event->eventname);
}
}
// If all the user_created and role_assigned events have matched
// then the $events array should be now empty.
$this->assertCount(0, $events);
$this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
$this->assertEquals(2, $DB->count_records('role_assignments'));
$this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$creatorrole->id)));
for ($i=1; $i<=5; $i++) {
$this->assertTrue($DB->record_exists('user', array('username'=>'username'.$i, 'email'=>'user'.$i.'@example.com', 'firstname'=>'Firstname'.$i, 'lastname'=>'Lastname'.$i)));
}
$this->delete_ldap_user($connection, $topdn, 1);
ob_start();
$sink = $this->redirectEvents();
$auth->sync_users(true);
$events = $sink->get_events();
$sink->close();
ob_end_clean();
// Check events, no new event.
$this->assertCount(0, $events);
$this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
$this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
$this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
$this->assertEquals(2, $DB->count_records('role_assignments'));
$this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$creatorrole->id)));
set_config('removeuser', AUTH_REMOVEUSER_SUSPEND, 'auth_ldap');
/** @var \auth_plugin_ldap $auth */
$auth = get_auth_plugin('ldap');
ob_start();
$sink = $this->redirectEvents();
$auth->sync_users(true);
$events = $sink->get_events();
$sink->close();
ob_end_clean();
// Check events, 1 user got updated.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\core\event\user_updated', $event);
$this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
$this->assertEquals(0, $DB->count_records('user', array('auth'=>'nologin', 'username'=>'username1')));
$this->assertEquals(1, $DB->count_records('user', array('auth'=>'ldap', 'suspended'=>'1', 'username'=>'username1')));
$this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
$this->assertEquals(2, $DB->count_records('role_assignments'));
$this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$creatorrole->id)));
$this->create_ldap_user($connection, $topdn, 1);
ob_start();
$sink = $this->redirectEvents();
$auth->sync_users(true);
$events = $sink->get_events();
$sink->close();
ob_end_clean();
// Check events, 1 user got updated.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\core\event\user_updated', $event);
$this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
$this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
$this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
$this->assertEquals(2, $DB->count_records('role_assignments'));
$this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$creatorrole->id)));
$DB->set_field('user', 'auth', 'nologin', array('username'=>'username1'));
ob_start();
$sink = $this->redirectEvents();
$auth->sync_users(true);
$events = $sink->get_events();
$sink->close();
ob_end_clean();
// Check events, 1 user got updated.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\core\event\user_updated', $event);
$this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
$this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
$this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
$this->assertEquals(2, $DB->count_records('role_assignments'));
$this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$creatorrole->id)));
set_config('removeuser', AUTH_REMOVEUSER_FULLDELETE, 'auth_ldap');
/** @var \auth_plugin_ldap $auth */
$auth = get_auth_plugin('ldap');
$this->delete_ldap_user($connection, $topdn, 1);
ob_start();
$sink = $this->redirectEvents();
$auth->sync_users(true);
$events = $sink->get_events();
$sink->close();
ob_end_clean();
// Check events, 2 events role_unassigned and user_deleted.
$this->assertCount(2, $events);
$event = array_pop($events);
$this->assertInstanceOf('\core\event\user_deleted', $event);
$event = array_pop($events);
$this->assertInstanceOf('\core\event\role_unassigned', $event);
$this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
$this->assertEquals(0, $DB->count_records('user', array('username'=>'username1')));
$this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
$this->assertEquals(1, $DB->count_records('user', array('deleted'=>1)));
$this->assertEquals(1, $DB->count_records('role_assignments'));
$this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$creatorrole->id)));
$this->create_ldap_user($connection, $topdn, 1);
ob_start();
$sink = $this->redirectEvents();
$auth->sync_users(true);
$events = $sink->get_events();
$sink->close();
ob_end_clean();
// Check events, 2 events role_assigned and user_created.
$this->assertCount(2, $events);
$event = array_pop($events);
$this->assertInstanceOf('\core\event\role_assigned', $event);
$event = array_pop($events);
$this->assertInstanceOf('\core\event\user_created', $event);
$this->assertEquals(6, $DB->count_records('user', array('auth'=>'ldap')));
$this->assertEquals(1, $DB->count_records('user', array('username'=>'username1')));
$this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
$this->assertEquals(1, $DB->count_records('user', array('deleted'=>1)));
$this->assertEquals(2, $DB->count_records('role_assignments'));
$this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$creatorrole->id)));
// Let's test syncing users in chunks of '1'.
set_config('field_updatelocal_email', 'onlogin', 'auth_ldap');
set_config('sync_updateuserchunk', 1, 'auth_ldap');
/** @var auth_plugin_ldap $auth */
$auth = get_auth_plugin('ldap');
$count = 0;
ob_start();
$auth->sync_users_update_callback(function ($users, $updatekeys) use (&$count) {
$count++;
});
ob_end_clean();
// After updating in chunks of '1', we should have counted more than one update.
$this->assertGreaterThan(1, $count);
ob_start();
\core\cron::setup_user();
$cron = new sync_task();
$cron->execute();
$this->runAdhocTasks('\auth_ldap\task\asynchronous_sync_task');
$output = ob_get_contents();
ob_end_clean();
// Use Reflection to make protected constants available.
$rp = new \ReflectionClassConstant(sync_task::class, 'MTRACE_MSG');
$synctaskmsg = $rp->getValue();
$rp = new \ReflectionClassConstant(asynchronous_sync_task::class, 'MTRACE_MSG');
$asynctaskmsg = $rp->getValue();
$this->assertMatchesRegularExpression(
sprintf('/%s.*%s/s', $synctaskmsg, $asynctaskmsg),
$output
);
$this->recursive_delete($connection, TEST_AUTH_LDAP_DOMAIN, 'dc=moodletest');
ldap_close($connection);
}
/**
* Test logging in via LDAP calls a user_loggedin event.
*/
public function test_ldap_user_loggedin_event(): void {
global $CFG, $DB, $USER;
$this->resetAfterTest();
$this->assertFalse(isloggedin());
$user = $DB->get_record('user', array('username'=>'admin'));
// Note: we are just going to trigger the function that calls the event,
// not actually perform a LDAP login, for the sake of sanity.
$ldap = new \auth_plugin_ldap();
// Set the key for the cache flag we want to set which is used by LDAP.
set_cache_flag($ldap->pluginconfig . '/ntlmsess', sesskey(), $user->username, AUTH_NTLMTIMEOUT);
// We are going to need to set the sesskey as the user's password in order for the LDAP log in to work.
update_internal_user_password($user, sesskey());
// The function ntlmsso_finish is responsible for triggering the event, so call it directly and catch the event.
$sink = $this->redirectEvents();
// We need to supress this function call, or else we will get the message "session_regenerate_id(): Cannot
// regenerate session id - headers already sent" as the ntlmsso_finish function calls complete_user_login
@$ldap->ntlmsso_finish();
$events = $sink->get_events();
$sink->close();
// Check that the event is valid.
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\core\event\user_loggedin', $event);
$this->assertEquals('user', $event->objecttable);
$this->assertEquals('2', $event->objectid);
$this->assertEquals(\context_system::instance()->id, $event->contextid);
}
/**
* Test logging in via LDAP calls a user_loggedin event.
*/
public function test_ldap_user_signup(): void {
global $CFG, $DB;
// User to create.
$user = array(
'username' => 'usersignuptest1',
'password' => 'Moodle2014!',
'idnumber' => 'idsignuptest1',
'firstname' => 'First Name User Test 1',
'lastname' => 'Last Name User Test 1',
'middlename' => 'Middle Name User Test 1',
'lastnamephonetic' => '最後のお名前のテスト一号',
'firstnamephonetic' => 'お名前のテスト一号',
'alternatename' => 'Alternate Name User Test 1',
'email' => 'usersignuptest1@example.com',
'description' => 'This is a description for user 1',
'city' => 'Perth',
'country' => 'AU',
'mnethostid' => $CFG->mnet_localhost_id,
'auth' => 'ldap'
);
if (!extension_loaded('ldap')) {
$this->markTestSkipped('LDAP extension is not loaded.');
}
$this->resetAfterTest();
if (!defined('TEST_AUTH_LDAP_HOST_URL') or !defined('TEST_AUTH_LDAP_BIND_DN') or !defined('TEST_AUTH_LDAP_BIND_PW') or !defined('TEST_AUTH_LDAP_DOMAIN')) {
$this->markTestSkipped('External LDAP test server not configured.');
}
// Make sure we can connect the server.
$debuginfo = '';
if (!$connection = ldap_connect_moodle(TEST_AUTH_LDAP_HOST_URL, 3, 'rfc2307', TEST_AUTH_LDAP_BIND_DN, TEST_AUTH_LDAP_BIND_PW, LDAP_DEREF_NEVER, $debuginfo, false)) {
$this->markTestSkipped('Can not connect to LDAP test server: '.$debuginfo);
}
$this->enable_plugin();
// Create new empty test container.
$topdn = 'dc=moodletest,'.TEST_AUTH_LDAP_DOMAIN;
$this->recursive_delete($connection, TEST_AUTH_LDAP_DOMAIN, 'dc=moodletest');
$o = array();
$o['objectClass'] = array('dcObject', 'organizationalUnit');
$o['dc'] = 'moodletest';
$o['ou'] = 'MOODLETEST';
if (!ldap_add($connection, 'dc=moodletest,'.TEST_AUTH_LDAP_DOMAIN, $o)) {
$this->markTestSkipped('Can not create test LDAP container.');
}
// Create a few users.
$o = array();
$o['objectClass'] = array('organizationalUnit');
$o['ou'] = 'users';
ldap_add($connection, 'ou='.$o['ou'].','.$topdn, $o);
// Configure the plugin a bit.
set_config('host_url', TEST_AUTH_LDAP_HOST_URL, 'auth_ldap');
set_config('start_tls', 0, 'auth_ldap');
set_config('ldap_version', 3, 'auth_ldap');
set_config('ldapencoding', 'utf-8', 'auth_ldap');
set_config('pagesize', '2', 'auth_ldap');
set_config('bind_dn', TEST_AUTH_LDAP_BIND_DN, 'auth_ldap');
set_config('bind_pw', TEST_AUTH_LDAP_BIND_PW, 'auth_ldap');
set_config('user_type', 'rfc2307', 'auth_ldap');
set_config('contexts', 'ou=users,'.$topdn, 'auth_ldap');
set_config('search_sub', 0, 'auth_ldap');
set_config('opt_deref', LDAP_DEREF_NEVER, 'auth_ldap');
set_config('user_attribute', 'cn', 'auth_ldap');
set_config('memberattribute', 'memberuid', 'auth_ldap');
set_config('memberattribute_isdn', 0, 'auth_ldap');
set_config('creators', 'cn=creators,'.$topdn, 'auth_ldap');
set_config('removeuser', AUTH_REMOVEUSER_KEEP, 'auth_ldap');
set_config('field_map_email', 'mail', 'auth_ldap');
set_config('field_updatelocal_email', 'oncreate', 'auth_ldap');
set_config('field_updateremote_email', '0', 'auth_ldap');
set_config('field_lock_email', 'unlocked', 'auth_ldap');
set_config('field_map_firstname', 'givenName', 'auth_ldap');
set_config('field_updatelocal_firstname', 'oncreate', 'auth_ldap');
set_config('field_updateremote_firstname', '0', 'auth_ldap');
set_config('field_lock_firstname', 'unlocked', 'auth_ldap');
set_config('field_map_lastname', 'sn', 'auth_ldap');
set_config('field_updatelocal_lastname', 'oncreate', 'auth_ldap');
set_config('field_updateremote_lastname', '0', 'auth_ldap');
set_config('field_lock_lastname', 'unlocked', 'auth_ldap');
set_config('passtype', 'md5', 'auth_ldap');
set_config('create_context', 'ou=users,'.$topdn, 'auth_ldap');
$this->assertEquals(2, $DB->count_records('user'));
$this->assertEquals(0, $DB->count_records('role_assignments'));
/** @var \auth_plugin_ldap $auth */
$auth = get_auth_plugin('ldap');
$sink = $this->redirectEvents();
$mailsink = $this->redirectEmails();
$auth->user_signup((object)$user, false);
$this->assertEquals(1, $mailsink->count());
$events = $sink->get_events();
$sink->close();
// Verify 2 events get generated.
$this->assertCount(2, $events);
// Get record from db.
$dbuser = $DB->get_record('user', array('username' => $user['username']));
$user['id'] = $dbuser->id;
// Last event is user_created.
$event = array_pop($events);
$this->assertInstanceOf('\core\event\user_created', $event);
$this->assertEquals($user['id'], $event->objectid);
$this->assertEquals(\context_user::instance($user['id']), $event->get_context());
// First event is user_password_updated.
$event = array_pop($events);
$this->assertInstanceOf('\core\event\user_password_updated', $event);
$this->assertEventContextNotUsed($event);
// Delete user which we just created.
ldap_delete($connection, 'cn='.$user['username'].',ou=users,'.$topdn);
}
protected function create_ldap_user($connection, $topdn, $i) {
$o = array();
$o['objectClass'] = array('inetOrgPerson', 'organizationalPerson', 'person', 'posixAccount');
$o['cn'] = 'username'.$i;
$o['sn'] = 'Lastname'.$i;
$o['givenName'] = 'Firstname'.$i;
$o['uid'] = $o['cn'];
$o['uidnumber'] = 2000+$i;
$o['gidNumber'] = 1000+$i;
$o['homeDirectory'] = '/';
$o['mail'] = 'user'.$i.'@example.com';
$o['userPassword'] = 'pass'.$i;
ldap_add($connection, 'cn='.$o['cn'].',ou=users,'.$topdn, $o);
}
protected function delete_ldap_user($connection, $topdn, $i) {
ldap_delete($connection, 'cn=username'.$i.',ou=users,'.$topdn);
}
protected function enable_plugin() {
$auths = get_enabled_auth_plugins();
if (!in_array('ldap', $auths)) {
$auths[] = 'ldap';
}
set_config('auth', implode(',', $auths));
}
protected function recursive_delete($connection, $dn, $filter) {
if ($res = ldap_list($connection, $dn, $filter, array('dn'))) {
$info = ldap_get_entries($connection, $res);
ldap_free_result($res);
if ($info['count'] > 0) {
if ($res = ldap_search($connection, "$filter,$dn", 'cn=*', array('dn'))) {
$info = ldap_get_entries($connection, $res);
ldap_free_result($res);
foreach ($info as $i) {
if (isset($i['dn'])) {
ldap_delete($connection, $i['dn']);
}
}
}
if ($res = ldap_search($connection, "$filter,$dn", 'ou=*', array('dn'))) {
$info = ldap_get_entries($connection, $res);
ldap_free_result($res);
foreach ($info as $i) {
if (isset($i['dn']) and $info[0]['dn'] != $i['dn']) {
ldap_delete($connection, $i['dn']);
}
}
}
ldap_delete($connection, "$filter,$dn");
}
}
}
}
+15
View File
@@ -0,0 +1,15 @@
This files describes API changes in the auth_ldap code.
=== 3.4 ===
* The "auth_ldap/coursecreators" setting was replaced with dynamically generated "auth_ldap/<role>context" settings,
migrating any existing value to a new setting in this style.
=== 3.3 ===
* The config.html file was migrated to use the admin settings API.
The identifier for configuration data stored in config_plugins table was converted from 'auth/ldap' to 'auth_ldap'.
=== 2.9.1 ===
* auth_plugin_ldap::update_user_record() accepts an additional (optional) param
to trigger update event.
+30
View File
@@ -0,0 +1,30 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Version details
*
* @package auth_ldap
* @author Martin Dougiamas
* @author Iñaki Arenaza
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'auth_ldap'; // Full name of the plugin (used for diagnostics)
+465
View File
@@ -0,0 +1,465 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
use auth_lti\local\ltiadvantage\entity\user_migration_claim;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/authlib.php');
require_once($CFG->libdir.'/accesslib.php');
/**
* LTI Authentication plugin.
*
* @package auth_lti
* @copyright 2016 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class auth_plugin_lti extends \auth_plugin_base {
/**
* @var int constant representing the automatic account provisioning mode.
* On first launch, for a previously unbound user, this mode dictates that a new Moodle account will be created automatically
* for the user and bound to their platform credentials {iss, sub}.
*/
public const PROVISIONING_MODE_AUTO_ONLY = 1;
/**
* @var int constant representing the prompt for new or existing provisioning mode.
* On first launch, for a previously unbound user, the mode dictates that the launch user will be presented with an options
* view, allowing them to select either 'link an existing account' or 'create a new account for me'.
*/
public const PROVISIONING_MODE_PROMPT_NEW_EXISTING = 2;
/**
* @var int constant representing the prompt for existing only provisioning mode.
* On first launch, for a previously unbound user, the mode dictates that the launch user will be presented with a view allowing
* them to link an existing account only. This is useful for situations like deep linking, where an existing account is needed.
*/
public const PROVISIONING_MODE_PROMPT_EXISTING_ONLY = 3;
/**
* Constructor.
*/
public function __construct() {
$this->authtype = 'lti';
}
/**
* Users can not log in via the traditional login form.
*
* @param string $username The username
* @param string $password The password
* @return bool Authentication success or failure
*/
public function user_login($username, $password) {
return false;
}
/**
* Authenticate the user based on the unique {iss, sub} tuple present in the OIDC JWT.
*
* This method ensures a Moodle user account has been found or is created, that the user is linked to the relevant
* LTI Advantage credentials (iss, sub) and that the user account is logged in.
*
* Launch code can therefore rely on this method to get a session before doing things like calling require_login().
*
* This method supports two workflows:
* 1. Automatic account provisioning - where the complete_login() call will ALWAYS create/find a user and return to
* calling code directly. No user interaction is required.
*
* 2. Manual account provisioning - where the complete_login() call will redirect ONLY ONCE to a login page,
* where the user can decide whether to use an automatically provisioned account, or bind an existing user account.
* When the decision has been made, the relevant account is bound and the user is redirected back to $returnurl.
* Once an account has been bound via this selection process, subsequent calls to complete_login() will return to
* calling code directly. Any calling code must provide its $returnurl to support the return from the account
* selection process and must also take care to cache any JWT data appropriately, since the return will not inlude
* that information.
*
* Which workflow is chosen depends on the roles present in the JWT.
* For teachers/admins, manual provisioning will take place. These user type are likely to have existing accounts.
* For learners, automatic provisioning will take place.
*
* Migration of legacy users is supported, however, only for the Learner role (automatic provisioning). Admins and
* teachers are likely to have existing accounts and we want them to be able to select and bind these, rather than
* binding an automatically provisioned legacy account which doesn't represent their real user account.
*
* The JWT data must be verified elsewhere. The code here assumes its integrity/authenticity.
*
* @param array $launchdata the JWT data provided in the link launch.
* @param moodle_url $returnurl the local URL to return to if authentication workflows are required.
* @param int $provisioningmode the desired account provisioning mode, which controls the auth flow for unbound users.
* @param array $legacyconsumersecrets an array of secrets used by the legacy consumer if a migration claim exists.
* @throws coding_exception if the specified provisioning mode is invalid.
*/
public function complete_login(array $launchdata, moodle_url $returnurl, int $provisioningmode,
array $legacyconsumersecrets = []): void {
// The platform user is already linked with a user account.
if ($this->get_user_binding($launchdata['iss'], $launchdata['sub'])) {
$user = $this->find_or_create_user_from_launch($launchdata);
if (isloggedin()) {
// If a different user is currently logged in, authenticate the linked user instead.
global $USER;
if ($USER->id !== $user->id) {
complete_user_login($user);
}
// If the linked user is already logged in, skip the call to complete_user_login() because this affects deep linking
// workflows on sites publishing and consuming resources on the same site, due to the regenerated sesskey.
} else {
complete_user_login($user);
}
// Always sync the PII, regardless of whether we're already authenticated as this user or not.
$this->update_user_account($user, $launchdata, $launchdata['iss']);
return;
}
// The platform user is not bound to a user account, check provisioning mode now.
if (!$this->is_valid_provisioning_mode($provisioningmode)) {
throw new coding_exception('Invalid account provisioning mode provided to complete_login().');
}
switch ($provisioningmode) {
case self::PROVISIONING_MODE_AUTO_ONLY:
// Automatic provisioning - this will create/migrate a user account and log the user in.
$user = $this->find_or_create_user_from_launch($launchdata, $legacyconsumersecrets);
complete_user_login($user);
$this->update_user_account($user, $launchdata, $launchdata['iss']);
break;
case self::PROVISIONING_MODE_PROMPT_NEW_EXISTING:
case self::PROVISIONING_MODE_PROMPT_EXISTING_ONLY:
default:
// Allow linking an existing account or creation of a new account via an intermediate account options page.
// Cache the relevant data and take the user to the account options page.
// Note: This mode also depends on the global auth config 'authpreventaccountcreation'. If set, only existing
// accounts can be bound in this provisioning mode.
global $SESSION;
$SESSION->auth_lti = (object)[
'launchdata' => $launchdata,
'returnurl' => $returnurl,
'provisioningmode' => $provisioningmode
];
redirect(new moodle_url('/auth/lti/login.php', [
'sesskey' => sesskey(),
]));
break;
}
}
/**
* Get a Moodle user account for the LTI user based on the user details returned by a NRPS 2 membership call.
*
* This method expects a single member structure, in array format, as defined here:
* See: https://www.imsglobal.org/spec/lti-nrps/v2p0#membership-container-media-type.
*
* This method supports migration of user accounts used in legacy launches, provided the legacy consumerkey corresponding to
* to the legacy consumer is provided. Calling code will have verified the migration claim during initial launches and should
* have the consumer key mapped to the deployment, ready to pass in.
*
* @param array $member the member data, in array format.
* @param string $iss the issuer to which the member relates.
* @param string $legacyconsumerkey optional consumer key mapped to the deployment to facilitate user migration.
* @return stdClass a Moodle user record.
*/
public function find_or_create_user_from_membership(array $member, string $iss,
string $legacyconsumerkey = ''): stdClass {
// Picture is not synced with membership-based auths because sync tasks may wish to perform slow operations like this a
// different way.
unset($member['picture']);
if ($binduser = $this->get_user_binding($iss, $member['user_id'])) {
$user = \core_user::get_user($binduser);
$this->update_user_account($user, $member, $iss);
return \core_user::get_user($user->id);
} else {
if (!empty($legacyconsumerkey)) {
// Consumer key is required to attempt user migration because legacy users were identified by a
// username consisting of the consumer key and user id.
// See the legacy \enrol_lti\helper::create_username() for details.
$legacyuserid = $member['lti11_legacy_user_id'] ?? $member['user_id'];
$username = 'enrol_lti' .
sha1($legacyconsumerkey . '::' . $legacyconsumerkey . ':' . $legacyuserid);
if ($user = \core_user::get_user_by_username($username)) {
$this->create_user_binding($iss, $member['user_id'], $user->id);
$this->update_user_account($user, $member, $iss);
return \core_user::get_user($user->id);
}
}
$user = $this->create_new_account($member, $iss);
return \core_user::get_user($user->id);
}
}
/**
* Get a Moodle user account for the LTI user corresponding to the user defined in a link launch.
*
* This method supports migration of user accounts used in legacy launches, provided the legacy consumer secrets corresponding
* to the legacy consumer are provided. If calling code wishes migration to be role-specific, it should check roles accordingly
* itself and pass relevant data in - as auth_plugin_lti::complete_login() does.
*
* @param array $launchdata all data in the decoded JWT including iss and sub.
* @param array $legacyconsumersecrets all secrets found for the legacy consumer, facilitating user migration.
* @return stdClass the Moodle user who is mapped to the platform user identified in the JWT data.
*/
public function find_or_create_user_from_launch(array $launchdata, array $legacyconsumersecrets = []): stdClass {
if ($binduser = $this->get_user_binding($launchdata['iss'], $launchdata['sub'])) {
return \core_user::get_user($binduser);
} else {
// Is the intent to migrate a user account used in legacy launches?
if (!empty($legacyconsumersecrets)) {
try {
// Validate the migration claim and try to find a legacy user.
$usermigrationclaim = new user_migration_claim($launchdata, $legacyconsumersecrets);
$username = 'enrol_lti' .
sha1($usermigrationclaim->get_consumer_key() . '::' .
$usermigrationclaim->get_consumer_key() . ':' . $usermigrationclaim->get_user_id());
if ($user = core_user::get_user_by_username($username)) {
$this->create_user_binding($launchdata['iss'], $launchdata['sub'], $user->id);
return core_user::get_user($user->id);
}
} catch (Exception $e) {
// There was an issue validating the user migration claim. We don't want to fail auth entirely though.
// Rather, we want to fall back to new account creation and log the attempt.
debugging("There was a problem migrating the LTI user '{$launchdata['sub']}' on the platform " .
"'{$launchdata['iss']}'. The migration claim could not be validated. A new account will be created.");
}
}
// At the point of the creation, to ensure the user_created event correctly reflects the creating user of '0' (the user
// performing the action), ensure any active session is terminated and an empty session initialised.
$this->empty_session();
$user = $this->create_new_account($launchdata, $launchdata['iss']);
return core_user::get_user($user->id);
}
}
/**
* Create a binding between the LTI user, as identified by {iss, sub} tuple and the user id.
*
* @param string $iss the issuer URL identifying the platform to which to user belongs.
* @param string $sub the sub string identifying the user on the platform.
* @param int $userid the id of the Moodle user account to bind.
*/
public function create_user_binding(string $iss, string $sub, int $userid): void {
global $DB;
$timenow = time();
$issuer256 = hash('sha256', $iss);
$sub256 = hash('sha256', $sub);
if ($DB->record_exists('auth_lti_linked_login', ['issuer256' => $issuer256, 'sub256' => $sub256])) {
return;
}
$rec = [
'userid' => $userid,
'issuer' => $iss,
'issuer256' => $issuer256,
'sub' => $sub,
'sub256' => $sub256,
'timecreated' => $timenow,
'timemodified' => $timenow
];
$DB->insert_record('auth_lti_linked_login', $rec);
}
/**
* Gets the id of the linked Moodle user account for an LTI user, or null if not found.
*
* @param string $issuer the issuer to which the user belongs.
* @param string $sub the sub string identifying the user on the issuer.
* @return int|null the id of the corresponding Moodle user record, or null if not found.
*/
public function get_user_binding(string $issuer, string $sub): ?int {
global $DB;
$issuer256 = hash('sha256', $issuer);
$sub256 = hash('sha256', $sub);
try {
$binduser = $DB->get_field('auth_lti_linked_login', 'userid',
['issuer256' => $issuer256, 'sub256' => $sub256], MUST_EXIST);
} catch (\dml_exception $e) {
$binduser = null;
}
return $binduser;
}
/**
* If there's an existing session, inits an empty session.
*
* @return void
*/
protected function empty_session(): void {
if (isloggedin()) {
\core\session\manager::init_empty_session();
}
}
/**
* Check whether a provisioning mode is valid or not.
*
* @param int $mode the mode
* @return bool true if valid for use, false otherwise.
*/
protected function is_valid_provisioning_mode(int $mode): bool {
$validmodes = [
self::PROVISIONING_MODE_AUTO_ONLY,
self::PROVISIONING_MODE_PROMPT_NEW_EXISTING,
self::PROVISIONING_MODE_PROMPT_EXISTING_ONLY
];
return in_array($mode, $validmodes);
}
/**
* Create a new user account based on the user data either in the launch JWT or from a membership call.
*
* @param array $userdata the user data coming from either a launch or membership service call.
* @param string $iss the issuer to which the user belongs.
* @return stdClass a complete Moodle user record.
*/
protected function create_new_account(array $userdata, string $iss): stdClass {
global $CFG;
require_once($CFG->dirroot.'/user/lib.php');
// Launches and membership calls handle the user id differently.
// Launch uses 'sub', whereas member uses 'user_id'.
$userid = !empty($userdata['sub']) ? $userdata['sub'] : $userdata['user_id'];
$user = new stdClass();
$user->username = 'enrol_lti_13_' . sha1($iss . '_' . $userid);
// If the email was stripped/not set then fill it with a default one.
// This stops the user from being redirected to edit their profile page.
$email = !empty($userdata['email']) ? $userdata['email'] :
'enrol_lti_13_' . sha1($iss . '_' . $userid) . "@example.com";
$email = \core_user::clean_field($email, 'email');
$user->email = $email;
$user->auth = 'lti';
$user->mnethostid = $CFG->mnet_localhost_id;
$user->firstname = $userdata['given_name'] ?? $userid;
$user->lastname = $userdata['family_name'] ?? $iss;
$user->password = '';
$user->confirmed = 1;
$user->id = user_create_user($user, false);
// Link this user with the LTI credentials for future use.
$this->create_user_binding($iss, $userid, $user->id);
return (object) get_complete_user_data('id', $user->id);
}
/**
* Update the personal fields of the user account, based on data present in either a launch of member sync call.
*
* @param stdClass $user the Moodle user account to update.
* @param array $userdata the user data coming from either a launch or membership service call.
* @param string $iss the issuer to which the user belongs.
*/
public function update_user_account(stdClass $user, array $userdata, string $iss): void {
global $CFG;
require_once($CFG->dirroot.'/user/lib.php');
if ($user->auth !== 'lti') {
return;
}
// Launches and membership calls handle the user id differently.
// Launch uses 'sub', whereas member uses 'user_id'.
$platformuserid = !empty($userdata['sub']) ? $userdata['sub'] : $userdata['user_id'];
$email = !empty($userdata['email']) ? $userdata['email'] :
'enrol_lti_13_' . sha1($iss . '_' . $platformuserid) . "@example.com";
$email = \core_user::clean_field($email, 'email');
$update = [
'id' => $user->id,
'firstname' => $userdata['given_name'] ?? $platformuserid,
'lastname' => $userdata['family_name'] ?? $iss,
'email' => $email
];
$userfieldstocompare = array_intersect_key((array) $user, $update);
if (!empty(array_diff($update, $userfieldstocompare))) {
user_update_user($update); // Only update if there's a change.
}
if (!empty($userdata['picture'])) {
try {
$this->update_user_picture($user->id, $userdata['picture']);
} catch (Exception $e) {
debugging("Error syncing the profile picture for user '$user->id' during LTI authentication.");
}
}
}
/**
* Update the user's picture with the image stored at $url.
*
* @param int $userid the id of the user to update.
* @param string $url the string URL where the new image can be found.
* @throws moodle_exception if there were any problems updating the picture.
*/
protected function update_user_picture(int $userid, string $url): void {
global $CFG, $DB;
require_once($CFG->libdir . '/filelib.php');
require_once($CFG->libdir . '/gdlib.php');
$fs = get_file_storage();
$context = \context_user::instance($userid, MUST_EXIST);
$fs->delete_area_files($context->id, 'user', 'newicon');
$filerecord = array(
'contextid' => $context->id,
'component' => 'user',
'filearea' => 'newicon',
'itemid' => 0,
'filepath' => '/'
);
$urlparams = array(
'calctimeout' => false,
'timeout' => 5,
'skipcertverify' => true,
'connecttimeout' => 5
);
try {
$fs->create_file_from_url($filerecord, $url, $urlparams);
} catch (\file_exception $e) {
throw new moodle_exception(get_string($e->errorcode, $e->module, $e->a));
}
$iconfile = $fs->get_area_files($context->id, 'user', 'newicon', false, 'itemid', false);
// There should only be one.
$iconfile = reset($iconfile);
// Something went wrong while creating temp file - remove the uploaded file.
if (!$iconfile = $iconfile->copy_content_to_temp()) {
$fs->delete_area_files($context->id, 'user', 'newicon');
throw new moodle_exception('There was a problem copying the profile picture to temp.');
}
// Copy file to temporary location and the send it for processing icon.
$newpicture = (int) process_new_icon($context, 'user', 'icon', 0, $iconfile);
// Delete temporary file.
@unlink($iconfile);
// Remove uploaded file.
$fs->delete_area_files($context->id, 'user', 'newicon');
// Set the user's picture.
$DB->set_field('user', 'picture', $newpicture, array('id' => $userid));
}
}
@@ -0,0 +1,150 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_lti\local\ltiadvantage\entity;
/**
* A simplified representation of a 'https://purl.imsglobal.org/spec/lti/claim/lti1p1' migration claim.
*
* This serves the purpose of migrating a legacy user account only. Claim properties that do not relate to user migration are not
* included or handled by this representation.
*
* See https://www.imsglobal.org/spec/lti/v1p3/migr#lti-1-1-migration-claim
*
* @package auth_lti
* @copyright 2021 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_migration_claim {
/** @var string the LTI 1.1 consumer key */
private $consumerkey;
/** @var string the LTI 1.1 user identifier.
* This is only included in the claim if it differs to the value included in the LTI 1.3 'sub' claim.
* If not included, the value will be taken from 'sub'.
*/
private $userid;
/**
* The migration_claim constructor.
*
* The signature of a migration claim must be verifiable. To achieve this, the constructor takes a list of secrets
* corresponding to the 'oauth_consumer_key' provided in the 'https://purl.imsglobal.org/spec/lti/claim/lti1p1'
* claim. How these secrets are determined is not the responsibility of this class. The constructor assumes these
* correspond.
*
* @param array $jwt the array of claim data, as received in a resource link launch JWT.
* @param array $consumersecrets a list of consumer secrets for the consumerkey included in the migration claim.
* @throws \coding_exception if the claim data is invalid.
*/
public function __construct(array $jwt, array $consumersecrets) {
// Can't get a claim instance without the claim data.
if (empty($jwt['https://purl.imsglobal.org/spec/lti/claim/lti1p1'])) {
throw new \coding_exception("Missing the 'https://purl.imsglobal.org/spec/lti/claim/lti1p1' JWT claim");
}
$claim = $jwt['https://purl.imsglobal.org/spec/lti/claim/lti1p1'];
// The oauth_consumer_key property MUST be sent.
// See: https://www.imsglobal.org/spec/lti/v1p3/migr#oauth_consumer_key.
if (empty($claim['oauth_consumer_key'])) {
throw new \coding_exception("Missing 'oauth_consumer_key' property in lti1p1 migration claim.");
}
// The oauth_consumer_key_sign property MAY be sent.
// For user migration to take place, however, this is deemed a required property since Moodle identified its
// legacy users through a combination of consumerkey and userid.
// See: https://www.imsglobal.org/spec/lti/v1p3/migr#oauth_consumer_key_sign.
if (empty($claim['oauth_consumer_key_sign'])) {
throw new \coding_exception("Missing 'oauth_consumer_key_sign' property in lti1p1 migration claim.");
}
if (!$this->verify_signature(
$claim['oauth_consumer_key'],
$claim['oauth_consumer_key_sign'],
$jwt['https://purl.imsglobal.org/spec/lti/claim/deployment_id'],
$jwt['iss'],
$jwt['aud'],
$jwt['exp'],
$jwt['nonce'],
$consumersecrets
)) {
throw new \coding_exception("Invalid 'oauth_consumer_key_sign' signature in lti1p1 claim.");
}
$this->consumerkey = $claim['oauth_consumer_key'];
$this->userid = $claim['user_id'] ?? $jwt['sub'];
}
/**
* Verify the claim signature by recalculating it using the launch data and cross-checking consumer secrets.
*
* @param string $consumerkey the LTI 1.1 consumer key.
* @param string $signature a signature of the LTI 1.1 consumer key and associated launch data.
* @param string $deploymentid the deployment id included in the launch.
* @param string $platform the platform included in the launch.
* @param string $clientid the client id included in the launch.
* @param string $exp the exp included in the launch.
* @param string $nonce the nonce included in the launch.
* @param array $consumersecrets the list of consumer secrets used with the given $consumerkey param
* @return bool true if the signature was verified, false otherwise.
*/
private function verify_signature(string $consumerkey, string $signature, string $deploymentid, string $platform,
string $clientid, string $exp, string $nonce, array $consumersecrets): bool {
$base = [
$consumerkey,
$deploymentid,
$platform,
$clientid,
$exp,
$nonce
];
$basestring = implode('&', $base);
// Legacy enrol_lti code permits tools to share a consumer key but use different secrets. This results in
// potentially many secrets per mapped tool consumer. As such, when generating the migration claim it's
// impossible to know which secret the platform will use to sign the consumer key. The consumer key in the
// migration claim is thus verified by trying all known secrets for the consumer, until either a match is found
// or no signatures match.
foreach ($consumersecrets as $consumersecret) {
$calculatedsignature = base64_encode(hash_hmac('sha256', $basestring, $consumersecret));
if ($signature === $calculatedsignature) {
return true;
}
}
return false;
}
/**
* Return the consumer key stored in the claim.
*
* @return string the consumer key included in the claim.
*/
public function get_consumer_key(): string {
return $this->consumerkey;
}
/**
* Return the LTI 1.1 user id stored in the claim.
*
* @return string the user id, or null if not provided in the claim.
*/
public function get_user_id(): string {
return $this->userid;
}
}
@@ -0,0 +1,48 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_lti\local\ltiadvantage\event;
use auth_lti\local\ltiadvantage\utility\cookie_helper;
use core\event\user_loggedin;
/**
* Event handler for auth_lti.
*
* @package auth_lti
* @copyright 2024 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_handler {
/**
* Allows the plugin to augment Set-Cookie headers when the user_loggedin event is fired as part of complete_user_login() calls.
*
* @param user_loggedin $event the event
* @return void
*/
public static function handle_user_loggedin(user_loggedin $event): void {
// The event data isn't important here. The intent of this listener is to ensure that the MoodleSession cookie is set up
// properly during LTI launches + login. This means two things:
// i) it's set with SameSite=None; Secure; where possible (since OIDC needs HTTPS this will almost always be possible).
// ii) it set with the 'Partitioned' attribute, when required.
// The former ensures cross-site cookies are sent for embedded launches. The latter is an opt-in flag needed to use Chrome's
// partitioning mechanism, CHIPS.
if (cookie_helper::cookies_supported()) {
cookie_helper::setup_session_cookie();
}
}
}
@@ -0,0 +1,173 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_lti\local\ltiadvantage\utility;
use core\session\utility\cookie_helper as core_cookie_helper;
/**
* Helper class providing utils dealing with cookies in LTI, particularly 3rd party cookies.
*
* @package auth_lti
* @copyright 2024 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class cookie_helper {
/** @var int Cookies are not supported. */
public const COOKIE_METHOD_NOT_SUPPORTED = 0;
/** @var int Cookies are supported without explicit partitioning. */
public const COOKIE_METHOD_NO_PARTITIONING = 1;
/** @var int Cookies are supported via explicit partitioning. */
public const COOKIE_METHOD_EXPLICIT_PARTITIONING = 2;
/**
* Check whether cookies can be used with the current user agent and, if so, via what method they are set.
*
* Currently, this tries 2 modes of setting a test cookie:
* 1. Setting a SameSite=None, Secure cookie. This will work in any first party context, and in 3rd party contexts for
* any browsers supporting automatic partitioning of 3rd party cookies (E.g. Firefox, Brave).
* 2. If 1 fails, setting a cookie with the Chrome 'Partitioned' attribute included, opting that cookie into CHIPS. This will
* work for Chrome.
*
* Upon completion of the cookie check, the check sets a SESSION flag indicating the method used to set the cookie, and upgrades
* the session cookie ('MoodleSession') using the respective method. This ensure the session cookie will continue to be sent.
*
* Then, the following methods can be used by client code to query whether the UA supports cookies, and how:
* @see self::cookies_supported() - whether it could be set at all.
* @see self::get_cookies_supported_mode() - if a cookie could be set, what mode was used to set it.
*
* This permits client code to make sure it's setting its cookies appropriately (via the advertised method), and allows it to
* present notices - such as in the case where a given UA is found to be lacking the requisite cookie support.
* E.g.
* cookie_helper::do_cookie_check($mypageurl);
* if (!cookie_helper::cookies_supported()) {
* // Print a notice stating that cookie support is required.
* }
* // Elsewhere in other client code...
* if (cookie_helper::get_cookies_supported_mode() === cookie_helper::COOKIE_METHOD_EXPLICIT_PARTITIONING) {
* // Set a cookie, making sure to use the helper to also opt-in to partitioning.
* setcookie('myauthcookie', 'myauthcookievalue', ['samesite' => 'None', 'secure' => true]);
* cookie_helper::add_partitioning_to_cookie('myauthcookie');
* }
*
* @param \moodle_url $pageurl the URL of the page making the check, used to redirect back to after setting test cookies.
* @return void
*/
public static function do_cookie_check(\moodle_url $pageurl): void {
global $_COOKIE, $SESSION, $CFG;
$cookiecheck1 = optional_param('cookiecheck1', null, PARAM_INT);
$cookiecheck2 = optional_param('cookiecheck2', null, PARAM_INT);
if (empty($cookiecheck1)) {
// Start the cookie check. Set two test cookies - one samesite none, and one partitioned - and redirect.
// Set cookiecheck to show the check has started.
self::set_test_cookie('cookiecheck1', self::COOKIE_METHOD_NO_PARTITIONING);
self::set_test_cookie('cookiecheck2', self::COOKIE_METHOD_EXPLICIT_PARTITIONING, true);
$pageurl->params([
'cookiecheck1' => self::COOKIE_METHOD_NO_PARTITIONING,
'cookiecheck2' => self::COOKIE_METHOD_EXPLICIT_PARTITIONING,
]);
// LTI needs to guarantee the 'SameSite=None', 'Secure' (and sometimes 'Partitioned') attributes are set on the
// MoodleSession cookie. This is done via manipulation of the outgoing headers after the cookie check redirect. To
// guarantee these outgoing Set-Cookie headers will be created after the redirect, expire the current cookie.
core_cookie_helper::expire_moodlesession();
redirect($pageurl);
} else {
// Have already started a cookie check, so check the result.
$cookie1received = isset($_COOKIE['cookiecheck1']) && $_COOKIE['cookiecheck1'] == $cookiecheck1;
$cookie2received = isset($_COOKIE['cookiecheck2']) && $_COOKIE['cookiecheck2'] == $cookiecheck2;
if ($cookie1received || $cookie2received) {
// The test cookie could be set and received.
// Set a session flag storing the method used to set it, and make sure the session cookie uses this method.
$cookiemethod = $cookie1received ? self::COOKIE_METHOD_NO_PARTITIONING : self::COOKIE_METHOD_EXPLICIT_PARTITIONING;
$SESSION->auth_lti_cookie_method = $cookiemethod;
self::setup_session_cookie();
}
}
}
/**
* If a cookie check has been made, returns whether cookies could be set or not.
*
* @return bool whether cookies are supported or not.
*/
public static function cookies_supported(): bool {
return self::get_cookies_supported_method() !== self::COOKIE_METHOD_NOT_SUPPORTED;
}
/**
* If a cookie check has been made, gets the method used to set a cookie, or self::COOKIE_METHOD_NOT_SUPPORTED if not supported.
*
* For cookie methods:
* @see self::COOKIE_METHOD_NOT_SUPPORTED
* @see self::COOKIE_METHOD_NO_PARTITIONING
* @see self::COOKIE_METHOD_EXPLICIT_PARTITIONING
*
* @return int the constant representing the method by which the cookie was set, or not.
*/
public static function get_cookies_supported_method(): int {
global $SESSION;
return $SESSION->auth_lti_cookie_method ?? self::COOKIE_METHOD_NOT_SUPPORTED;
}
/**
* Sets up the session cookie according to the method used in the cookie check, and with SameSite=None; Secure attributes.
*
* @return void
*/
public static function setup_session_cookie(): void {
global $CFG;
require_once($CFG->libdir . '/sessionlib.php');
if (is_moodle_cookie_secure()) {
$atts = ['SameSite=None', 'Secure'];
if (self::get_cookies_supported_method() == self::COOKIE_METHOD_EXPLICIT_PARTITIONING) {
$atts[] = 'Partitioned';
}
core_cookie_helper::add_attributes_to_cookie_response_header('MoodleSession' . $CFG->sessioncookie, $atts);
}
}
/**
* Set a test cookie, using SameSite=None; Secure; attributes if possible, and with or without partitioning opt-in.
*
* @param string $name cookie name
* @param string $value cookie value
* @param bool $partitioned whether to try to add partitioning opt-in, which requires secure cookies (https sites).
* @return void
*/
private static function set_test_cookie(string $name, string $value, bool $partitioned = false): void {
global $CFG;
require_once($CFG->libdir . '/sessionlib.php');
$atts = ['expires' => time() + 30];
if (is_moodle_cookie_secure()) {
$atts['samesite'] = 'none';
$atts['secure'] = true;
}
setcookie($name, $value, $atts);
if (is_moodle_cookie_secure() && $partitioned) {
core_cookie_helper::add_attributes_to_cookie_response_header($name, ['Partitioned']);
}
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_lti\output;
use core\output\notification;
/**
* Renderer class for auth_lti.
*
* @package auth_lti
* @copyright 2021 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends \plugin_renderer_base {
/**
* Render the account options view, displayed to instructors on first launch if no account binding exists.
*
* @param int $provisioningmode the desired account provisioning mode, see auth_plugin_lti constants for details.
* @return string the html.
*/
public function render_account_binding_options_page(int $provisioningmode): string {
$formaction = new \moodle_url('/auth/lti/login.php');
$notification = new notification(get_string('firstlaunchnotice', 'auth_lti'), \core\notification::INFO, false);
$cancreateaccounts = !get_config('moodle', 'authpreventaccountcreation');
if ($provisioningmode == \auth_plugin_lti::PROVISIONING_MODE_PROMPT_EXISTING_ONLY) {
$cancreateaccounts = false;
}
$accountinfo = [];
if (isloggedin()) {
global $USER;
$accountinfo = [
'firstname' => $USER->firstname,
'lastname' => $USER->lastname,
'email' => $USER->email,
'picturehtml' => $this->output->user_picture($USER, ['size' => 35, 'class' => 'round']),
];
}
$context = [
'isloggedin' => isloggedin(),
'info' => $notification->export_for_template($this),
'formaction' => $formaction->out(),
'sesskey' => sesskey(),
'accountinfo' => $accountinfo,
'cancreateaccounts' => $cancreateaccounts,
];
return parent::render_from_template('auth_lti/local/ltiadvantage/login', $context);
}
/**
* Render the page displayed when the account binding is complete, letting the user continue to the launch.
*
* Callers can provide different messages depending on which type of binding took place. For example, a newly
* provisioned account may require a slightly different message to an existing account being linked.
*
* The return URL is the page the user will be taken back to when they click 'Continue'. This is likely the launch
* or deeplink launch endpoint but could be any calling code in LTI which wants to use the account binding workflow.
*
* @param notification $notification the notification containing the message describing the binding success.
* @param \moodle_url $returnurl the URL to return to when the user clicks continue on the rendered page.
* @return string the rendered HTML
*/
public function render_account_binding_complete(notification $notification, \moodle_url $returnurl): string {
$context = (object) [
'notification' => $notification->export_for_template($this),
'returnurl' => $returnurl->out()
];
return parent::render_from_template('auth_lti/local/ltiadvantage/account_binding_complete', $context);
}
}
+184
View File
@@ -0,0 +1,184 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_lti\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\context;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
/**
* Privacy Subsystem for auth_lti implementing null_provider.
*
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @package auth_lti
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\plugin\provider,
\core_privacy\local\request\core_userlist_provider {
/**
* Get all contexts contain user information for the given user.
*
* @param int $userid the id of the user.
* @return contextlist the list of contexts containing user information.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
$sql = "SELECT ctx.id
FROM {auth_lti_linked_login} ll
JOIN {context} ctx ON ctx.instanceid = ll.userid AND ctx.contextlevel = :contextlevel
WHERE ll.userid = :userid";
$params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER];
$contextlist = new contextlist();
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Export all user data for the user in the identified contexts.
*
* @param approved_contextlist $contextlist the list of approved contexts for the user.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
$user = $contextlist->get_user();
$linkedlogins = $DB->get_records('auth_lti_linked_login', ['userid' => $user->id], '',
'issuer, issuer256, sub, sub256, timecreated, timemodified');
foreach ($linkedlogins as $login) {
$data = (object)[
'timecreated' => transform::datetime($login->timecreated),
'timemodified' => transform::datetime($login->timemodified),
'issuer' => $login->issuer,
'issuer256' => $login->issuer256,
'sub' => $login->sub,
'sub256' => $login->sub256
];
writer::with_context(\context_user::instance($user->id))->export_data([
get_string('privacy:metadata:auth_lti', 'auth_lti'), $login->issuer
], $data);
}
}
/**
* Delete all user data for this context.
*
* @param \context $context The context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
if ($context->contextlevel != CONTEXT_USER) {
return;
}
static::delete_user_data($context->instanceid);
}
/**
* Delete user data in the list of given contexts.
*
* @param approved_contextlist $contextlist the list of contexts.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
if (empty($contextlist->count())) {
return;
}
$userid = $contextlist->get_user()->id;
foreach ($contextlist->get_contexts() as $context) {
if ($context->contextlevel != CONTEXT_USER) {
continue;
}
if ($context->instanceid == $userid) {
static::delete_user_data($context->instanceid);
}
}
}
/**
* Get the list of users within a specific context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!$context instanceof \context_user) {
return;
}
$sql = "SELECT userid
FROM {auth_lti_linked_login}
WHERE userid = ?";
$params = [$context->instanceid];
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
$context = $userlist->get_context();
if ($context instanceof \context_user) {
static::delete_user_data($context->instanceid);
}
}
/**
* Description of the metadata stored for users in auth_lti.
*
* @param collection $collection a collection to add to.
* @return collection the collection, with relevant metadata descriptions for auth_lti added.
*/
public static function get_metadata(collection $collection): collection {
$authfields = [
'userid' => 'privacy:metadata:auth_lti:userid',
'issuer' => 'privacy:metadata:auth_lti:issuer',
'issuer256' => 'privacy:metadata:auth_lti:issuer256',
'sub' => 'privacy:metadata:auth_lti:sub',
'sub256' => 'privacy:metadata:auth_lti:sub256',
'timecreated' => 'privacy:metadata:auth_lti:timecreated',
'timemodified' => 'privacy:metadata:auth_lti:timemodified'
];
$collection->add_database_table('auth_lti_linked_login', $authfields, 'privacy:metadata:auth_lti:tableexplanation');
$collection->link_subsystem('core_auth', 'privacy:metadata:auth_lti:authsubsystem');
return $collection;
}
/**
* Delete user data for the user.
*
* @param int $userid The id of the user.
*/
protected static function delete_user_data(int $userid) {
global $DB;
// Because we only use user contexts the instance ID is the user ID.
$DB->delete_records('auth_lti_linked_login', ['userid' => $userid]);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* LTI Auth plugin event handler definition.
*
* @package auth_lti
* @category event
* @copyright 2024 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$observers = [
[
'eventname' => '\core\event\user_loggedin',
'callback' => '\auth_lti\local\ltiadvantage\event\event_handler::handle_user_loggedin',
],
];
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="auth/lti/db" VERSION="20211005" COMMENT="XMLDB file for Moodle auth/lti"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="auth_lti_linked_login" COMMENT="Accounts linked to a users Moodle account.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The user account the LTI user is linked to."/>
<FIELD NAME="issuer" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="issuer256" TYPE="char" LENGTH="64" NOTNULL="true" SEQUENCE="false" COMMENT="SHA256 hash of the issuer from which the platform user originates."/>
<FIELD NAME="sub" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="sub256" TYPE="char" LENGTH="64" NOTNULL="true" SEQUENCE="false" COMMENT="SHA256 hash of the subject identifying the user for the issuer."/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="userid_key" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
<KEY NAME="unique_key" TYPE="unique" FIELDS="userid, issuer256, sub256"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
+45
View File
@@ -0,0 +1,45 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* LTI authentication plugin upgrade code
*
* @package auth_lti
* @copyright 2021 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Upgrade function.
*
* @param int $oldversion the version we are upgrading from.
* @return bool result.
*/
function xmldb_auth_lti_upgrade($oldversion) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+56
View File
@@ -0,0 +1,56 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'auth_lti', language 'en'.
*
* @package auth_lti
* @copyright 2016 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['accountcreatedsuccess'] = 'Your account has been created and is now ready to use.';
$string['accountlinkedsuccess'] = 'Your existing account has been successfully linked.';
$string['auth_ltidescription'] = 'The LTI authentication plugin, together with the \'Publish as LTI tool\' enrolment plugin, allows remote users to access selected courses and activities. In other words, Moodle functions as an LTI tool provider.';
$string['cannotcreateaccounts'] = 'Account creation is currently prohibited on this site.';
$string['createaccount'] = 'Create account';
$string['createaccountforme'] = 'Create an account for me';
$string['createnewaccount'] = 'I\'d like to create a new account';
$string['currentlyloggedinas'] = 'You are currently logged in as:';
$string['firstlaunchnotice'] = 'It looks like this is your first time here. Please select from one of the following account options.';
$string['getstartedwithnewaccount'] = 'Get started with a new account';
$string['haveexistingaccount'] = 'I have an existing account';
$string['linkthisaccount'] = 'Link this account';
$string['mustbeloggedin'] = 'Log in to link your existing account.';
$string['pluginname'] = 'LTI';
$string['privacy:metadata:auth_lti'] = 'LTI authentication';
$string['privacy:metadata:auth_lti:authsubsystem'] = 'This plugin is connected to the authentication subsystem.';
$string['privacy:metadata:auth_lti:issuer'] = 'The issuer URL identifying the platform to which the linked user belongs.';
$string['privacy:metadata:auth_lti:issuer256'] = 'The SHA256 hash of the issuer URL.';
$string['privacy:metadata:auth_lti:sub'] = 'The subject string identifying the user on the issuer.';
$string['privacy:metadata:auth_lti:sub256'] = 'The SHA256 hash of the subject string identifying the user on the issuer.';
$string['privacy:metadata:auth_lti:tableexplanation'] = 'LTI accounts linked to a user\'s Moodle account.';
$string['privacy:metadata:auth_lti:timecreated'] = 'The timestamp when the user account was linked to the LTI login.';
$string['privacy:metadata:auth_lti:timemodified'] = 'The timestamp when this record was modified.';
$string['privacy:metadata:auth_lti:userid'] = 'The ID of the user account which the LTI login is linked to';
$string['provisioningmodeauto'] = 'New accounts only (automatic)';
$string['provisioningmodenewexisting'] = 'Existing and new accounts (prompt)';
$string['provisioningmodeexistingonly'] = 'Existing accounts only (prompt)';
$string['useexistingaccount'] = 'Use existing account';
$string['welcome'] = 'Welcome!';
// Deprecated since Moodle 4.4.
$string['firstlaunchnoauthnotice'] = 'To link your existing account you must be logged in to the site. Please log in to the site in a new tab/window and then relaunch the tool here. For further information, see the documentation <a href="{$a}" target="_blank">Publish as LTI tool</a>.';
+1
View File
@@ -0,0 +1 @@
firstlaunchnoauthnotice,auth_lti
+35
View File
@@ -0,0 +1,35 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Callbacks for auth_lti.
*
* @package auth_lti
* @copyright 2021 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Callback to remove linked logins for deleted users.
*
* @param stdClass $user the user being deleted.
*/
function auth_lti_pre_user_delete($user) {
global $DB;
$DB->delete_records('auth_lti_linked_login', ['userid' => $user->id]);
}
+125
View File
@@ -0,0 +1,125 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Page allowing a platform user, identified by their {iss, sub} tuple, to be bound to a new or existing Moodle account.
*
* This is an LTI Advantage specific login feature.
*
* The auth flow defined in auth_lti\auth::complete_login() redirects here when a launching user does not have an
* account binding yet. This page prompts the user to select between:
* a) An auto provisioned account.
* An account with auth type 'lti' is created for the user. This account is bound to the launch credentials.
* Or
* b) Use an existing account
* The standard Moodle auth flow is leveraged to get an existing user account. This account is then bound to the launch
* credentials.
*
* @package auth_lti
* @copyright 2021 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\event\user_login_failed;
use core\output\notification;
require_once(__DIR__ . '/../../config.php');
global $OUTPUT, $PAGE, $SESSION;
// Form fields dealing with the user's choice about account types (new, existing).
$newaccount = optional_param('new_account', false, PARAM_BOOL);
$existingaccount = optional_param('existing_account', false, PARAM_BOOL);
if (empty($SESSION->auth_lti) || empty($SESSION->auth_lti->launchdata)) {
throw new coding_exception('Missing LTI launch credentials.');
}
if (empty($SESSION->auth_lti->returnurl)) {
throw new coding_exception('Missing return URL.');
}
if ($newaccount) {
require_sesskey();
$launchdata = $SESSION->auth_lti->launchdata;
$returnurl = $SESSION->auth_lti->returnurl;
unset($SESSION->auth_lti);
if (!empty($CFG->authpreventaccountcreation)) {
// If 'authpreventaccountcreation' is enabled, the option to create a new account isn't presented to users in the form.
// This just ensures no action is taken were the 'newaccount' value to be present in the submitted data.
// Trigger login failed event.
$failurereason = AUTH_LOGIN_UNAUTHORISED;
$event = user_login_failed::create(['other' => ['reason' => $failurereason]]);
$event->trigger();
// Site settings prevent creating new accounts.
$errormsg = get_string('cannotcreateaccounts', 'auth_lti');
$SESSION->loginerrormsg = $errormsg;
redirect(new moodle_url('/login/index.php'));
} else {
// Create a new account and link it, logging the user in.
$auth = get_auth_plugin('lti');
$newuser = $auth->find_or_create_user_from_launch($launchdata);
complete_user_login($newuser);
$auth->update_user_account($newuser, $launchdata, $launchdata['iss']);
$PAGE->set_context(context_system::instance());
$PAGE->set_url(new moodle_url('/auth/lti/login.php'));
$PAGE->set_pagelayout('popup');
$renderer = $PAGE->get_renderer('auth_lti');
echo $OUTPUT->header();
echo $renderer->render_account_binding_complete(
new notification(get_string('accountcreatedsuccess', 'auth_lti'), notification::NOTIFY_SUCCESS, false),
$returnurl
);
echo $OUTPUT->footer();
exit();
}
} else if ($existingaccount) {
// Only when authenticated can an account be bound, allowing the user to continue to the original launch action.
require_login(null, false);
require_sesskey();
$launchdata = $SESSION->auth_lti->launchdata;
$returnurl = $SESSION->auth_lti->returnurl;
unset($SESSION->auth_lti);
global $USER;
$auth = get_auth_plugin('lti');
$auth->create_user_binding($launchdata['iss'], $launchdata['sub'], $USER->id);
$PAGE->set_context(context_system::instance());
$PAGE->set_url(new moodle_url('/auth/lti/login.php'));
$PAGE->set_pagelayout('popup');
$renderer = $PAGE->get_renderer('auth_lti');
echo $OUTPUT->header();
echo $renderer->render_account_binding_complete(
new notification(get_string('accountlinkedsuccess', 'auth_lti'), notification::NOTIFY_SUCCESS, false),
$returnurl
);
echo $OUTPUT->footer();
exit();
}
// Render the relevant account provisioning page, based on the provisioningmode set in the calling code.
$PAGE->set_context(context_system::instance());
$PAGE->set_url(new moodle_url('/auth/lti/login.php'));
$PAGE->set_pagelayout('popup');
$renderer = $PAGE->get_renderer('auth_lti');
echo $OUTPUT->header();
require_once($CFG->dirroot . '/auth/lti/auth.php');
echo $renderer->render_account_binding_options_page($SESSION->auth_lti->provisioningmode);
echo $OUTPUT->footer();
@@ -0,0 +1,53 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template auth_lti/local/ltiadvantage/account_binding_complete
Template which displays the confirmation after the user has either signed in and has their account linked, or
has had an account automatically provisioned and linked.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* notification
* returnurl
Example context (json):
{
"notification": {
"message": "Your account was successfully linked!",
"extraclasses": "",
"announce": true,
"closebutton": false,
"issuccess": true,
"isinfo": false,
"iswarning": false,
"iserror": false
},
"returnurl": "https://your.site/enrol/lti/launch_deeplink.php?id=123abc"
}
}}
<div id="lti_adv_account_binding_complete">
{{#notification}}
{{> core/notification}}
{{/notification}}
<a class="btn btn-primary" href="{{returnurl}}">{{#str}}continue, core{{/str}}</a>
</div>
@@ -0,0 +1,112 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template auth_lti/local/ltiadvantage/login
Template which displays a choice screen for instructors on first launch, allowing them to select whether to use an
existing account in the tool, or to auto provision a new one.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* formaction
* sesskey
* info - a notification describing the first launch options
* cancreateaccounts - whether or not the user is allowed to create auth_lti accounts
* accountinfo - information about the user, importantly whether they are logged in or not.
Example context (json):
{
"formaction": "auth/lti/login.php",
"sesskey": "1a2b3c4dfg",
"info": {
"message": "Looks like this is your first time here...",
"extraclasses": "",
"announce": false,
"closebutton": false,
"issuccess": true
},
"cancreateaccounts": true,
"isloggedin": true,
"accountinfo": {
"firstname": "John",
"lastname": "Smith",
"email": "john@example.com",
"picturehtml": "<img src=\"http://site.example.com/pluginfile.php/5/user/icon/boost/f2?rev=99\" class=\"round\" alt=\"\" width=\"35\" height=\"35\">"
}
}
}}
<div id="lti_adv_account_binding_options">
<form action="{{formaction}}" method="POST">
<input type="hidden" name="sesskey" value="{{sesskey}}">
<div class="container-fluid">
<h2>{{#str}} welcome, auth_lti {{/str}}</h2>
{{#info}}
{{> core/notification}}
{{/info}}
<div class="row">
<div class="{{#cancreateaccounts}}col-sm-6{{/cancreateaccounts}}{{^cancreateaccounts}}col-sm-12{{/cancreateaccounts}} d-flex">
<div class="card w-100">
<div class="card-header">
{{#str}} haveexistingaccount, auth_lti {{/str}}
</div>
<div class="card-body text-center d-flex flex-column">
<i class="fa fa-user-circle-o fa-2x link"></i>
<h4 class="card-title">{{#str}} useexistingaccount, auth_lti {{/str}}</h4>
{{#isloggedin}}
{{#accountinfo}}
<p class="card-text mt-2">
<span class="text-muted">
{{#str}} currentlyloggedinas, auth_lti {{/str}}
</span>
<br>
{{{picturehtml}}}
{{firstname}} {{lastname}} ({{email}})
</p>
<input type="submit" class="btn btn-primary mt-auto" name="existing_account" value="{{#str}} linkthisaccount, auth_lti {{/str}}">
{{/accountinfo}}
{{/isloggedin}}
{{^isloggedin}}
<p class="card-text text-muted">{{#str}} mustbeloggedin, auth_lti {{/str}}</p>
<input type="submit" class="btn btn-primary mt-auto" name="existing_account" value="{{#str}} login, moodle {{/str}}">
{{/isloggedin}}
</div>
</div>
</div>
{{#cancreateaccounts}}
<div class="col-sm-6 d-flex">
<div class="card w-100">
<div class="card-header">
{{#str}} createnewaccount, auth_lti {{/str}}
</div>
<div class="card-body text-center d-flex flex-column">
<i class="fa fa-user-plus fa-2x"></i>
<h4 class="card-title">{{#str}} createaccount, auth_lti {{/str}}</h4>
<p class="card-text text-muted">{{#str}} getstartedwithnewaccount, auth_lti {{/str}}</p>
<input type="submit" class="btn btn-secondary mt-auto" name="new_account" value="{{#str}} createaccountforme, auth_lti {{/str}}">
</div>
</div>
</div>
{{/cancreateaccounts}}
</div>
</div>
</form>
</div>
File diff suppressed because it is too large Load Diff
+240
View File
@@ -0,0 +1,240 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_lti\privacy;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
use core_privacy\tests\provider_testcase;
use core_privacy\local\request\approved_userlist;
/**
* Test for the auth_lti privacy provider.
*
* @package auth_lti
* @copyright 2021 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \auth_lti\privacy\provider
*/
class provider_test extends provider_testcase {
/**
* Set up method.
*/
public function setUp(): void {
$this->resetAfterTest();
$this->setAdminUser();
}
/**
* Check that a user context is returned if there is any user data for this user.
*
* @covers ::get_contexts_for_userid
*/
public function test_get_contexts_for_userid(): void {
$user = $this->getDataGenerator()->create_user();
$this->assertEmpty(provider::get_contexts_for_userid($user->id));
$auth = get_auth_plugin('lti');
$auth->create_user_binding('https://lms.example.com', 'abc123', $user->id);
$contextlist = provider::get_contexts_for_userid($user->id);
// Check that we only get back one context.
$this->assertCount(1, $contextlist);
// Check that a context is returned is the expected.
$usercontext = \context_user::instance($user->id);
$this->assertEquals($usercontext->id, $contextlist->get_contextids()[0]);
}
/**
* Test that user data is exported correctly.
*
* @covers ::export_user_data
*/
public function test_export_user_data(): void {
$user = $this->getDataGenerator()->create_user();
$auth = get_auth_plugin('lti');
$auth->create_user_binding('https://lms.example.com', 'abc123', $user->id);
$usercontext = \context_user::instance($user->id);
$writer = writer::with_context($usercontext);
$this->assertFalse($writer->has_any_data());
$approvedlist = new approved_contextlist($user, 'auth_lti', [$usercontext->id]);
provider::export_user_data($approvedlist);
$data = $writer->get_data([get_string('privacy:metadata:auth_lti', 'auth_lti'), 'https://lms.example.com']);
$this->assertEquals('https://lms.example.com', $data->issuer);
$this->assertEquals(hash('sha256', 'https://lms.example.com'), $data->issuer256);
$this->assertEquals('abc123', $data->sub);
$this->assertEquals(hash('sha256', 'abc123'), $data->sub256);
}
/**
* Test deleting all user data for a specific context.
*
* @covers ::delete_data_for_all_users_in_context
*/
public function test_delete_data_for_all_users_in_context(): void {
global $DB;
$auth = get_auth_plugin('lti');
$user1 = $this->getDataGenerator()->create_user();
$auth->create_user_binding('https://lms.example.com', 'abc123', $user1->id);
$user1context = \context_user::instance($user1->id);
$user2 = $this->getDataGenerator()->create_user();
$auth->create_user_binding('https://lms.example.com', 'def456', $user2->id);
// Verify there are two linked logins.
$ltiaccounts = $DB->get_records('auth_lti_linked_login');
$this->assertCount(2, $ltiaccounts);
// Delete everything for the first user context.
provider::delete_data_for_all_users_in_context($user1context);
// Get all LTI linked accounts match with user1.
$ltiaccounts = $DB->get_records('auth_lti_linked_login', ['userid' => $user1->id]);
$this->assertCount(0, $ltiaccounts);
// Verify there is now only one linked login.
$ltiaccounts = $DB->get_records('auth_lti_linked_login');
$this->assertCount(1, $ltiaccounts);
}
/**
* This should work identical to the above test.
*
* @covers ::delete_data_for_user
*/
public function test_delete_data_for_user(): void {
global $DB;
$auth = get_auth_plugin('lti');
$user1 = $this->getDataGenerator()->create_user();
$auth->create_user_binding('https://lms.example.com', 'abc123', $user1->id);
$user1context = \context_user::instance($user1->id);
$user2 = $this->getDataGenerator()->create_user();
$auth->create_user_binding('https://lms.example.com', 'def456', $user2->id);
// Verify there are two linked logins.
$ltiaccounts = $DB->get_records('auth_lti_linked_login');
$this->assertCount(2, $ltiaccounts);
// Delete everything for the first user.
$approvedlist = new approved_contextlist($user1, 'auth_lti', [$user1context->id]);
provider::delete_data_for_user($approvedlist);
// Get all LTI accounts linked with user1.
$ltiaccounts = $DB->get_records('auth_lti_linked_login', ['userid' => $user1->id]);
$this->assertCount(0, $ltiaccounts);
// Verify there is only one linked login now.
$ltiaccounts = $DB->get_records('auth_lti_linked_login', array());
$this->assertCount(1, $ltiaccounts);
}
/**
* Test that only users with a user context are fetched.
*
* @covers ::get_users_in_context
*/
public function test_get_users_in_context(): void {
$auth = get_auth_plugin('lti');
$component = 'auth_lti';
$user = $this->getDataGenerator()->create_user();
$usercontext = \context_user::instance($user->id);
// The list of users should not return anything yet (no linked login yet).
$userlist = new userlist($usercontext, $component);
provider::get_users_in_context($userlist);
$this->assertCount(0, $userlist);
$auth->create_user_binding('https://lms.example.com', 'abc123', $user->id);
// The list of users for user context should return the user.
provider::get_users_in_context($userlist);
$this->assertCount(1, $userlist);
$expected = [$user->id];
$actual = $userlist->get_userids();
$this->assertEquals($expected, $actual);
// The list of users for system context should not return any users.
$systemcontext = \context_system::instance();
$userlist = new userlist($systemcontext, $component);
provider::get_users_in_context($userlist);
$this->assertCount(0, $userlist);
}
/**
* Test that data for users in approved userlist is deleted.
*
* @covers ::delete_data_for_users
*/
public function test_delete_data_for_users(): void {
$auth = get_auth_plugin('lti');
$component = 'auth_lti';
$user1 = $this->getDataGenerator()->create_user();
$usercontext1 = \context_user::instance($user1->id);
$user2 = $this->getDataGenerator()->create_user();
$usercontext2 = \context_user::instance($user2->id);
$auth->create_user_binding('https://lms.example.com', 'abc123', $user1->id);
$auth->create_user_binding('https://lms.example.com', 'def456', $user2->id);
// The list of users for usercontext1 should return user1.
$userlist1 = new userlist($usercontext1, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(1, $userlist1);
$expected = [$user1->id];
$actual = $userlist1->get_userids();
$this->assertEquals($expected, $actual);
// The list of users for usercontext2 should return user2.
$userlist2 = new userlist($usercontext2, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
$expected = [$user2->id];
$actual = $userlist2->get_userids();
$this->assertEquals($expected, $actual);
// Add userlist1 to the approved user list.
$approvedlist = new approved_userlist($usercontext1, $component, $userlist1->get_userids());
// Delete user data using delete_data_for_user for usercontext1.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in usercontext1 - The user list should now be empty.
$userlist1 = new userlist($usercontext1, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(0, $userlist1);
// Re-fetch users in usercontext2 - The user list should not be empty (user2).
$userlist2 = new userlist($usercontext2, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
// User data should be only removed in the user context.
$systemcontext = \context_system::instance();
// Add userlist2 to the approved user list in the system context.
$approvedlist = new approved_userlist($systemcontext, $component, $userlist2->get_userids());
// Delete user1 data using delete_data_for_user.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in usercontext2 - The user list should not be empty (user2).
$userlist2 = new userlist($usercontext2, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* LTI authentication plugin version information
*
* @package auth_lti
* @copyright 2016 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'auth_lti'; // Full name of the plugin (used for diagnostics).
+215
View File
@@ -0,0 +1,215 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Authentication Plugin: Manual Authentication
* Just does a simple check against the moodle database.
*
* @package auth_manual
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/authlib.php');
/**
* Manual authentication plugin.
*
* @package auth
* @subpackage manual
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class auth_plugin_manual extends auth_plugin_base {
/**
* The name of the component. Used by the configuration.
*/
const COMPONENT_NAME = 'auth_manual';
const LEGACY_COMPONENT_NAME = 'auth/manual';
/**
* Constructor.
*/
public function __construct() {
$this->authtype = 'manual';
$config = get_config(self::COMPONENT_NAME);
$legacyconfig = get_config(self::LEGACY_COMPONENT_NAME);
$this->config = (object)array_merge((array)$legacyconfig, (array)$config);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function auth_plugin_manual() {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct();
}
/**
* Returns true if the username and password work and false if they are
* wrong or don't exist. (Non-mnet accounts only!)
*
* @param string $username The username
* @param string $password The password
* @return bool Authentication success or failure.
*/
function user_login($username, $password) {
global $CFG, $DB, $USER;
if (!$user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id))) {
return false;
}
if (!validate_internal_user_password($user, $password)) {
return false;
}
if ($password === 'changeme') {
// force the change - this is deprecated and it makes sense only for manual auth,
// because most other plugins can not change password easily or
// passwords are always specified by users
set_user_preference('auth_forcepasswordchange', true, $user->id);
}
return true;
}
/**
* Updates the user's password.
*
* Called when the user password is updated.
*
* @param object $user User table object
* @param string $newpassword Plaintext password
* @return boolean result
*/
function user_update_password($user, $newpassword) {
$user = get_complete_user_data('id', $user->id);
set_user_preference('auth_manual_passwordupdatetime', time(), $user->id);
// This will also update the stored hash to the latest algorithm
// if the existing hash is using an out-of-date algorithm (or the
// legacy md5 algorithm).
return update_internal_user_password($user, $newpassword);
}
function prevent_local_passwords() {
return false;
}
/**
* Returns true if this authentication plugin is 'internal'.
*
* @return bool
*/
function is_internal() {
return true;
}
/**
* Returns true if this authentication plugin can change the user's
* password.
*
* @return bool
*/
function can_change_password() {
return true;
}
/**
* Returns the URL for changing the user's pw, or empty if the default can
* be used.
*
* @return moodle_url
*/
function change_password_url() {
return null;
}
/**
* Returns true if plugin allows resetting of internal password.
*
* @return bool
*/
function can_reset_password() {
return true;
}
/**
* Returns true if plugin can be manually set.
*
* @return bool
*/
function can_be_manually_set() {
return true;
}
/**
* Return number of days to user password expires.
*
* If user password does not expire, it should return 0 or a positive value.
* If user password is already expired, it should return negative value.
*
* @param mixed $username username (with system magic quotes)
* @return integer
*/
public function password_expire($username) {
$result = 0;
if (!empty($this->config->expirationtime)) {
$user = core_user::get_user_by_username($username, 'id,timecreated');
$lastpasswordupdatetime = get_user_preferences('auth_manual_passwordupdatetime', $user->timecreated, $user->id);
$expiretime = $lastpasswordupdatetime + $this->config->expirationtime * DAYSECS;
$now = time();
$result = ($expiretime - $now) / DAYSECS;
if ($expiretime > $now) {
$result = ceil($result);
} else {
$result = floor($result);
}
}
return $result;
}
/**
* Confirm the new user as registered. This should normally not be used,
* but it may be necessary if the user auth_method is changed to manual
* before the user is confirmed.
*
* @param string $username
* @param string $confirmsecret
*/
function user_confirm($username, $confirmsecret = null) {
global $DB;
$user = get_complete_user_data('username', $username);
if (!empty($user)) {
if ($user->confirmed) {
return AUTH_CONFIRM_ALREADY;
} else {
$DB->set_field("user", "confirmed", 1, array("id"=>$user->id));
return AUTH_CONFIRM_OK;
}
} else {
return AUTH_CONFIRM_ERROR;
}
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy Subsystem implementation for auth_manual.
*
* @package auth_manual
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_manual\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\request\writer;
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\transform;
/**
* Privacy provider for the authentication manual.
*
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\user_preference_provider {
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised item collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
// There is a one user preference.
$collection->add_user_preference('auth_manual_passwordupdatetime',
'privacy:metadata:preference:passwordupdatetime');
return $collection;
}
/**
* Export all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$lastpasswordupdatetime = get_user_preferences('auth_manual_passwordupdatetime', null, $userid);
if ($lastpasswordupdatetime !== null) {
$time = transform::datetime($lastpasswordupdatetime);
writer::export_user_preference('auth_manual',
'auth_manual_passwordupdatetime',
$time,
get_string('privacy:metadata:preference:passwordupdatetime', 'auth_manual')
);
}
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Manual authentication plugin upgrade code
*
* @package auth_manual
* @copyright 2011 Petr Skoda (http://skodak.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Function to upgrade auth_manual.
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_auth_manual_upgrade($oldversion) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+34
View File
@@ -0,0 +1,34 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'auth_manual', language 'en'.
*
* @package auth_manual
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['auth_manualdescription'] = 'This method removes any way for users to create their own accounts. All accounts must be manually created by the admin user.';
$string['expiration'] = 'Enable password expiry';
$string['expiration_desc'] = 'Allow passwords to expire after a specified time.';
$string['expiration_warning'] = 'Notification threshold';
$string['expiration_warning_desc'] = 'Number of days before password expiry that a notification is issued.';
$string['passwdexpiretime'] = 'Password duration';
$string['passwdexpiretime_desc'] = 'Length of time for which a password is valid.';
$string['pluginname'] = 'Manual accounts';
$string['passwdexpire_settings'] = 'Password expiry settings';
$string['privacy:metadata:preference:passwordupdatetime'] = 'The date of the last password change.';
+78
View File
@@ -0,0 +1,78 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Admin settings and defaults
*
* @package auth_manual
* @copyright 2017 Stephen Bourget
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
// Introductory explanation.
$settings->add(new admin_setting_heading('auth_manual/pluginname',
new lang_string('passwdexpire_settings', 'auth_manual'),
new lang_string('auth_manualdescription', 'auth_manual')));
$expirationoptions = array(
new lang_string('no'),
new lang_string('yes'),
);
$settings->add(new admin_setting_configselect('auth_manual/expiration',
new lang_string('expiration', 'auth_manual'),
new lang_string('expiration_desc', 'auth_manual'), 0, $expirationoptions));
$expirationtimeoptions = array(
'30' => new lang_string('numdays', '', 30),
'60' => new lang_string('numdays', '', 60),
'90' => new lang_string('numdays', '', 90),
'120' => new lang_string('numdays', '', 120),
'150' => new lang_string('numdays', '', 150),
'180' => new lang_string('numdays', '', 180),
'365' => new lang_string('numdays', '', 365),
);
$settings->add(new admin_setting_configselect('auth_manual/expirationtime',
new lang_string('passwdexpiretime', 'auth_manual'),
new lang_string('passwdexpiretime_desc', 'auth_manual'), 30, $expirationtimeoptions));
$expirationwarningoptions = array(
'0' => new lang_string('never'),
'1' => new lang_string('numdays', '', 1),
'2' => new lang_string('numdays', '', 2),
'3' => new lang_string('numdays', '', 3),
'4' => new lang_string('numdays', '', 4),
'5' => new lang_string('numdays', '', 5),
'6' => new lang_string('numdays', '', 6),
'7' => new lang_string('numdays', '', 7),
'10' => new lang_string('numdays', '', 10),
'14' => new lang_string('numdays', '', 14),
);
$settings->add(new admin_setting_configselect('auth_manual/expiration_warning',
new lang_string('expiration_warning', 'auth_manual'),
new lang_string('expiration_warning_desc', 'auth_manual'), 0, $expirationwarningoptions));
// Display locking / mapping of profile fields.
$authplugin = get_auth_plugin('manual');
display_auth_lock_options($settings, $authplugin->authtype,
$authplugin->userfields, get_string('auth_fieldlocks_help', 'auth'), false, false);
}
@@ -0,0 +1,28 @@
@auth @auth_manual
Feature: Test manual authentication works.
In order to check manual authentication
As a teacher
I need to go on login page and enter username and password.
Background:
Given the following "users" exist:
| username |
| teacher1 |
@javascript
Scenario: Check login works with javascript.
Given I am on homepage
And I expand navigation bar
And I click on "Log in" "link" in the ".logininfo" "css_element"
When I set the field "Username" to "teacher1"
And I set the field "Password" to "teacher1"
When I press "Log in"
Then I should see "You are logged in as"
Scenario: Check login works without javascript.
Given I am on homepage
And I click on "Log in" "link" in the ".logininfo" "css_element"
When I set the field "Username" to "teacher1"
And I set the field "Password" to "teacher1"
When I press "Log in"
Then I should see "You are logged in as"
+88
View File
@@ -0,0 +1,88 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_manual;
use auth_plugin_manual;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/auth/manual/auth.php');
/**
* Manual authentication tests class.
*
* @package auth_manual
* @category test
* @copyright 2014 Gilles-Philippe Leblanc <gilles-philippe.leblanc@umontreal.ca>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manual_test extends \advanced_testcase {
/** @var auth_plugin_manual Keeps the authentication plugin. */
protected $authplugin;
/**
* Setup test data.
*/
protected function setUp(): void {
$this->resetAfterTest(true);
$this->authplugin = new auth_plugin_manual();
set_config('expiration', '1', 'auth_manual');
set_config('expiration_warning', '2', 'auth_manual');
set_config('expirationtime', '30', 'auth_manual');
$this->authplugin->config = get_config(auth_plugin_manual::COMPONENT_NAME);
}
/**
* Test user_update_password method.
*/
public function test_user_update_password(): void {
$user = $this->getDataGenerator()->create_user();
$expectedtime = time();
$passwordisupdated = $this->authplugin->user_update_password($user, 'MyNewPassword*');
// Assert that the actual time should be equal or a little greater than the expected time.
$this->assertGreaterThanOrEqual($expectedtime, get_user_preferences('auth_manual_passwordupdatetime', 0, $user->id));
// Assert that the password was successfully updated.
$this->assertTrue($passwordisupdated);
}
/**
* Test test_password_expire method.
*/
public function test_password_expire(): void {
$userrecord = array();
$expirationtime = 31 * DAYSECS;
$userrecord['timecreated'] = time() - $expirationtime;
$user1 = $this->getDataGenerator()->create_user($userrecord);
$user2 = $this->getDataGenerator()->create_user();
// The user 1 was created 31 days ago and has not changed his password yet, so the password has expirated.
$this->assertLessThanOrEqual(-1, $this->authplugin->password_expire($user1->username));
// The user 2 just came to be created and has not changed his password yet, so the password has not expirated.
$this->assertEquals(30, $this->authplugin->password_expire($user2->username));
$this->authplugin->user_update_password($user1, 'MyNewPassword*');
// The user 1 just updated his password so the password has not expirated.
$this->assertEquals(30, $this->authplugin->password_expire($user1->username));
}
}
@@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Base class for unit tests for auth_manual.
*
* @package auth_manual
* @category test
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_manual\privacy;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/auth/manual/auth.php');
use core_privacy\local\request\writer;
use core_privacy\local\request\transform;
use auth_manual\privacy\provider;
/**
* Unit tests for the auth_manual implementation of the privacy API.
*
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends \core_privacy\tests\provider_testcase {
/** @var \auth_plugin_manual Keeps the authentication plugin. */
protected $authplugin;
/**
* Basic setup for these tests.
*/
public function setUp(): void {
$this->resetAfterTest(true);
$this->authplugin = new \auth_plugin_manual();
}
/**
* Test to check export_user_preferences.
* returns user preferences data.
*/
public function test_export_user_preferences(): void {
$user = $this->getDataGenerator()->create_user();
$this->authplugin->user_update_password($user, 'MyPrivacytestPassword*');
provider::export_user_preferences($user->id);
$writer = writer::with_context(\context_system::instance());
$prefs = $writer->get_user_preferences('auth_manual');
$time = transform::datetime(get_user_preferences('auth_manual_passwordupdatetime', 0, $user->id));
$this->assertEquals($time, $prefs->auth_manual_passwordupdatetime->value);
$this->assertEquals(get_string('privacy:metadata:preference:passwordupdatetime', 'auth_manual'),
$prefs->auth_manual_passwordupdatetime->description);
}
}
+7
View File
@@ -0,0 +1,7 @@
This files describes API changes in /auth/manual/*,
information provided here is intended especially for developers.
=== 3.3 ===
* The config.html file was migrated to use the admin settings API.
The identifier for configuration data stored in config_plugins table was converted from 'auth/manual' to 'auth_manual'.
+29
View File
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Manual authentication plugin version information
*
* @package auth_manual
* @copyright 2011 Petr Skoda (http://skodak.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'auth_manual'; // Full name of the plugin (used for diagnostics)

Some files were not shown because too many files have changed in this diff Show More