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