xref: /plugin/authssocas/auth.php (revision a3e10dfab52e5e1c640768ce5bb7909a74873d9c)
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