aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Mudrák <david@moodle.com>2017-09-28 14:04:22 +0200
committerDavid Mudrák <david@moodle.com>2017-09-28 14:04:22 +0200
commita94afb1febd3f982a5e1ec0bedb1cb4985023496 (patch)
tree07a292226c83a7ef57f7cc6b83bec67523c69b45
parentc597efe65011d3a89bd2751882e9de691846734e (diff)
downloadAuthMinetest-a94afb1febd3f982a5e1ec0bedb1cb4985023496.tar.gz
AuthMinetest-a94afb1febd3f982a5e1ec0bedb1cb4985023496.tar.bz2
AuthMinetest-a94afb1febd3f982a5e1ec0bedb1cb4985023496.zip
Initial implementation
-rw-r--r--MoodlePasswordPrimaryAuthenticationProvider.php302
-rw-r--r--extension.json15
2 files changed, 317 insertions, 0 deletions
diff --git a/MoodlePasswordPrimaryAuthenticationProvider.php b/MoodlePasswordPrimaryAuthenticationProvider.php
new file mode 100644
index 0000000..212c6bf
--- /dev/null
+++ b/MoodlePasswordPrimaryAuthenticationProvider.php
@@ -0,0 +1,302 @@
+<?php
+/**
+ * This program is a 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.
+ *
+ * This program 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 this program. If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Auth
+ */
+
+namespace MediaWiki\Auth;
+
+use User;
+
+/**
+ * A primary authentication provider that authenticates the user against a remote Moodle site.
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+class MoodlePasswordPrimaryAuthenticationProvider extends AbstractPrimaryAuthenticationProvider {
+
+ /** @var string The URL of the Moodle site we authenticate against. */
+ protected $moodleUrl;
+
+ /** @var array */
+ protected $tokens = [];
+
+ /**
+ * @param array $params Settings
+ * - moodleUrl: The URL of the Moodle site we authenticate against.
+ */
+ public function __construct( $params = [] ) {
+
+ if ( empty( $params['moodleUrl'] ) ) {
+ throw new \InvalidArgumentException( 'The moodleUrl parameter missing in the auth configuration' );
+ }
+
+ $this->moodleUrl = $params['moodleUrl'];
+ }
+
+ public function beginPrimaryAuthentication( array $reqs ) {
+ $req = AuthenticationRequest::getRequestByClass( $reqs, PasswordAuthenticationRequest::class );
+ if ( !$req ) {
+ return AuthenticationResponse::newAbstain();
+ }
+
+ if ( $req->username === null || $req->password === null ) {
+ return AuthenticationResponse::newAbstain();
+ }
+
+ $username = User::getCanonicalName( $req->username, 'usable' );
+ if ( $username === false ) {
+ return AuthenticationResponse::newAbstain();
+ }
+
+ $token = $this->getMoodleUserToken( $req->username, $req->password );
+
+ if ( $token === false ) {
+ return AuthenticationResponse::newAbstain();
+
+ } else {
+ $this->tokens[$username] = $token;
+ return AuthenticationResponse::newPass( $username );
+ }
+ }
+
+ /**
+ * Prepares a curl handler to use for querying the Moodle web services.
+ *
+ * @param string $url
+ * @return resource
+ */
+ protected function getMoodleCurlClient( $url ) {
+
+ $curl = curl_init( $url );
+
+ curl_setopt_array( $curl, [
+ CURLOPT_USERAGENT => 'MWAuthMoodleBot/1.0',
+ CURLOPT_NOBODY => false,
+ CURLOPT_HEADER => false,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_MAXREDIRS => 10,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_SSL_VERIFYPEER => 1,
+ CURLOPT_SSL_VERIFYHOST => 2,
+ ]);
+
+ return $curl;
+ }
+
+ /**
+ * Attempts to authenticate the user against Moodle and returns the auth token.
+ *
+ * @param string $username
+ * @param string $password
+ * @return string|bool False on error, token otherwise.
+ */
+ protected function getMoodleUserToken( $username, $password ) {
+
+ $curl = $this->getMoodleCurlClient( $this->moodleUrl.'/login/token.php' );
+
+ $params = http_build_query( [
+ 'username' => $username,
+ 'password' => $password,
+ 'service' => 'moodle_mobile_app',
+ ] );
+
+ curl_setopt_array( $curl, [
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => $params,
+ ]);
+
+ $ret = curl_exec( $curl );
+ $info = curl_getinfo( $curl );
+ $error = curl_error( $curl );
+ curl_close( $curl );
+
+ if ( !empty( $error ) ) {
+ $this->logger->error( 'AuthMoodle: cURL error: '.$error );
+ return false;
+
+ } else if ( $info['http_code'] != 200 ) {
+ $this->logger->error( 'AuthMoodle: cURL error: unexpected HTTP response code '.$info['http_code'] );
+ return false;
+
+ } else {
+ $decoded = @json_decode( $ret );
+ if ( empty( $decoded ) ) {
+ $this->logger->error( 'AuthMoodle: Unable to decode the JSON response: '.$ret );
+ return false;
+ }
+ }
+
+ if ( !empty( $decoded->token ) ) {
+ return $decoded->token;
+
+ } else if ( isset( $decoded->error ) ) {
+ $this->logger->error( 'AuthMoodle: Remote error: '.$decoded->error );
+ return false;
+
+ } else {
+ $this->logger->error( 'AuthMoodle: Unknown error: '.$ret );
+ return false;
+ }
+ }
+
+ /**
+ * @param null|\User $user
+ * @param AuthenticationResponse $response
+ */
+ public function postAuthentication( $user, AuthenticationResponse $response ) {
+ if ( $response->status !== AuthenticationResponse::PASS ) {
+ return;
+ }
+
+ if ( empty( $this->tokens[$user->getName()] ) ) {
+ $this->logger->error( 'AuthMoodle: Moodle token not found' );
+ return;
+ }
+
+ $userinfo = $this->getMoodleUserInfo( $user->getName(), $this->tokens[$user->getName()] );
+
+ if ( empty( $userinfo ) ) {
+ $this->logger->error( 'AuthMoodle: Empty user info, skipping update ');
+ return;
+ }
+
+ if ( $user->getRealName() === '' ) {
+ // Set the user's real name if they are logging in for the first time. Also note MDLSITE-1293.
+ $this->logger->debug( 'AuthMoodle: Setting the user real name' );
+ $mwdbr = wfGetDB( DB_SLAVE );
+ $realname = $userinfo->fullname;
+ $counter = 1;
+ while ( $mwdbr->selectField( 'user', 'user_name', ['user_real_name' => $realname] ) && $counter < 100 ) {
+ $counter++;
+ $realname = $userinfo->fullname.' '.$counter;
+ }
+ $user->setRealName( $realname );
+ }
+
+ $user->setEmail( $userinfo->email );
+
+ // TODO This should not be needed once https://bugzilla.wikimedia.org/show_bug.cgi?id=13963 is fixed.
+ $user->saveSettings();
+ }
+
+ /**
+ * Loads the Moodle user's real name and email.
+ *
+ * @param string $username
+ * @param string $token
+ * @return object|bool
+ */
+ protected function getMoodleUserInfo( $username, $token ) {
+
+ $this->logger->debug( 'AuthMoodle: Attempting to get info about the user: '.$username.' using the token: '.$token );
+
+ // Get the Moodle user id first.
+
+ $params = http_build_query( [
+ 'wstoken' => $token,
+ 'wsfunction' => 'core_webservice_get_site_info',
+ 'moodlewsrestformat' => 'json',
+ ] );
+
+ $curl = $this->getMoodleCurlClient( $this->moodleUrl.'/webservice/rest/server.php?'.$params );
+
+ $ret = curl_exec( $curl );
+ curl_close( $curl );
+
+ $decoded = @json_decode( $ret );
+
+ if ( empty( $decoded->userid ) ) {
+ $this->logger->error( 'AuthMoodle: Unable to get Moodle user id' );
+ return false;
+ }
+
+ if ( strtolower( $decoded->username ) !== strtolower( $username ) ) {
+ $this->logger->error( 'AuthMoodle: User name mismatch' );
+ return false;
+ }
+
+ $moodleuserid = $decoded->userid;
+
+ // Get the user profile.
+
+ $params = http_build_query( [
+ 'wstoken' => $token,
+ 'wsfunction' => 'core_user_get_users_by_field',
+ 'moodlewsrestformat' => 'json',
+ 'field' => 'id',
+ 'values' => [$moodleuserid],
+ ] );
+
+ $curl = $this->getMoodleCurlClient( $this->moodleUrl.'/webservice/rest/server.php?'.$params );
+
+ $ret = curl_exec( $curl );
+ curl_close( $curl );
+
+ $decoded = @json_decode( $ret );
+
+ if ( empty( $decoded ) ) {
+ $this->logger->error( 'AuthMoodle: Unable to get Moodle user profile' );
+ return false;
+ }
+
+ return (object) [
+ 'fullname' => $decoded[0]->fullname,
+ 'email' => $decoded[0]->email,
+ ];
+ }
+
+ public function testUserCanAuthenticate( $username ) {
+ return $this->testUserExists( $username );
+ }
+
+ public function testUserExists( $username, $flags = User::READ_NORMAL ) {
+ // TODO - there is no easy way to do this without additional web services on the Moodle side.
+ return false;
+ }
+
+ public function providerAllowsPropertyChange( $property ) {
+ return false;
+ }
+
+ public function providerAllowsAuthenticationDataChange( AuthenticationRequest $req, $checkData = true) {
+ return \StatusValue::newGood( 'ignored' );
+ }
+
+ public function providerChangeAuthenticationData( AuthenticationRequest $req ) {
+ return;
+ }
+
+ public function accountCreationType() {
+ return self::TYPE_CREATE;
+ }
+
+ public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) {
+ throw new \BadMethodCallException( 'This should not get called' );
+ }
+
+ public function getAuthenticationRequests( $action, array $options ) {
+ switch ( $action ) {
+ case AuthManager::ACTION_LOGIN:
+ return [ new PasswordAuthenticationRequest() ];
+ default:
+ return [];
+ }
+ }
+}
diff --git a/extension.json b/extension.json
new file mode 100644
index 0000000..7fba642
--- /dev/null
+++ b/extension.json
@@ -0,0 +1,15 @@
+{
+ "name": "AuthMoodle",
+ "version": "1.0.0-beta",
+ "author": [
+ "David Mudrák"
+ ],
+ "url": "https://github.com/moodlehq/mediawiki-authmoodle",
+ "description": "Extension for MediaWiki allowing to authenticate users against Moodle database via mobile app services"
+ "license-name": "GPL-3.0+",
+ "type": "auth",
+ "AutoloadClasses": {
+ "MediaWiki\\Auth\\MoodlePasswordPrimaryAuthenticationProvider": "MoodlePasswordPrimaryAuthenticationProvider.php"
+ },
+ "manifest_version": 1
+}