1<?php 2/** 3 * DokuWiki Plugin smartcard (Auth Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Stephen Bowman <sbbowman@gmail.com> 7 */ 8 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11 12//class auth_plugin_authsmartcard extends DokuWiki_Auth_Plugin { 13class auth_plugin_authsmartcard extends auth_plugin_authplain { 14 15 16 /** 17 * Constructor. 18 */ 19 public function __construct() { 20 parent::__construct(); // for compatibility 21 22 $this->cando['addUser'] = true; // can Users be created? 23 $this->cando['delUser'] = true; // can Users be deleted? 24 $this->cando['modLogin'] = true; // can login names be changed? 25 $this->cando['modPass'] = true; // can passwords be changed? 26 $this->cando['modName'] = true; // can real names be changed? 27 $this->cando['modMail'] = true; // can emails be changed? 28 $this->cando['modGroups'] = true; // can groups be changed? 29 $this->cando['getUsers'] = true; // can a (filtered) list of users be retrieved? 30 $this->cando['getUserCount']= true; // can the number of users be retrieved? 31 $this->cando['getGroups'] = true; // can a list of available groups be retrieved? 32 $this->cando['external'] = false; // does the module do external auth checking? 33 $this->cando['logout'] = false; // can the user logout again? (eg. not possible with HTTP auth) 34 35 $this->success = true; 36 } 37 38 39 /** 40 * Check user+password 41 * 42 * May be ommited if trustExternal is used. 43 * 44 * @param string $user the user name 45 * @param string $pass the clear text password 46 * @return bool 47 */ 48 public function checkPass(&$username, &$password) { 49 50 session_start(); 51 // test if already logged in 52 if(isset($_SESSION['smartcard_userdata']) && $_SESSION['smartcard_userdata']['username']==$username && md5($_SESSION['smartcard_userdata']['pass'])==$password){ 53 return true; 54 } 55 56 // client certificate log in 57 if($username=='smartcard'){ 58 // if client cert exists 59 if(isset($_SESSION['SSL_CLIENT_CERT']) && $_SESSION['SSL_CLIENT_CERT']){ 60 // parse cert data 61 $client_cert_data = openssl_x509_parse($_SESSION['SSL_CLIENT_CERT']); 62 $cn = $client_cert_data['subject']['CN']; 63 // if cn was in cert 64 if($cn){ 65 $userdata = $this->findUserByCN($cn); 66 if(!$userdata){ 67 $this->__log("Did not find user with cn=$cn"); 68 msg('Did not find user with cn: '.$cn, -1); 69 } 70 } 71 } 72 // if client cert does not exist 73 else{ 74 $this->__log("Client certificate not found"); 75 msg('Smartcard was not found. Please check that it is connected and drivers are installed.', -1); 76 return false; 77 } 78 } 79 80 // SEE THAT WE GOT SMTH AND LOG HIM IN 81 if($userdata){ 82 // overwrite username and password with what was found in the user db (notice, function was defined: checkPass(&$username, &$password)) 83 $username = $userdata['username']; 84 $password = md5($userdata['pass']); 85 // set $_SESSION['smartcard_userdata'] because otherwise auth will fail later on because invalid pw 86 $_SESSION['smartcard_userdata'] = $userdata; 87 session_write_close(); 88 return true; 89 } 90 91 // logon failed for some other unknown reason... 92 unset($_SESSION['smartcard_userdata']); 93 session_write_close(); 94 return false; 95 } 96 97 /** 98 * Finds user by cn 99 * 100 */ 101 public function findUserByCN($cn){ 102 if(!$cn){ 103 $this->__log("passed an empty CN?"); 104 return false; 105 } 106 107 // retrieve all users where the CN is in the group for a user. 108 $users = $this->retrieveUsers(0, 2000, array('grps'=>$cn)); 109 110 // if user count 1 111 if(count($users)==1){ 112 // create username value for user 113 foreach($users as $key => &$value){ 114 $value['username'] = $key; 115 } 116 $users = array_values($users); 117 $this->__log("Found user=" . $users[0]['username'] ." with CN=$cn"); 118 $this->__log(array($users[0])); 119 return $users[0]; 120 } 121 // if user count more than 1 122 if(count($users)>1){ 123 $this->__log("Found multiple users with group having CN=$cn, that should not happen"); 124 return false; 125 } 126 // no users found 127 $this->__log("$cn not found"); 128 return false; 129 } 130 131 /** 132 * Finds user by username and password 133 * 134 */ 135 public function findUserByUsernameAndPassword($username, $password){ 136 $username = preg_replace('/[^\w\d\.-_]/', '', $username); 137 $password = preg_replace('/[^\w\d\.-_]/', '', $password); 138 139 $userdata = $this->getUserData($username); 140 $userdata['username'] = $username; 141 return $userdata; 142 } 143 144 /** 145 * Logs messages to data/log/auth_smartcard.log.txt 146 * 147 */ 148 public function __log($text){ 149 150 $text = json_encode($text); 151 152 if($this->getConf('log_to_file') && $this->getConf('logfile')){ 153 file_put_contents($this->getConf('logfile'), date('c').": ".$text."\n", FILE_APPEND); 154 } 155 } 156 157 /** 158 * Return user info 159 * 160 * Returns info about the given user needs to contain 161 * at least these fields: 162 * 163 * name string full name of the user 164 * mail string email addres of the user 165 * grps array list of groups the user is in 166 * 167 * @param string $user the user name 168 * @return array containing user data or false 169 */ 170 public function getUserData($user) { 171 return parent::getUserData($user); 172 } 173 174 /** 175 * Create a new User [implement only where required/possible] 176 * 177 * Returns false if the user already exists, null when an error 178 * occurred and true if everything went well. 179 * 180 * The new user HAS TO be added to the default group by this 181 * function! 182 * 183 * Set addUser capability when implemented 184 * 185 * @param string $user 186 * @param string $pass 187 * @param string $name 188 * @param string $mail 189 * @param null|array $grps 190 * @return bool|null 191 */ 192 public function createUser($user, $pass, $name, $mail, $grps = null) { 193 return parent::createUser($user, $pass, $name, $mail, $grps); 194 } 195 196 /** 197 * Modify user data [implement only where required/possible] 198 * 199 * Set the mod* capabilities according to the implemented features 200 * 201 * @param string $user nick of the user to be changed 202 * @param array $changes array of field/value pairs to be changed (password will be clear text) 203 * @return bool 204 */ 205 public function modifyUser($user, $changes) { 206 return parent::modifyUser($user, $changes); 207 } 208 209 /** 210 * Delete one or more users [implement only where required/possible] 211 * 212 * Set delUser capability when implemented 213 * 214 * @param array $users 215 * @return int number of users deleted 216 */ 217 public function deleteUsers($users) { 218 return parent::deleteUsers($users); 219 } 220 221 /** 222 * Return a count of the number of user which meet $filter criteria 223 * [should be implemented whenever retrieveUsers is implemented] 224 * 225 * Set getUserCount capability when implemented 226 * 227 * @param array $filter array of field/pattern pairs, empty array for no filter 228 * @return int 229 */ 230 public function getUserCount($filter = array()) { 231 return parent::getUserCount($filter); 232 } 233 234 /** 235 * Bulk retrieval of user data 236 * 237 * @param int $start index of first user to be returned 238 * @param int $limit max number of users to be returned 239 * @param array $filter array of field/pattern pairs 240 * @return array userinfo (refer getUserData for internal userinfo details) 241 */ 242 public function retrieveUsers($start, $limit, $filter) { 243 return parent::retrieveUsers($start, $limit, $filter); 244 } 245 246 /** 247 * Define a group [implement only where required/possible] 248 * 249 * Set addGroup capability when implemented 250 * 251 * @param string $group 252 * @return bool 253 */ 254 public function addGroup($group) { 255 return parent::addGroup(); 256 } 257 258 /** 259 * Retrieve groups [implement only where required/possible] 260 * 261 * Set getGroups capability when implemented 262 * 263 * @param int $start 264 * @param int $limit 265 * @return array 266 */ 267 public function retrieveGroups($start = 0, $limit = 0) { 268 return parent::retrieveGroups(); 269 } 270 271 /** 272 * Return case sensitivity of the backend 273 * 274 * When your backend is caseinsensitive (eg. you can login with USER and 275 * user) then you need to overwrite this method and return false 276 * 277 * @return bool 278 */ 279 public function isCaseSensitive() { 280 return parent::isCaseSensitive(); 281 } 282} 283 284// vim:ts=4:sw=4:et: 285