began work on PDO auth plugin
This commit is contained in:
parent
ef89d2cdf6
commit
f64dbc9005
12 changed files with 564 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -40,6 +40,7 @@
|
|||
!/lib/plugins/authldap
|
||||
!/lib/plugins/authmysql
|
||||
!/lib/plugins/authpgsql
|
||||
!/lib/plugins/authpdo
|
||||
!/lib/plugins/authplain
|
||||
!/lib/plugins/config
|
||||
!/lib/plugins/extension
|
||||
|
|
13
lib/plugins/authpdo/.travis.yml
Normal file
13
lib/plugins/authpdo/.travis.yml
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Config file for travis-ci.org
|
||||
|
||||
language: php
|
||||
php:
|
||||
- "5.5"
|
||||
- "5.4"
|
||||
- "5.3"
|
||||
env:
|
||||
- DOKUWIKI=master
|
||||
- DOKUWIKI=stable
|
||||
before_install: wget https://raw.github.com/splitbrain/dokuwiki-travis/master/travis.sh
|
||||
install: sh travis.sh
|
||||
script: cd _test && phpunit --stderr --group plugin_authpdo
|
27
lib/plugins/authpdo/README
Normal file
27
lib/plugins/authpdo/README
Normal file
|
@ -0,0 +1,27 @@
|
|||
authpdo Plugin for DokuWiki
|
||||
|
||||
Authenticate against a database via PDO
|
||||
|
||||
All documentation for this plugin can be found at
|
||||
https://www.dokuwiki.org/plugin:authpdo
|
||||
|
||||
If you install this plugin manually, make sure it is installed in
|
||||
lib/plugins/authpdo/ - if the folder is called different it
|
||||
will not work!
|
||||
|
||||
Please refer to http://www.dokuwiki.org/plugins for additional info
|
||||
on how to install plugins in DokuWiki.
|
||||
|
||||
----
|
||||
Copyright (C) Andreas Gohr <andi@splitbrain.org>
|
||||
|
||||
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
|
||||
the Free Software Foundation; version 2 of the License
|
||||
|
||||
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.
|
||||
|
||||
See the COPYING file in your DokuWiki folder for details
|
33
lib/plugins/authpdo/_test/general.test.php
Normal file
33
lib/plugins/authpdo/_test/general.test.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/**
|
||||
* General tests for the authpdo plugin
|
||||
*
|
||||
* @group plugin_authpdo
|
||||
* @group plugins
|
||||
*/
|
||||
class general_plugin_authpdo_test extends DokuWikiTest {
|
||||
|
||||
/**
|
||||
* Simple test to make sure the plugin.info.txt is in correct format
|
||||
*/
|
||||
public function test_plugininfo() {
|
||||
$file = __DIR__.'/../plugin.info.txt';
|
||||
$this->assertFileExists($file);
|
||||
|
||||
$info = confToHash($file);
|
||||
|
||||
$this->assertArrayHasKey('base', $info);
|
||||
$this->assertArrayHasKey('author', $info);
|
||||
$this->assertArrayHasKey('email', $info);
|
||||
$this->assertArrayHasKey('date', $info);
|
||||
$this->assertArrayHasKey('name', $info);
|
||||
$this->assertArrayHasKey('desc', $info);
|
||||
$this->assertArrayHasKey('url', $info);
|
||||
|
||||
$this->assertEquals('authpdo', $info['base']);
|
||||
$this->assertRegExp('/^https?:\/\//', $info['url']);
|
||||
$this->assertTrue(mail_isvalid($info['email']));
|
||||
$this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']);
|
||||
$this->assertTrue(false !== strtotime($info['date']));
|
||||
}
|
||||
}
|
49
lib/plugins/authpdo/_test/sqlite.test.php
Normal file
49
lib/plugins/authpdo/_test/sqlite.test.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* General tests for the authpdo plugin
|
||||
*
|
||||
* @group plugin_authpdo
|
||||
* @group plugins
|
||||
*/
|
||||
class sqlite_plugin_authpdo_test extends DokuWikiTest {
|
||||
|
||||
protected $dbfile;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->dbfile = tempnam('/tmp/', 'pluginpdo_test_');
|
||||
copy(__DIR__ . '/test.sqlite3', $this->dbfile);
|
||||
|
||||
global $conf;
|
||||
|
||||
$conf['plugin']['authpdo']['debug'] = 1;
|
||||
$conf['plugin']['authpdo']['dsn'] = 'sqlite:' . $this->dbfile;
|
||||
$conf['plugin']['authpdo']['user'] = '';
|
||||
$conf['plugin']['authpdo']['pass'] = '';
|
||||
|
||||
|
||||
$conf['plugin']['authpdo']['select-user'] = 'SELECT id as uid, login as user, name, pass as clear, mail FROM user WHERE login = :user';
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
parent::tearDown();
|
||||
unlink($this->dbfile);
|
||||
}
|
||||
|
||||
public function test_userinfo() {
|
||||
global $conf;
|
||||
$auth = new auth_plugin_authpdo();
|
||||
|
||||
// clear text pasword (with default config above
|
||||
$this->assertFalse($auth->checkPass('nobody', 'nope'));
|
||||
$this->assertFalse($auth->checkPass('admin', 'nope'));
|
||||
$this->assertTrue($auth->checkPass('admin', 'password'));
|
||||
|
||||
// now with a hashed password
|
||||
$conf['plugin']['authpdo']['select-user'] = 'SELECT id as uid, login as user, name, pass as hash, mail FROM user WHERE login = :user';
|
||||
$this->assertFalse($auth->checkPass('admin', 'password'));
|
||||
$this->assertFalse($auth->checkPass('user', md5('password')));
|
||||
|
||||
}
|
||||
}
|
BIN
lib/plugins/authpdo/_test/test.sqlite3
Normal file
BIN
lib/plugins/authpdo/_test/test.sqlite3
Normal file
Binary file not shown.
374
lib/plugins/authpdo/auth.php
Normal file
374
lib/plugins/authpdo/auth.php
Normal file
|
@ -0,0 +1,374 @@
|
|||
<?php
|
||||
/**
|
||||
* DokuWiki Plugin authpdo (Auth Component)
|
||||
*
|
||||
* @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
|
||||
// must be run within Dokuwiki
|
||||
if(!defined('DOKU_INC')) die();
|
||||
|
||||
class auth_plugin_authpdo extends DokuWiki_Auth_Plugin {
|
||||
|
||||
/** @var PDO */
|
||||
protected $pdo;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct(); // for compatibility
|
||||
|
||||
if(!class_exists('PDO')) {
|
||||
$this->_debug('PDO extension for PHP not found.', -1, __LINE__);
|
||||
$this->success = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!$this->getConf('dsn')) {
|
||||
$this->_debug('No DSN specified', -1, __LINE__);
|
||||
$this->success = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo = new PDO(
|
||||
$this->getConf('dsn'),
|
||||
$this->getConf('user'),
|
||||
$this->getConf('pass'),
|
||||
array(
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
)
|
||||
);
|
||||
} catch(PDOException $e) {
|
||||
$this->_debug($e);
|
||||
$this->success = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME set capabilities accordingly
|
||||
//$this->cando['addUser'] = false; // can Users be created?
|
||||
//$this->cando['delUser'] = false; // can Users be deleted?
|
||||
//$this->cando['modLogin'] = false; // can login names be changed?
|
||||
//$this->cando['modPass'] = false; // can passwords be changed?
|
||||
//$this->cando['modName'] = false; // can real names be changed?
|
||||
//$this->cando['modMail'] = false; // can emails be changed?
|
||||
//$this->cando['modGroups'] = false; // can groups be changed?
|
||||
//$this->cando['getUsers'] = false; // can a (filtered) list of users be retrieved?
|
||||
//$this->cando['getUserCount']= false; // can the number of users be retrieved?
|
||||
//$this->cando['getGroups'] = false; // can a list of available groups be retrieved?
|
||||
//$this->cando['external'] = false; // does the module do external auth checking?
|
||||
//$this->cando['logout'] = true; // can the user logout again? (eg. not possible with HTTP auth)
|
||||
|
||||
// FIXME intialize your auth system and set success to true, if successful
|
||||
$this->success = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user+password
|
||||
*
|
||||
* May be ommited if trustExternal is used.
|
||||
*
|
||||
* @param string $user the user name
|
||||
* @param string $pass the clear text password
|
||||
* @return bool
|
||||
*/
|
||||
public function checkPass($user, $pass) {
|
||||
|
||||
$data = $this->_selectUser($user);
|
||||
if($data == false) return false;
|
||||
|
||||
if(isset($data['hash'])) {
|
||||
// hashed password
|
||||
$passhash = new PassHash();
|
||||
return $passhash->verify_hash($pass, $data['hash']);
|
||||
} else {
|
||||
// clear text password in the database O_o
|
||||
return ($pass == $data['clear']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return user info
|
||||
*
|
||||
* Returns info about the given user needs to contain
|
||||
* at least these fields:
|
||||
*
|
||||
* name string full name of the user
|
||||
* mail string email addres of the user
|
||||
* grps array list of groups the user is in
|
||||
*
|
||||
* @param string $user the user name
|
||||
* @param bool $requireGroups whether or not the returned data must include groups
|
||||
* @return array containing user data or false
|
||||
*/
|
||||
public function getUserData($user, $requireGroups = true) {
|
||||
$data = $this->_selectUser($user);
|
||||
if($data == false) return false;
|
||||
|
||||
if($requireGroups) {
|
||||
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new User [implement only where required/possible]
|
||||
*
|
||||
* Returns false if the user already exists, null when an error
|
||||
* occurred and true if everything went well.
|
||||
*
|
||||
* The new user HAS TO be added to the default group by this
|
||||
* function!
|
||||
*
|
||||
* Set addUser capability when implemented
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $pass
|
||||
* @param string $name
|
||||
* @param string $mail
|
||||
* @param null|array $grps
|
||||
* @return bool|null
|
||||
*/
|
||||
//public function createUser($user, $pass, $name, $mail, $grps = null) {
|
||||
// FIXME implement
|
||||
// return null;
|
||||
//}
|
||||
|
||||
/**
|
||||
* Modify user data [implement only where required/possible]
|
||||
*
|
||||
* Set the mod* capabilities according to the implemented features
|
||||
*
|
||||
* @param string $user nick of the user to be changed
|
||||
* @param array $changes array of field/value pairs to be changed (password will be clear text)
|
||||
* @return bool
|
||||
*/
|
||||
//public function modifyUser($user, $changes) {
|
||||
// FIXME implement
|
||||
// return false;
|
||||
//}
|
||||
|
||||
/**
|
||||
* Delete one or more users [implement only where required/possible]
|
||||
*
|
||||
* Set delUser capability when implemented
|
||||
*
|
||||
* @param array $users
|
||||
* @return int number of users deleted
|
||||
*/
|
||||
//public function deleteUsers($users) {
|
||||
// FIXME implement
|
||||
// return false;
|
||||
//}
|
||||
|
||||
/**
|
||||
* Bulk retrieval of user data [implement only where required/possible]
|
||||
*
|
||||
* Set getUsers capability when implemented
|
||||
*
|
||||
* @param int $start index of first user to be returned
|
||||
* @param int $limit max number of users to be returned
|
||||
* @param array $filter array of field/pattern pairs, null for no filter
|
||||
* @return array list of userinfo (refer getUserData for internal userinfo details)
|
||||
*/
|
||||
//public function retrieveUsers($start = 0, $limit = -1, $filter = null) {
|
||||
// FIXME implement
|
||||
// return array();
|
||||
//}
|
||||
|
||||
/**
|
||||
* Return a count of the number of user which meet $filter criteria
|
||||
* [should be implemented whenever retrieveUsers is implemented]
|
||||
*
|
||||
* Set getUserCount capability when implemented
|
||||
*
|
||||
* @param array $filter array of field/pattern pairs, empty array for no filter
|
||||
* @return int
|
||||
*/
|
||||
//public function getUserCount($filter = array()) {
|
||||
// FIXME implement
|
||||
// return 0;
|
||||
//}
|
||||
|
||||
/**
|
||||
* Define a group [implement only where required/possible]
|
||||
*
|
||||
* Set addGroup capability when implemented
|
||||
*
|
||||
* @param string $group
|
||||
* @return bool
|
||||
*/
|
||||
//public function addGroup($group) {
|
||||
// FIXME implement
|
||||
// return false;
|
||||
//}
|
||||
|
||||
/**
|
||||
* Retrieve groups [implement only where required/possible]
|
||||
*
|
||||
* Set getGroups capability when implemented
|
||||
*
|
||||
* @param int $start
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
//public function retrieveGroups($start = 0, $limit = 0) {
|
||||
// FIXME implement
|
||||
// return array();
|
||||
//}
|
||||
|
||||
/**
|
||||
* Return case sensitivity of the backend
|
||||
*
|
||||
* When your backend is caseinsensitive (eg. you can login with USER and
|
||||
* user) then you need to overwrite this method and return false
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCaseSensitive() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a given username
|
||||
*
|
||||
* This function is applied to any user name that is given to
|
||||
* the backend and should also be applied to any user name within
|
||||
* the backend before returning it somewhere.
|
||||
*
|
||||
* This should be used to enforce username restrictions.
|
||||
*
|
||||
* @param string $user username
|
||||
* @return string the cleaned username
|
||||
*/
|
||||
public function cleanUser($user) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a given groupname
|
||||
*
|
||||
* This function is applied to any groupname that is given to
|
||||
* the backend and should also be applied to any groupname within
|
||||
* the backend before returning it somewhere.
|
||||
*
|
||||
* This should be used to enforce groupname restrictions.
|
||||
*
|
||||
* Groupnames are to be passed without a leading '@' here.
|
||||
*
|
||||
* @param string $group groupname
|
||||
* @return string the cleaned groupname
|
||||
*/
|
||||
public function cleanGroup($group) {
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Session Cache validity [implement only where required/possible]
|
||||
*
|
||||
* DokuWiki caches user info in the user's session for the timespan defined
|
||||
* in $conf['auth_security_timeout'].
|
||||
*
|
||||
* This makes sure slow authentication backends do not slow down DokuWiki.
|
||||
* This also means that changes to the user database will not be reflected
|
||||
* on currently logged in users.
|
||||
*
|
||||
* To accommodate for this, the user manager plugin will touch a reference
|
||||
* file whenever a change is submitted. This function compares the filetime
|
||||
* of this reference file with the time stored in the session.
|
||||
*
|
||||
* This reference file mechanism does not reflect changes done directly in
|
||||
* the backend's database through other means than the user manager plugin.
|
||||
*
|
||||
* Fast backends might want to return always false, to force rechecks on
|
||||
* each page load. Others might want to use their own checking here. If
|
||||
* unsure, do not override.
|
||||
*
|
||||
* @param string $user - The username
|
||||
* @return bool
|
||||
*/
|
||||
//public function useSessionCache($user) {
|
||||
// FIXME implement
|
||||
//}
|
||||
|
||||
/**
|
||||
* Select data of a specified user
|
||||
*
|
||||
* @param $user
|
||||
* @return bool|array
|
||||
*/
|
||||
protected function _selectUser($user) {
|
||||
$sql = $this->getConf('select-user');
|
||||
|
||||
try {
|
||||
$sth = $this->pdo->prepare($sql);
|
||||
$sth->execute(array(':user' => $user));
|
||||
$result = $sth->fetchAll();
|
||||
$sth->closeCursor();
|
||||
$sth = null;
|
||||
} catch(PDOException $e) {
|
||||
$this->_debug($e);
|
||||
$result = array();
|
||||
}
|
||||
$found = count($result);
|
||||
if($found == 0) return false;
|
||||
|
||||
if($found > 1) {
|
||||
$this->_debug('Found more than one matching user', -1, __LINE__);
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = array_shift($result);
|
||||
$dataok = true;
|
||||
|
||||
if(!isset($data['user'])) {
|
||||
$this->_debug("Statement did not return 'user' attribute", -1, __LINE__);
|
||||
$dataok = false;
|
||||
}
|
||||
if(!isset($data['hash']) && !isset($data['clear'])) {
|
||||
$this->_debug("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__);
|
||||
$dataok = false;
|
||||
}
|
||||
if(!isset($data['name'])) {
|
||||
$this->_debug("Statement did not return 'name' attribute", -1, __LINE__);
|
||||
$dataok = false;
|
||||
}
|
||||
if(!isset($data['mail'])) {
|
||||
$this->_debug("Statement did not return 'mail' attribute", -1, __LINE__);
|
||||
$dataok = false;
|
||||
}
|
||||
|
||||
if(!$dataok) return false;
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around msg() but outputs only when debug is enabled
|
||||
*
|
||||
* @param string|Exception $message
|
||||
* @param int $err
|
||||
* @param int $line
|
||||
*/
|
||||
protected function _debug($message, $err = 0, $line = 0) {
|
||||
if(!$this->getConf('debug')) return;
|
||||
if(is_a($message, 'Exception')) {
|
||||
$err = -1;
|
||||
$line = $message->getLine();
|
||||
$msg = $message->getMessage();
|
||||
} else {
|
||||
$msg = $message;
|
||||
}
|
||||
|
||||
if(defined('DOKU_UNITTEST')) {
|
||||
printf("\n%s, %s:%d\n", $msg, __FILE__, $line);
|
||||
} else {
|
||||
msg('authpdo: ' . $msg, $err, $line, __FILE__);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vim:ts=4:sw=4:et:
|
21
lib/plugins/authpdo/conf/default.php
Normal file
21
lib/plugins/authpdo/conf/default.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
/**
|
||||
* Default settings for the authpdo plugin
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
|
||||
//$conf['fixme'] = 'FIXME';
|
||||
|
||||
$conf['debug'] = 0;
|
||||
$conf['dsn'] = '';
|
||||
$conf['user'] = '';
|
||||
$conf['pass'] = '';
|
||||
|
||||
/**
|
||||
* statement to select a single user identified by its login name given as :user
|
||||
*
|
||||
* return; user, name, mail, (clear|hash), [uid]
|
||||
* other fields are returned but not used
|
||||
*/
|
||||
$conf['select-user'] = '';
|
10
lib/plugins/authpdo/conf/metadata.php
Normal file
10
lib/plugins/authpdo/conf/metadata.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
/**
|
||||
* Options for the authpdo plugin
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
|
||||
|
||||
//$meta['fixme'] = array('string');
|
||||
|
16
lib/plugins/authpdo/lang/en/lang.php
Normal file
16
lib/plugins/authpdo/lang/en/lang.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
/**
|
||||
* English language file for authpdo plugin
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
|
||||
// menu entry for admin plugins
|
||||
// $lang['menu'] = 'Your menu entry';
|
||||
|
||||
// custom language strings for the plugin
|
||||
// $lang['fixme'] = 'FIXME';
|
||||
|
||||
|
||||
|
||||
//Setup VIM: ex: et ts=4 :
|
13
lib/plugins/authpdo/lang/en/settings.php
Normal file
13
lib/plugins/authpdo/lang/en/settings.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
/**
|
||||
* english language file for authpdo plugin
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
|
||||
// keys need to match the config setting name
|
||||
// $lang['fixme'] = 'FIXME';
|
||||
|
||||
|
||||
|
||||
//Setup VIM: ex: et ts=4 :
|
7
lib/plugins/authpdo/plugin.info.txt
Normal file
7
lib/plugins/authpdo/plugin.info.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
base authpdo
|
||||
author Andreas Gohr
|
||||
email andi@splitbrain.org
|
||||
date 2016-01-29
|
||||
name authpdo plugin
|
||||
desc Authenticate against a database via PDO
|
||||
url https://www.dokuwiki.org/plugin:authpdo
|
Loading…
Reference in a new issue