*/ class auth_plugin_authdiscourse extends DokuWiki_Auth_Plugin { private $sso_secret, $sso_url; private $login_url; private $nonce, $prev_nonce; public function __construct() { parent::__construct(); $this->success = true; $this->cando['external'] = true; $this->cando['logoff'] = true; global $conf; $cfg = $conf['plugin']['authdiscourse']; if (empty($cfg['sso_secret']) || empty($cfg['sso_url'])) { $this->success = false; } else { $this->sso_secret = $cfg['sso_secret']; $this->sso_url = $cfg['sso_url']; } // We need to set this cookie early, as the login URL will only be // requested during rendering. This also ensures that the nonce stays // valid for only exactly one request. // Note: This would probably be better in the session, but I couldn't // get that to work. list($prev_nonce, $mac) = explode(';', $_COOKIE['authdiscourse_nonce']); if (!empty($mac) && hash_equals(hash_hmac('sha256', $prev_nonce, $this->sso_secret), $mac)) $this->prev_nonce = $prev_nonce; $this->nonce = base64_encode(random_bytes(18)); setcookie('authdiscourse_nonce', $this->nonce.';'.hash_hmac('sha256', $this->nonce, $this->sso_secret), 0, "", "", ($conf['securecookie'] && is_ssl()), true); } public function logOff() { @session_start(); session_destroy(); } public function trustExternal($user, $pass, $sticky=false) { global $USERINFO; // We don't use the login form, so $user and $pass will never be set. if (empty($_SESSION['authdiscourse_login'])) { if (!$this->checkSSO()) { return false; } } // User is already logged-in or successfully authenticated now. $login = $_SESSION['authdiscourse_login']; $USERINFO['name'] = $login['name']; $USERINFO['mail'] = $login['email']; $groups = explode(',', $login['groups']); $groups[] = 'user'; if ($login['admin'] == 'true') $groups[] = 'admin'; if ($login['moderator'] == 'true') $groups[] = 'moderator'; $USERINFO['grps'] = $groups; $_SERVER['REMOTE_USER'] = $login['username']; $_SESSION[DOKU_COOKIE]['auth']['user'] = $login['username']; $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO; return true; } // Checks SSO data after redirect from the SSO server. private function checkSSO() { // Are we returning from the SSO server? if (!empty($_GET) && isset($_GET['sso'])){ @session_start(); $sso = urldecode($_GET['sso']); $sig = $_GET['sig']; // validate sso $new_sig = hash_hmac('sha256', $sso, $this->sso_secret); if (!hash_equals(hash_hmac('sha256', $sso, $this->sso_secret), $sig)) { msg($this->getLang('sso_failed'), -1); return false; } $query = array(); parse_str(base64_decode($sso), $query); // verify nonce with generated nonce if ($query['nonce'] !== $this->prev_nonce) { msg($this->getLang('sso_failed'), -1); return false; } msg($this->getLang('sso_success'), 1); // login user $_SESSION['authdiscourse_login'] = $query; return true; } return false; } // Returns the external SSO login URL. public function getLoginURL() { if (empty($this->login_url)) $this->login_url = $this->generateLoginURL(); return $this->login_url; } // Generates a URL to the SSO server. private function generateLoginURL() { $payload = base64_encode(http_build_query(array( 'nonce' => $this->nonce, 'return_sso_url' => "https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]", ))); $request = array( 'sso' => $payload, 'sig' => hash_hmac('sha256', $payload, $this->sso_secret), ); return $this->sso_url.'?'.http_build_query($request); } }