1<?php
2
3/**
4 * DokuWiki Plugin authsaml (SAML class)
5 *
6 * @author  Sixto Martin <sixto.martin.garcia@gmail.com>
7 * @author  Andreas Aakre Solberg, UNINETT, http://www.uninett.no
8 * @author  François Kooman
9 * @author  Thijs Kinkhorst, Universiteit van Tilburg / SURFnet bv
10 * @author  Jorge Hervás <jordihv@gmail.com>, Lukas Slansky <lukas.slansky@upce.cz>
11
12 * @license GPL2 http://www.gnu.org/licenses/gpl.html
13 * @link https://github.com/pitbulk/dokuwiki-saml
14 */
15
16class saml_handler {
17
18    protected $simplesaml_path;
19    protected $simplesaml_authsource;
20    protected $simplesaml_uid;
21    protected $simplesaml_mail;
22    protected $simplesaml_name;
23    protected $simplesaml_grps;
24    protected $defaultgroup;
25
26    protected $force_saml_login;
27
28    protected $use_internal_user_store;
29    protected $saml_user_file;
30    protected $users;
31
32    public $ssp = null;
33    protected $attributes = array();
34
35
36    public function __construct($plugin_conf)
37    {
38        global $auth, $conf;
39
40        $this->defaultgroup = $conf['defaultgroup'];
41
42        $this->simplesaml_path = $plugin_conf['simplesaml_path'];
43        $this->simplesaml_authsource = $plugin_conf['simplesaml_authsource'];
44        $this->simplesaml_uid = $plugin_conf['simplesaml_uid'];
45        $this->simplesaml_mail = $plugin_conf['simplesaml_mail'];
46        $this->simplesaml_name = $plugin_conf['simplesaml_name'];
47        $this->simplesaml_grps = $plugin_conf['simplesaml_grps'];
48
49        $auth_saml_active = isset($conf['authtype']) && $conf['authtype'] == 'authsaml';
50
51        // Store users in files if we chose authsaml as authtype (due we cant use internal store) or if
52        // other auth is active and the 'use_internal_user_store' param in the plugin configuration file is false
53        $this->use_internal_user_store = !$auth_saml_active && $plugin_conf['use_internal_user_store'];
54
55        // Force redirection to the IdP if we chose authsaml as authtype or if we configured as true the 'force_saml_login'
56        $force_saml_login = $auth_saml_active || $plugin_conf['force_saml_login'];
57
58        $this->saml_user_file = $conf['savedir'] . '/users.saml.php';
59    }
60
61    /**
62	 *  Get a simplesamlphp auth instance (initiate it when it does not exist)
63     */
64    public function get_ssp_instance()
65    {
66        if ($this->ssp == null) {
67            include_once($this->simplesaml_path.'/lib/_autoload.php');
68            $this->ssp = new SimpleSAML_Auth_Simple($this->simplesaml_authsource);
69        }
70        return $this->ssp;
71    }
72
73    /**
74     * Get user data
75     *
76     * @return string|null
77     */
78    public function getUserData($user) {
79        if ($this->use_internal_user_store) {
80            global $auth;
81            return $auth->getUserData($user);
82        }
83        else {
84            return $this->getFILEUserData($user);
85        }
86    }
87
88    public function checkPass($user) {
89        $ssp = $this->get_ssp_instance();
90        if ($ssp->isAuthenticated()) {
91            if ($user == $this->getUsername()) {
92                return true;
93            }
94        }
95        return false;
96    }
97
98    public function slo()
99    {
100        if ($this->ssp->isAuthenticated()) {
101            $this->ssp->logout();
102        }
103        if ($this->use_internal_user_store) {
104            auth_logoff();
105        }
106    }
107
108    public function getUsername()
109    {
110        $attributes = $this->ssp->getAttributes();
111        return $attributes[$this->simplesaml_uid][0];
112    }
113
114
115    /**
116     * Get user data from the SAML assertion
117     *
118     * @return array|false
119     */
120    public function getSAMLUserData()
121    {
122        $this->attributes = $this->ssp->getAttributes();
123
124        if (!empty($this->attributes)) {
125            if (!array_key_exists($this->simplesaml_name , $this->attributes)) {
126              $name = "";
127            } else {
128              $name = $this->attributes[$this->simplesaml_name][0];
129            }
130
131            if (!array_key_exists($this->simplesaml_mail , $this->attributes)) {
132              $mail = "";
133            } else {
134              $mail = $this->attributes[$this->simplesaml_mail][0];
135            }
136
137            if (!array_key_exists($this->simplesaml_grps, $this->attributes) ||
138              empty($this->attributes[$this->simplesaml_grps])) {
139                $grps = array();
140            } else {
141                $grps = $this->attributes[$this->simplesaml_grps];
142            }
143
144            return array(
145                'name' => $name,
146                'mail' => $mail,
147                'grps' => $grps
148            );
149        }
150        return false;
151    }
152
153    /**
154     * Get user data from the saml store user file
155     *
156     * @return array|false
157     */
158    public function getFILEUserData($user)
159    {
160        if($this->users === null) $this->_loadUserData();
161        return isset($this->users[$user]) ? $this->users[$user] : false;
162    }
163
164	function login($username)
165    {
166        global $conf, $USERINFO;
167
168        $ssp = $this->get_ssp_instance();
169        $this->attributes = $ssp->getAttributes();
170
171
172        if ($ssp->isAuthenticated() && !empty($this->attributes)) {
173
174            $_SERVER['REMOTE_USER'] = $username;
175
176            $userData = $this->getUserData($username);
177
178            $USERINFO['name'] = $userData['name'];
179            $USERINFO['mail'] = $userData['mail'];
180            $USERINFO['grps'] = $userData['grps'];
181
182            // set cookie
183            $cookie    = base64_encode($username).'|'.((int) false).'|'.base64_encode($pass);
184            $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'];
185            $time      = $sticky ? (time() + 60 * 60 * 24 * 365) : 0; //one year
186            if(version_compare(PHP_VERSION, '5.2.0', '>')) {
187                setcookie(DOKU_COOKIE, $cookie, $time, $cookieDir, '', ($conf['securecookie'] && is_ssl()), true);
188            } else {
189                setcookie(DOKU_COOKIE, $cookie, $time, $cookieDir, '', ($conf['securecookie'] && is_ssl()));
190            }
191            // set session
192            $_SESSION[DOKU_COOKIE]['auth']['user'] = $username;
193            $_SESSION[DOKU_COOKIE]['auth']['pass'] = sha1(auth_pwgen());
194            $_SESSION[DOKU_COOKIE]['auth']['buid'] = auth_browseruid();
195            $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
196            $_SESSION[DOKU_COOKIE]['auth']['time'] = time();
197        }
198	}
199
200	function register_user($username) {
201		global $auth;
202		$user = $username;
203		$pass = auth_pwgen();
204
205        $userData = $this->getSAMLUserData();
206
207        if ($this->use_internal_user_store) {
208            global $auth;
209            if ($auth->canDo('addUser')) {
210                if (empty($userData['grps'])) {
211                    $userData['grps'] = array($this->defaultgroup);
212                }
213        		return $auth->createUser($user, $pass, $userData['name'], $userData['mail'], $userData['grps']);
214            }
215            else {
216                return false;
217            }
218        }
219        else {
220            return $this->_saveUserData($username, $userData);
221        }
222	}
223
224	function update_user($username) {
225		global $auth, $conf;
226
227		$changes = array();
228        $userData = $this->getSAMLUserData();
229
230		if ($auth->canDo('modName')) {
231    		if(!empty($userData['name'])) {
232				$changes['name'] = $userData['name'];
233			}
234		}
235		if ($auth->canDo('modMail')) {
236			if(!empty($userData['mail'])) {
237				$changes['mail'] = $userData['mail'];
238			}
239        }
240        if ($auth->canDo('modGroups')) {
241			if(!empty($userData['grps'])) {
242				$changes['grps'] = $userData['grps'];
243			}
244        }
245
246		if (!empty($changes)) {
247            if ($this->use_internal_user_store) {
248                $auth->modifyUser($username, $changes);
249            }
250            else {
251                $this->modifyUser($username, $changes);
252            }
253		}
254	}
255
256    function delete_user($users) {
257        if ($this->use_internal_user_store) {
258            global $auth;
259    		return $auth->deleteUser($users);
260        }
261        else {
262            return $this->deleteUsers($users);
263        }
264    }
265
266    /**
267     * Load all user data (modified copy from plain.class.php)
268     *
269     * loads the user file into a datastructure
270     *
271     * @author  Lukas Slansky <lukas.slansky@upce.cz>
272     */
273    function _loadUserData() {
274        global $conf;
275
276        $this->users = array();
277
278        if(!@file_exists($this->saml_user_file))
279            return;
280
281        $lines = file($this->saml_user_file);
282        foreach($lines as $line){
283            $line = preg_replace('/#.*$/','',$line); //ignore comments
284            $line = trim($line);
285            if(empty($line)) continue;
286
287            $row = explode(":",$line,5);
288            $groups = array_map('urldecode', array_values(array_filter(explode(",",$row[3]))));
289
290            $this->users[$row[0]]['name'] = urldecode($row[1]);
291            $this->users[$row[0]]['mail'] = $row[2];
292            $this->users[$row[0]]['grps'] = $groups;
293        }
294    }
295
296    /**
297     * Save user data
298     *
299     * saves the user file into a datastructure
300     *
301     * @author  Lukas Slansky <lukas.slansky@upce.cz>
302     */
303    function _saveUserData($username, $userData) {
304        global $conf;
305
306        $pattern = '/^' . $username . ':/';
307
308        // Delete old line from users file
309        if (!io_deleteFromFile($this->saml_user_file, $pattern, true)) {
310          msg('Error saving user data (1)', -1);
311          return false;
312        }
313        if ($userData['grps'] == null) {
314            $userData['grps'] = array();
315        }
316
317        $groups = join(',',array_map('urlencode',$userData['grps']));
318        $userline = join(':',array($username, $userData['name'], $userData['mail'], $groups))."\n";
319        // Save new line into users file
320        if (!io_saveFile($this->saml_user_file, $userline, true)) {
321          msg('Error saving user data (2)', -1);
322          return false;
323        }
324        $this->users[$username] = $userinfo;
325        return true;
326    }
327
328    public function deleteUsers($users) {
329
330        if(!is_array($users) || empty($users)) return 0;
331
332        if($this->users === null) $this->_loadUserData();
333
334        $deleted = array();
335        foreach($users as $user) {
336            if(isset($this->users[$user])) $deleted[] = preg_quote($user, '/');
337        }
338
339        if(empty($deleted)) return 0;
340
341        $pattern = '/^('.join('|', $deleted).'):/';
342
343        if(io_deleteFromFile($this->saml_user_file, $pattern, true)) {
344            foreach($deleted as $user) unset($this->users[$user]);
345            return count($deleted);
346        }
347
348        // problem deleting, reload the user list and count the difference
349        $count = count($this->users);
350        $this->_loadUserData();
351        $count -= count($this->users);
352        return $count;
353    }
354
355    public function modifyUser($username, $changes) {
356        global $ACT;
357
358        // sanity checks, user must already exist and there must be something to change
359        if(($userinfo = $this->getFILEUserData($username)) === false) return false;
360        if(!is_array($changes) || !count($changes)) return true;
361
362        // update userinfo with new data
363        $newuser = $username;
364        foreach($changes as $field => $value) {
365            if($field == 'user') {
366                $newuser = $value;
367                continue;
368            }
369            $userinfo[$field] = $value;
370        }
371
372        $groups   = join(',', $userinfo['grps']);
373        $groups   = array_map('urlencode', $groups);
374
375        $userline = join(':', array($newuser, $userinfo['name'], $userinfo['mail'], $groups))."\n";
376
377        if(!$this->deleteUsers(array($username))) {
378            msg('Unable to modify user data. Please inform the Wiki-Admin', -1);
379            return false;
380        }
381
382        if(!io_saveFile($this->saml_user_file, $userline, true)) {
383            msg('There was an error modifying your user data. You should register again.', -1);
384            // FIXME, user has been deleted but not recreated, should force a logout and redirect to login page
385            $ACT = 'register';
386            return false;
387        }
388
389        $this->users[$newuser] = $userinfo;
390        return true;
391    }
392
393
394
395}
396
397
398
399