aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Pérez-Cerezo <gabriel@gpcf.eu>2019-04-17 09:00:02 +0200
committerGabriel Pérez-Cerezo <gabriel@gpcf.eu>2019-04-17 09:00:02 +0200
commit015333afe98d72a42b72b2f4879fa7a1b9aa0460 (patch)
treefebf7dae3f83394547ad3c7e42520f59e6b3dc07
parent03667616c1c6ca3613153179dd994e73d72137f6 (diff)
downloadAuthMinetest-015333afe98d72a42b72b2f4879fa7a1b9aa0460.tar.gz
AuthMinetest-015333afe98d72a42b72b2f4879fa7a1b9aa0460.tar.bz2
AuthMinetest-015333afe98d72a42b72b2f4879fa7a1b9aa0460.zip
Repurpose plugin as minetest authentication.
-rw-r--r--MinetestPasswordPrimaryAuthenticationProvider.php201
-rw-r--r--MoodlePasswordPrimaryAuthenticationProvider.php310
-rw-r--r--README.md34
-rw-r--r--extension.json11
-rw-r--r--minetestauth.py77
5 files changed, 309 insertions, 324 deletions
diff --git a/MinetestPasswordPrimaryAuthenticationProvider.php b/MinetestPasswordPrimaryAuthenticationProvider.php
new file mode 100644
index 0000000..7d2fb19
--- /dev/null
+++ b/MinetestPasswordPrimaryAuthenticationProvider.php
@@ -0,0 +1,201 @@
+<?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 Minetest site.
+ *
+ * @ingroup Auth
+ * @since 1.27
+ */
+class MinetestPasswordPrimaryAuthenticationProvider extends AbstractPrimaryAuthenticationProvider {
+
+ /** @var string The URL of the Minetest site we authenticate against. */
+ protected $minetestUrl;
+
+ /** @var array */
+ /* protected $tokens = [];*/
+
+ /**
+ * @param array $params Settings
+ * - minetestUrl: The URL of the Minetest site we authenticate against.
+ */
+ public function __construct( $params = [] ) {
+
+ if ( empty( $params['minetestUrl'] ) ) {
+ throw new \InvalidArgumentException( 'The minetestUrl parameter missing in the auth configuration' );
+ }
+
+ $this->minetestUrl = $params['minetestUrl'];
+ }
+
+ 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->getMinetestUserToken( $req->username, $req->password );
+
+ if ( $token === false ) {
+ return AuthenticationResponse::newAbstain();
+
+ } else {
+ return AuthenticationResponse::newPass( $username );
+ }
+ }
+
+ /**
+ * Prepares a curl handler to use for querying the Minetest web services.
+ *
+ * @param string $url
+ * @return resource
+ */
+ protected function getMinetestCurlClient( $url ) {
+
+ $curl = curl_init( $url );
+
+ curl_setopt_array( $curl, [
+ CURLOPT_USERAGENT => 'MWAuthMinetestBot/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 Minetest. Checks if user is authenticated.
+ *
+ * @param string $username
+ * @param string $password
+ * @return bool False on error, true otherwise
+ */
+ protected function getMinetestUserToken( $username, $password ) {
+
+ $curl = $this->getMinetestCurlClient( $this->minetestUrl.'/query' );
+
+ $params = http_build_query( [
+ 'name' => $username,
+ 'password' => $password,
+ ] );
+
+ 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 );
+
+ sleep(2);
+
+ $query2 = $this->getMinetestCurlClient( $this->minetestUrl.'/status/'.$username );
+ $ret = curl_exec ( $query2 );
+
+
+ if ( !empty( $error ) ) {
+ $this->logger->error( 'AuthMinetest: cURL error: '.$error );
+ return false;
+
+ } else if ( $info['http_code'] != 200 ) {
+ $this->logger->error( 'AuthMinetest: cURL error: unexpected HTTP response code '.$info['http_code'] );
+ return false;
+
+ }
+ if ( $ret == "True" ) {
+ return true;
+
+ } else {
+ return false;
+ }
+
+ }
+
+ /**
+ * @param null|\User $user
+ * @param AuthenticationResponse $response
+ */
+ public function postAuthentication( $user, AuthenticationResponse $response ) {
+ if ( $response->status !== AuthenticationResponse::PASS ) {
+ return;
+ }
+ return;
+ }
+
+
+ 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 Minetest 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/MoodlePasswordPrimaryAuthenticationProvider.php b/MoodlePasswordPrimaryAuthenticationProvider.php
deleted file mode 100644
index 2799e9a..0000000
--- a/MoodlePasswordPrimaryAuthenticationProvider.php
+++ /dev/null
@@ -1,310 +0,0 @@
-<?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->exception ) ) {
- $this->logger->error( 'AuthMoodle: Remote exception: '.$decoded->exception );
- return false;
-
- } 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 );
- $user->confirmEmail();
- $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;
- }
-
- if ( isset( $decoded->exception ) ) {
- $this->logger->error( 'AuthMoodle: Remote exception: '.$decoded->exception );
- 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/README.md b/README.md
index fcddb68..34b6bfc 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,52 @@
-# AuthMoodle
+# AuthMinetest
-Extension for MediaWiki allowing to authenticate users against Moodle database via mobile app service.
+Extension for MediaWiki allowing to authenticate users against a Minetest server
## Requirements:
* MediaWiki 1.27+
-* Moodle 3.1+ with mobile app service enabled
+* Minetest with auth_export mod enabled
+* minetestauth.py duct-tape http server
## Installation and setup
-Clone / unzip into your MediaWiki's extension/AuthMoodle/ folder.
+Clone / unzip into your MediaWiki's extension/AuthMinetest/ folder.
Configure your MediaWiki authentication manager to use this extension as the
primary authentication provider:
- wfLoadExtension( 'AuthMoodle' );
+ wfLoadExtension( 'AuthMinetest' );
$wgAuthManagerAutoConfig['primaryauth'] = [
- MediaWiki\Auth\MoodlePasswordPrimaryAuthenticationProvider::class => [
- 'class' => MediaWiki\Auth\MoodlePasswordPrimaryAuthenticationProvider::class,
+ MediaWiki\Auth\MinetestPasswordPrimaryAuthenticationProvider::class => [
+ 'class' => MediaWiki\Auth\MinetestPasswordPrimaryAuthenticationProvider::class,
'args' => [
[
- 'moodleUrl' => 'https://your.moodle.url',
+ 'minetestUrl' => 'http://your.minetest.url',
]
],
'sort' => 0,
],
- ];
+ ];
+
+Enable the auth\_export mod on your minetest server, and install the
+minetestauth.py script. You then need to point both the auth\_export
+mod and this plugin to the minetestauth.py script like this:
+
+ auth_export ←→ minetestauth.py ←→ AuthMinetest
+
+Keep in mind that stuff is sent in plain text, so you should have all
+of these listening on localhost or use encrypted tunnels between the
+hosts, such as ssh or vpn tunnels.
## Copying
+This extension is based on
+[AuthMoodle](https://github.com/moodlehq/mediawiki-authmoodle) by
+David Mudrák.
+
Copyright 2017 David Mudrák <david@moodle.org>
+Copyright 2019 Gabriel Pérez-Cerezo <gabriel@gpcf.eu>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/extension.json b/extension.json
index 024966a..46a0ab1 100644
--- a/extension.json
+++ b/extension.json
@@ -1,15 +1,16 @@
{
- "name": "AuthMoodle",
+ "name": "AuthMinetest",
"version": "1.0.0",
"author": [
- "David Mudrák"
+ "Gabriel Pérez-Cerezo",
+ "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",
+ "url": "https://git.bananach.space/MinetestAuth.git",
+ "description": "Extension for MediaWiki allowing to authenticate users against Minetest servers",
"license-name": "GPL-3.0+",
"type": "auth",
"AutoloadClasses": {
- "MediaWiki\\Auth\\MoodlePasswordPrimaryAuthenticationProvider": "MoodlePasswordPrimaryAuthenticationProvider.php"
+ "MediaWiki\\Auth\\MinetestPasswordPrimaryAuthenticationProvider": "MinetestPasswordPrimaryAuthenticationProvider.php"
},
"manifest_version": 1
}
diff --git a/minetestauth.py b/minetestauth.py
new file mode 100644
index 0000000..eec3ba1
--- /dev/null
+++ b/minetestauth.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+#
+# Primitive server to interface minetest with mediawiki
+# Functionality:
+# - responds to minetest server's requests for authentication
+# - mediawiki posts request to server on the URL /query
+# - mediawiki queries user auth status on the URL /status/$USERNAME
+#
+from http.server import BaseHTTPRequestHandler, HTTPServer
+import socketserver
+import cgi
+import json
+queue = []
+auth_status = {}
+allowed = ["127.0.0.1"]
+class S(BaseHTTPRequestHandler):
+ def check_allowed(self) :
+ if not self.client_address[0] in allowed :
+ self.send_response(403)
+ self.end_headers()
+ return False
+ return True
+ def _set_headers(self):
+
+ self.send_response(200)
+ self.send_header('Content-type', 'text/json')
+ self.send_header('Connection', 'keepalive')
+ self.end_headers()
+ return True
+
+ def do_GET(self):
+ if not self.check_allowed() :
+ return
+ self._set_headers()
+ if self.path == "/api/minetest/channel" :
+ if queue :
+ k = queue.pop()
+ self.wfile.write((' { "data" : { "name": %s, "password" : %s }, "type": "auth"} ' % (json.dumps(k[0]),json.dumps(k[1]))).encode())
+ else :
+ self.wfile.write("{}".encode())
+ elif self.path.startswith("/status/"):
+ name = self.path.split("/")[-1]
+ if not name in auth_status :
+ self.wfile.write("Unknown".encode())
+ else :
+ self.wfile.write(str(auth_status[name]).encode())
+ del auth_status[name]
+ def do_POST(self):
+ if not self.check_allowed() :
+ return
+ self._set_headers()
+ if self.path == "/query" :
+ form = cgi.FieldStorage(
+ fp=self.rfile,
+ headers=self.headers,
+ environ={'REQUEST_METHOD': 'POST'}
+ )
+ user = form.getvalue("name")
+ pwd = form.getvalue("password")
+ queue.append((user,pwd))
+ else: # User has been identified
+ js = self.rfile.read().decode()
+ k = json.loads(js)
+ auth_status[k["data"]["name"]] = k["data"]["success"]
+
+def run(port=8000):
+ server_address = ('127.0.0.1', port)
+ httpd = HTTPServer(server_address, S)
+ httpd.serve_forever()
+
+if __name__ == "__main__":
+ from sys import argv
+
+ if len(argv) == 2:
+ run(port=int(argv[1]))
+ else:
+ run()