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