1<?php
2/**
3 * DokuWiki Plugin authhiorgserver (Auth Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  HiOrg Server GmbH <support@hiorg-server.de>
7 */
8
9// must be run within Dokuwiki
10if(!defined('DOKU_INC')) {
11    die();
12}
13
14class auth_plugin_authhiorgserver extends DokuWiki_Auth_Plugin {
15
16    private $ssourl = "";
17    private $data = array();
18    private $triedsilent = false;
19    private $usersepchar = "@";
20
21    /**
22     * Constructor.
23     */
24    public function __construct() {
25        parent::__construct(); // for compatibility
26
27        $this->cando['addUser']     = false; // can Users be created?
28        $this->cando['delUser']     = false; // can Users be deleted?
29        $this->cando['modLogin']    = false; // can login names be changed?
30        $this->cando['modPass']     = false; // can passwords be changed?
31        $this->cando['modName']     = false; // can real names be changed?
32        $this->cando['modMail']     = false; // can emails be changed?
33        $this->cando['modGroups']   = false; // can groups be changed?
34        $this->cando['getUsers']    = false; // can a (filtered) list of users be retrieved?
35        $this->cando['getUserCount']= false; // can the number of users be retrieved?
36        $this->cando['getGroups']   = false; // can a list of available groups be retrieved?
37        $this->cando['logout']      = true; // can the user logout again? (eg. not possible with HTTP auth)
38        $this->cando['external']    = true; // does the module do external auth checking?
39
40        // $this->loadConfig(); // deprecated seit 2012
41
42        $this->ssourl = $this->getConf('ssourl');
43        $ov = $this->getConf('ov');
44        if(!empty($ov)) {
45            $this->ssourl = $this->addUrlParams($this->ssourl,array("ov"=>$ov));
46        }
47
48        $this->data = array();
49
50        $this->triedsilent = (isset($_SESSION[DOKU_COOKIE]['auth']['hiorg']['triedsilent'])
51                              && ($_SESSION[DOKU_COOKIE]['auth']['hiorg']['triedsilent'] == true));
52
53        $this->success = true;
54    }
55
56
57    /**
58     * Log off the current user
59     */
60    public function logOff() {
61        $url = $this->addUrlParams($this->ssourl,array("logout"=>1,"token"=>$this->data["token"],"weiter"=> $this->myUrl()));
62
63        $this->data = array();
64        $_SESSION[DOKU_COOKIE]['auth']['hiorg'] = array("triedsilent"=>true);
65
66        send_redirect($url);
67    }
68
69    /**
70     * Do all authentication
71     *
72     * @param   string  $user    Username
73     * @param   string  $pass    Cleartext Password
74     * @param   bool    $sticky  Cookie should not expire
75     * @return  bool             true on successful auth
76     */
77    public function trustExternal($user, $pass, $sticky = false) {
78
79        if ($this->loadUserInfoFromSession()) {
80            $this->setGlobalConfig();
81            return true;
82        }
83
84        global $ACT;
85        if($ACT == "login") {
86            $this->processSSO();
87
88            $this->setGlobalConfig();
89            $this->saveUserInfoToSession();
90
91        } elseif(!$this->triedsilent) {
92            $_SESSION[DOKU_COOKIE]['auth']['hiorg']['triedsilent'] = $this->triedsilent = true;
93            $this->SSOsilent();
94        }
95
96        return true;
97    }
98
99    function myUrl($urlParameters = '') {
100        // global $ID;  // ist zu diesem Zeitpunkt noch nicht initialisiert
101        $ID = getID();
102        return wl($ID, $urlParameters, true, '&');
103    }
104
105    function processSSO() {
106
107        // 1. Schritt: noch kein gueltiges Token vom HiOrg-Server erhalten
108        if(empty($_GET["token"])) {
109            $ziel = $this->addUrlParams($this->ssourl,array("weiter"=> $this->myUrl(array("do"=>"login")), // do=login, damit wir für den 2. Schritt wieder hier landen
110                                                            "getuserinfo"=>"name,vorname,username,email,user_id"));
111            send_redirect($ziel);
112        }
113
114        // 2. Schritt: Token vom HiOrg-Server erhalten: jetzt Login ueberpruefen und Nutzerdaten abfragen
115        $token = $_GET["token"];
116
117        $url = $this->addUrlParams($this->ssourl,array("token"=>$token));
118        $daten = $this->getUrl($url);
119
120        if(mb_substr( $daten ,0,2) != "OK") {
121            nice_die("Login beim HiOrg-Server fehlgeschlagen!");
122        }
123        $daten = unserialize(base64_decode(mb_substr( $daten , 3)));
124
125        // wenn per Konfig auf eine Organisation festgelegt, Cross-Logins abfangen:
126        $ov = $this->getConf('ov');
127        if( !empty($ov) && ($daten["ov"] != $ov) ) {
128            nice_die("Falsches Organisationskuerzel: ".$daten["ov"]. ", erwartet: ".$ov);
129        }
130
131        // $daten = array("name"=>"Hansi", "vorname"=>"Tester", "username"=>"admin", "email"=>"test@test.de", "user_id"=>"abcde12345", "ov"=>"xxx");
132
133        $this->data = array("uid"  => $daten["user_id"],
134                            "user" => $this->buildUser($daten["username"],$daten["ov"]),
135                            "name" => $this->buildName($daten["vorname"], $daten["name"]),
136                            "mail" => $daten["email"],
137                            "token"=> $token);
138        $this->data["grps"] = $this->getGroups($this->data["user"]);
139
140        return true;
141    }
142
143    function SSOsilent() {
144        $ziel = $this->addUrlParams($this->ssourl, array("weiter"      => $this->myUrl(array("do"=>"login")), // do=login, damit wir für den 2. Schritt wieder hier landen
145                                                         "getuserinfo" => "name,vorname,username,email,user_id",
146                                                         "silent"      => $this->myUrl()));
147        send_redirect($ziel);
148    }
149
150    function getGroups($user) {
151        if(empty($user)) {
152            return "";
153        }
154
155        $ov = trim($this->getConf("ov"));
156
157        global $conf;
158        $return = array($this->cleanGroup($conf["defaultgroup"]));
159
160        $groups = array("group1"=>$this->getConf("group1_name"),
161                        "group2"=>$this->getConf("group2_name"),
162                        "admin" =>"admin");
163
164        foreach($groups as $name => $group) {
165            $users = $this->getConf($name."_users");
166            if(!empty($group) && !empty($users)) {
167                if(!empty($ov)) { // ov automatisch ergänzen, wenn bekannt und nicht genannt
168                    $userary = explode(",",$users);
169                    $users = "";
170                    foreach($userary as $u) {
171                        if(strpos($u,$this->usersepchar)===false) {
172                            $u = $this->buildUser($u, $ov);
173                        }
174                        $users .= "," . $u;
175                    }
176                }
177                if(strpos($users,$user)!==false) {
178                    $return[] = $this->cleanGroup($group);
179                }
180            }
181        }
182
183        return $return;
184    }
185
186    function buildUser($user, $ov="") {
187        if(empty($ov)) {
188            $ov = trim($this->getConf("ov"));
189        }
190        return $this->cleanUser($user) . $this->usersepchar . $this->cleanUser($ov);
191    }
192
193    function buildName($vorname, $name) {
194        switch ($this->getConf('syncname')) {
195            case 'vname':
196                return substr($vorname,0,1).". ".$name;
197            case 'vona':
198                return substr($vorname,0,2).substr($name,0,2);
199            case 'vn':
200                return substr($vorname,0,1).substr($name,0,1);
201            default:
202                return $vorname." ".$name;
203        }
204    }
205
206    function loadUserInfoFromSession() {
207        if(isset($_SESSION[DOKU_COOKIE]['auth']['hiorg'])) {
208            $data = $_SESSION[DOKU_COOKIE]['auth']['hiorg'];
209            if(empty($data) || !is_array($data) || empty($data["token"])) {
210                return false;
211            } else {
212                $this->data = $data;
213                return true;
214            }
215        }
216        return false;
217    }
218
219    function saveUserInfoToSession() {
220        if(!empty($this->data["token"])) {
221            $_SESSION[DOKU_COOKIE]['auth']['hiorg'] = $this->data;
222            return true;
223        }
224        return false;
225    }
226
227    function setGlobalConfig() {
228        global $USERINFO;
229        $USERINFO['name'] = $this->data['name'];
230        $USERINFO['mail'] = $this->data['mail'];
231        $USERINFO['grps'] = $this->data['grps'];
232        $_SERVER['REMOTE_USER'] = $this->data['user'];
233        $_SESSION[DOKU_COOKIE]['auth']['user'] = $this->data['user'];
234        $_SESSION[DOKU_COOKIE]['auth']['pass'] = "";
235        $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
236        return true;
237    }
238
239    /**
240     * Helper: builds URL by adding parameters
241     *
242     * @param   string $url URL
243     * @param   array $params additional parameters
244     * @return  string
245     */
246    function addUrlParams($url, $params) {
247        if(!is_array($params) || empty($params)) {
248            return $url;
249        }
250
251        $parary = array();
252        $p = strpos($url,"?");
253        if($p!==false) {
254            foreach(explode("&",substr($url,$p+1)) as $par) {
255                $q = strpos($par,"=");
256                $parary[substr($par,0,$q)] = substr($par,$q+1);
257            }
258            $url = substr($url,0,$p);
259        }
260
261        foreach($params as $par => $val) {
262            $parary[rawurlencode($par)] = rawurlencode($val);
263        }
264
265        $ret = $url;
266        $sep = "?";
267        foreach($parary as $par => $val) {
268            $ret .= $sep . $par . "=" . $val;
269            $sep = "&";
270        }
271        return $ret;
272    }
273
274    /**
275     * Helper: fetches external URL via GET
276     *
277     * @param   string $url URL
278     * @return  string
279     */
280    function getUrl($url) {
281        $http = new DokuHTTPClient();
282        $daten = $http->get($url);
283
284        // Workarounds, o.g. Klasse macht manchmal Probleme:
285        if(empty($daten)) {
286            if(function_exists("curl_init")) {
287                $ch = curl_init($url);
288                curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
289                curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
290                $daten = curl_exec($ch);
291                curl_close($ch);
292
293            } else {
294                if (!ini_get("allow_url_fopen") && version_compare(phpversion(), "4.3.4", "<=")) {
295                    ini_set("allow_url_fopen", "1");
296                }
297                if ($fp = @fopen($url, "r")) {
298                    $daten = "";
299                    while (!feof($fp)) $daten.= fread($fp, 1024);
300                    fclose($fp);
301                }
302            }
303        }
304
305        return $daten;
306    }
307
308    /**
309     * Return user info
310     *
311     * Returns info about the given user needs to contain
312     * at least these fields:
313     *
314     * name string  full name of the user
315     * mail string  email addres of the user
316     * grps array   list of groups the user is in
317     *
318     * @param   string $user the user name
319     * @return  array containing user data or false
320     */
321    /*public function getUserData($user) {
322        // FIXME implement
323        return false;
324    }*/
325
326
327    /**
328     * Bulk retrieval of user data [implement only where required/possible]
329     *
330     * Set getUsers capability when implemented
331     *
332     * @param   int   $start     index of first user to be returned
333     * @param   int   $limit     max number of users to be returned
334     * @param   array $filter    array of field/pattern pairs, null for no filter
335     * @return  array list of userinfo (refer getUserData for internal userinfo details)
336     */
337    //public function retrieveUsers($start = 0, $limit = -1, $filter = null) {
338        // FIXME implement
339    //    return array();
340    //}
341
342    /**
343     * Return a count of the number of user which meet $filter criteria
344     * [should be implemented whenever retrieveUsers is implemented]
345     *
346     * Set getUserCount capability when implemented
347     *
348     * @param  array $filter array of field/pattern pairs, empty array for no filter
349     * @return int
350     */
351    //public function getUserCount($filter = array()) {
352        // FIXME implement
353    //    return 0;
354    //}
355
356    /**
357     * Retrieve groups [implement only where required/possible]
358     *
359     * Set getGroups capability when implemented
360     *
361     * @param   int $start
362     * @param   int $limit
363     * @return  array
364     */
365    //public function retrieveGroups($start = 0, $limit = 0) {
366        // FIXME implement
367    //    return array();
368    //}
369
370    /**
371     * Return case sensitivity of the backend
372     *
373     * When your backend is caseinsensitive (eg. you can login with USER and
374     * user) then you need to overwrite this method and return false
375     *
376     * @return bool
377     */
378    public function isCaseSensitive() {
379        return false;
380    }
381
382    /**
383     * Sanitize a given username
384     *
385     * This function is applied to any user name that is given to
386     * the backend and should also be applied to any user name within
387     * the backend before returning it somewhere.
388     *
389     * This should be used to enforce username restrictions.
390     *
391     * @param string $user username
392     * @return string the cleaned username
393     */
394
395    public function cleanUser($user) {
396        global $conf;
397        return cleanID(str_replace(':', $conf['sepchar'], $user));
398    }
399    /**
400     * Sanitize a given groupname
401     *
402     * This function is applied to any groupname that is given to
403     * the backend and should also be applied to any groupname within
404     * the backend before returning it somewhere.
405     *
406     * This should be used to enforce groupname restrictions.
407     *
408     * Groupnames are to be passed without a leading '@' here.
409     *
410     * @param  string $group groupname
411     * @return string the cleaned groupname
412     */
413    public function cleanGroup($group) {
414        global $conf;
415        return cleanID(str_replace(':', $conf['sepchar'], $group));
416    }
417
418    /**
419     * Check Session Cache validity [implement only where required/possible]
420     *
421     * DokuWiki caches user info in the user's session for the timespan defined
422     * in $conf['auth_security_timeout'].
423     *
424     * This makes sure slow authentication backends do not slow down DokuWiki.
425     * This also means that changes to the user database will not be reflected
426     * on currently logged in users.
427     *
428     * To accommodate for this, the user manager plugin will touch a reference
429     * file whenever a change is submitted. This function compares the filetime
430     * of this reference file with the time stored in the session.
431     *
432     * This reference file mechanism does not reflect changes done directly in
433     * the backend's database through other means than the user manager plugin.
434     *
435     * Fast backends might want to return always false, to force rechecks on
436     * each page load. Others might want to use their own checking here. If
437     * unsure, do not override.
438     *
439     * @param  string $user - The username
440     * @return bool
441     */
442    //public function useSessionCache($user) {
443      // FIXME implement
444    //}
445}
446
447// vim:ts=4:sw=4:et:
448