began work on PDO auth plugin

This commit is contained in:
Andreas Gohr 2016-01-29 21:25:50 +01:00
parent ef89d2cdf6
commit f64dbc9005
12 changed files with 564 additions and 0 deletions

1
.gitignore vendored
View file

@ -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

View 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

View 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

View 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']));
}
}

View 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')));
}
}

Binary file not shown.

View 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:

View 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'] = '';

View file

@ -0,0 +1,10 @@
<?php
/**
* Options for the authpdo plugin
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
//$meta['fixme'] = array('string');

View 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 :

View 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 :

View 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