1<?php 2 3/** 4 * CAS authentication plugin 5 * 6 * @licence GPL 2 (http://www.gnu.org/licenses/gpl.html) 7 * @author Xylle, Fabian Bircher 8 * @version 0.0.3 9 * 10 */ 11 12use AuthSSOCas\SimpleFileLogger; 13use dokuwiki\Extension\AuthPlugin; 14use dokuwiki\Logger; 15 16 17class auth_plugin_authssocas extends AuthPlugin 18{ 19 /** 20 * @var array|mixed 21 */ 22 private array $options = array(); 23 24 private ?string $logfileuser = null; 25 26 public function __construct() 27 { 28 global $conf; 29 parent::__construct(); 30 require_once __DIR__ . '/vendor/autoload.php'; 31 32 // Vérifie si la classe phpCAS existe 33 if (!class_exists('phpCAS')) { 34 msg("CAS err: phpCAS class not found.", -1); 35 $this->success = false; 36 return; 37 } 38 // Vérifie si l'extension curl existe 39 if (!extension_loaded("curl")) { 40 msg("CAS err: CURL php extension not found.", -1); 41 $this->success = false; 42 return; 43 } 44 // Définition des capacités de l'extension d'authentification 45 $this->cando['external'] = true; 46 47 // Création d'un journal des connexions, si un fichier est défini. 48 if ($this->getConf('logfileuser')) { 49 $this->logfileuser = $conf['logdir'] . "/" . $this->getConf('logfileuser'); 50 } 51 if (!is_null($this->logfileuser) and !@is_readable($this->logfileuser)) { 52 if (!fopen($this->logfileuser, 'a')) { 53 msg("plainCAS: The CAS log users file could not be opened.", -1); 54 $this->success = false; 55 } 56 } 57 58 59 // Chargement des options 60 $this->options['debug'] = $this->getConf('debug'); 61 $this->options['group_attribut'] = $this->getConf('group_attribut'); 62 $this->options['group_attribut_separator'] = $this->getConf('group_attribut_separator'); 63 $this->options['handlelogoutrequest'] = $this->getConf('handlelogoutrequest'); 64 $this->options['handlelogoutrequestTrustedHosts'] = $this->getConf('handlelogoutrequestTrustedHosts'); 65 $this->options['mail_attribut'] = $this->getConf('mail_attribut'); 66 $this->options['name_attribut'] = $this->getConf('name_attribut'); 67 $this->options['port'] = $this->getConf('port'); 68 $this->options['samlValidate'] = $this->getConf('samlValidate'); 69 $this->options['server'] = $this->getConf('server'); 70 $this->options['rootcas'] = $this->getConf('rootcas'); 71 $this->options['uid_attribut'] = $this->getConf('uid_attribut'); 72 $this->options['cacert'] = $this->getConf('cacert'); 73 74 $server_version = CAS_VERSION_2_0; 75 if ($this->getOption("samlValidate")) { 76 $server_version = SAML_VERSION_1_1; 77 } 78 79 if ($this->getOption("debug")) { 80 $logdir = $conf['logdir']; 81 $logger = new SimpleFileLogger($logdir . '/cas.log'); 82 phpCAS::setLogger($logger); 83 phpCAS::setVerbose(true); 84 } 85 86 if (!DOKU_BASE == "/") { 87 $service_base_url = str_replace(DOKU_BASE, "", DOKU_URL); 88 } else { 89 $service_base_url = DOKU_URL; 90 } 91 92 // Configuration du client CAS 93 phpCAS::client( 94 $server_version, 95 $this->getOption('server'), 96 (int)$this->getOption('port'), 97 $this->getOption('rootcas'), 98 $service_base_url 99 ); 100 101 if ($this->getConf('autologin')) { 102 phpCAS::setCacheTimesForAuthRecheck(-1); 103 } else { 104 phpCAS::setCacheTimesForAuthRecheck(1); 105 } 106 107 // Gestion de l'autorité de certification du certificat du serveur CAS pour la bibliothèque php_curl 108 $cas_cacert_file = DOKU_CONF . 'authssocas.cacert.pem'; 109 if ($this->getOption('cacert')) { 110 if (!io_saveFile($cas_cacert_file, $this->getOption('cacert'))) { 111 msg('The ' . $cas_cacert_file . ' file is not writable. Please inform the Wiki-Admin', -1); 112 } 113 phpCAS::setCasServerCACert($cas_cacert_file); 114 } else { 115 phpCAS::setNoCasServerValidation(); 116 } 117 118 // Gestion de la déconnexion sur le serveur CAS 119 if ($this->getOption('handlelogoutrequest')) { 120 phpCAS::handleLogoutRequests(true, $this->getOption('handlelogoutrequestTrustedHosts')); 121 } else { 122 phpCAS::handleLogoutRequests(false); 123 } 124 } 125 126 /** 127 * 128 * Récupère les options 129 * Transforme en tableau les URL de notification de la déconnexion pour les serveurs CAS 130 * 131 * @param $optionName 132 * @return array|mixed|string[]|null 133 */ 134 private function getOption($optionName): mixed 135 { 136 if (isset($this->options[$optionName])) { 137 switch ($optionName) { 138 case 'handlelogoutrequestTrustedHosts': 139 $arr = explode(',', $this->options[$optionName]); 140 foreach ($arr as $key => $item) { 141 $arr[$key] = trim($item); 142 } 143 return $arr; 144 default: 145 return $this->options[$optionName]; 146 } 147 } 148 return NULL; 149 } 150 151 /** 152 * 153 * Transfert de la demande de connexion au serveur CAS 154 * 155 * @return void 156 * @noinspection PhpUnused 157 */ 158 public function logIn(): void 159 { 160 global $ID; 161 $login_url = DOKU_URL . 'doku.php?id=' . $ID; 162 163 phpCAS::setFixedServiceURL($login_url); 164 phpCAS::forceAuthentication(); 165 } 166 167 /** 168 * 169 * Déconnexion de l'utilisateur avec prise en compte de la déconnexion générale du CAS 170 * 171 * @return void 172 */ 173 public function logOff(): void 174 { 175 global $ID; 176 global $USERINFO; 177 178 @session_start(); 179 session_destroy(); 180 181 $this->auth_log($USERINFO, "logout"); 182 if ($this->getOption('handlelogoutrequest')) { 183 $logout_url = DOKU_URL . 'doku.php?id=' . $ID; 184 @phpCAS::logoutWithRedirectService($logout_url); 185 } else { 186 phpCAS::handleLogoutRequests(); 187 unset($_SESSION); 188 } 189 } 190 191 public function trustExternal($user, $pass, $sticky = false): bool 192 { 193 global $USERINFO; 194 195 if (!empty($_SESSION[DOKU_COOKIE]['auth']['info'])) { 196 $USERINFO['name'] = $_SESSION[DOKU_COOKIE]['auth']['info']['name']; 197 $USERINFO['mail'] = $_SESSION[DOKU_COOKIE]['auth']['info']['mail']; 198 $USERINFO['grps'] = $_SESSION[DOKU_COOKIE]['auth']['info']['grps']; 199 $_SERVER['REMOTE_USER'] = $_SESSION[DOKU_COOKIE]['auth']['user']; 200 return true; 201 } 202 203 if (phpCAS::isAuthenticated() or ($this->getOption('autologin') and phpCAS::checkAuthentication())) { 204 205 $USERINFO = $this->cas_user_attributes(phpCAS::getAttributes()); 206 $this->auth_log($USERINFO, "login"); 207 $_SESSION[DOKU_COOKIE]['auth']['user'] = $USERINFO['uid']; 208 $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO; 209 $_SERVER['REMOTE_USER'] = $USERINFO['uid']; 210 return true; 211 } 212 213 return false; 214 } 215 216 /** 217 * 218 * Renvoi les informations de l'utilisateur fournit par le CAS 219 * 220 * @param $attributes 221 * @return array 222 */ 223 private function cas_user_attributes($attributes): array 224 { 225 return array( 226 'uid' => $attributes[$this->getOption('uid_attribut')] ?? '', 227 'name' => $attributes[$this->getOption('name_attribut')] ?? '', 228 'mail' => $attributes[$this->getOption('mail_attribut')] ?? '', 229 'grps' => $this->cas_user_groups($attributes), 230 ); 231 } 232 233 /** 234 * 235 * Log user connection if the log file is defined 236 * 237 * format : DATE|TIME|ACTION|USER|CLIENT_IP|REAL_CLIENT_IP|USERINFO 238 * ACTION : login ou logout 239 * REAL_CLIENT_IP : si null, il y a un tiret 240 * USERINFO : si null, il y a un tiret 241 * 242 * @param $userinfo 243 * @param string $action 244 * @return void 245 */ 246 private function auth_log($userinfo, string $action): void 247 { 248 if (!is_null($this->logfileuser)) { 249 $date = (new DateTime('now'))->format('Ymd|H:i:s'); 250 $real_client_ip = ($this->getOption('http_header_real_ip') ? ($_SERVER[$this->getOption('http_header_real_ip')] ?? '-') : '-'); 251 $client_ip = $_SERVER['REMOTE_ADDR']; 252 253 $utilisateur = $userinfo['uid'] ?? ($_SESSION[DOKU_COOKIE]['auth']['user'] ?? '-'); 254 $informations = $userinfo ? json_encode($userinfo, JSON_UNESCAPED_UNICODE) : '-'; 255 256 $userline = $date . "|" . 257 $action . "|" . 258 $utilisateur . '|' . 259 $client_ip . '|' . 260 $real_client_ip . '|' . 261 $informations . 262 PHP_EOL; 263 264 265 $this->write_log($userline); 266 } 267 } 268 269 /** 270 * 271 * Renvoi les groupes de l'utilisateur fournis par le CAS 272 * et s'assure que la valeur est bien de type array 273 * 274 * @param $attributes 275 * @return array 276 */ 277 private function cas_user_groups($attributes): array 278 { 279 global $conf; 280 $raw_groups = $attributes[$this->getOption('group_attribut')] ?: array(); 281 $user_groups = array(); 282 283 Logger::debug("authssocas: raw user groups '" . implode(',', (array)$raw_groups) . "' - Group separator : '" . $this->getOption('group_attribut_separator') . "' - defaultgroup : '{$conf['defaultgroup']}'"); 284 if (!$this->getOption('group_attribut_separator')) { 285 # Sans configuration de group_attribut_separator : la valeur retournée par CAS doit être un tableau. 286 if (!is_array($raw_groups)) { 287 $user_groups = array($raw_groups); 288 } else { 289 $user_groups = $raw_groups; 290 } 291 } else { 292 # Avec une configuration `group_attribut_separator` : la valeur retournée par CAS doit être une chaîne de caractères. 293 if (is_array($raw_groups)) { 294 $user_groups = $raw_groups; 295 } elseif (is_string($raw_groups)) { 296 $user_groups = explode($this->getOption('group_attribut_separator'), $raw_groups); 297 } 298 } 299 300 # Toujours ajouter le groupe par défaut (comme le font les autres plugins d'authentification). 301 if ($conf['defaultgroup'] && !in_array($conf['defaultgroup'], $user_groups)) { 302 $user_groups[] = $conf['defaultgroup']; 303 } 304 return $user_groups; 305 } 306 307 /** 308 * @param string $userline 309 * @return void 310 */ 311 public function write_log(string $userline): void 312 { 313 if (!io_saveFile($this->logfileuser, $userline, true)) { 314 msg($this->getLang('writefail'), -1); 315 } 316 } 317} 318