1<?php
2// must be run within Dokuwiki
3if(!defined('DOKU_INC')) die();
4
5/**
6 * Discourse authentication backend
7 *
8 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
9 * @author     Lukas Werling <lukas@lwrl.de>
10 */
11class auth_plugin_authdiscourse extends DokuWiki_Auth_Plugin {
12    private $sso_secret, $sso_url;
13    private $login_url;
14    private $nonce, $prev_nonce;
15
16    public function __construct() {
17        parent::__construct();
18
19        $this->success = true;
20        $this->cando['external'] = true;
21        $this->cando['logoff'] = true;
22
23        global $conf;
24        $cfg = $conf['plugin']['authdiscourse'];
25        if (empty($cfg['sso_secret']) || empty($cfg['sso_url'])) {
26            $this->success = false;
27        } else {
28            $this->sso_secret = $cfg['sso_secret'];
29            $this->sso_url = $cfg['sso_url'];
30        }
31        // We need to set this cookie early, as the login URL will only be
32        // requested during rendering. This also ensures that the nonce stays
33        // valid for only exactly one request.
34        // Note: This would probably be better in the session, but I couldn't
35        // get that to work.
36        list($prev_nonce, $mac) = explode(';', $_COOKIE['authdiscourse_nonce']);
37        if (!empty($mac) && hash_equals(hash_hmac('sha256', $prev_nonce, $this->sso_secret), $mac))
38            $this->prev_nonce = $prev_nonce;
39        $this->nonce = base64_encode(random_bytes(18));
40        setcookie('authdiscourse_nonce', $this->nonce.';'.hash_hmac('sha256', $this->nonce, $this->sso_secret), 0, "", "", ($conf['securecookie'] && is_ssl()), true);
41    }
42
43    public function logOff() {
44        @session_start();
45        session_destroy();
46    }
47
48    public function trustExternal($user, $pass, $sticky=false) {
49        global $USERINFO;
50        // We don't use the login form, so $user and $pass will never be set.
51
52        if (empty($_SESSION['authdiscourse_login'])) {
53            if (!$this->checkSSO()) {
54                return false;
55            }
56        }
57
58        // User is already logged-in or successfully authenticated now.
59        $login = $_SESSION['authdiscourse_login'];
60
61        $USERINFO['name'] = $login['name'];
62        $USERINFO['mail'] = $login['email'];
63        $groups = explode(',', $login['groups']);
64        $groups[] = 'user';
65        if ($login['admin'] == 'true') $groups[] = 'admin';
66        if ($login['moderator'] == 'true') $groups[] = 'moderator';
67        $USERINFO['grps'] = $groups;
68
69        $_SERVER['REMOTE_USER']                = $login['username'];
70        $_SESSION[DOKU_COOKIE]['auth']['user'] = $login['username'];
71        $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
72
73        return true;
74    }
75
76    // Checks SSO data after redirect from the SSO server.
77    private function checkSSO() {
78        // Are we returning from the SSO server?
79        if (!empty($_GET) && isset($_GET['sso'])){
80            @session_start();
81            $sso = urldecode($_GET['sso']);
82            $sig = $_GET['sig'];
83
84            // validate sso
85            $new_sig = hash_hmac('sha256', $sso, $this->sso_secret);
86            if (!hash_equals(hash_hmac('sha256', $sso, $this->sso_secret), $sig)) {
87                msg($this->getLang('sso_failed'), -1);
88                return false;
89            }
90
91            $query = array();
92            parse_str(base64_decode($sso), $query);
93
94            // verify nonce with generated nonce
95            if ($query['nonce'] !== $this->prev_nonce) {
96                msg($this->getLang('sso_failed'), -1);
97                return false;
98            }
99
100            msg($this->getLang('sso_success'), 1);
101
102            // login user
103            $_SESSION['authdiscourse_login'] = $query;
104            return true;
105        }
106        return false;
107    }
108
109    // Returns the external SSO login URL.
110    public function getLoginURL() {
111        if (empty($this->login_url))
112            $this->login_url =  $this->generateLoginURL();
113        return $this->login_url;
114    }
115
116    // Generates a URL to the SSO server.
117    private function generateLoginURL() {
118        $payload =  base64_encode(http_build_query(array(
119            'nonce' => $this->nonce,
120            'return_sso_url' => "https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]",
121        )));
122        $request = array(
123            'sso' => $payload,
124            'sig' => hash_hmac('sha256', $payload, $this->sso_secret),
125        );
126        return $this->sso_url.'?'.http_build_query($request);
127    }
128}
129