url = $url; $this->broker = $broker; $this->secret = $secret; $this->cookie_lifetime = $cookie_lifetime; if (isset($_COOKIE[$this->getCookieName()])) $this->token = $_COOKIE[$this->getCookieName()]; } /** * Get the cookie name. * * Note: Using the broker name in the cookie name. * This resolves issues when multiple brokers are on the same domain. * * @return string */ protected function getCookieName() { return 'sso_token_' . preg_replace('/[_\W]+/', '_', strtolower($this->broker)); } /** * Generate session id from session key * * @return string */ protected function getSessionId() { if (!isset($this->token)) return null; $checksum = hash('sha256', 'session' . $this->token . $this->secret); return "SSO-{$this->broker}-{$this->token}-$checksum"; } /** * Generate session token */ public function generateToken() { if (isset($this->token)) return; $this->token = base_convert(md5(uniqid(rand(), true)), 16, 36); setcookie($this->getCookieName(), $this->token, time() + $this->cookie_lifetime, '/'); } /** * Clears session token */ public function clearToken() { setcookie($this->getCookieName(), null, 1, '/'); $this->token = null; } /** * Check if we have an SSO token. * * @return boolean */ public function isAttached() { return isset($this->token); } /** * Get URL to attach session at SSO server. * * @param array $params * @return string */ public function getAttachUrl($params = []) { $this->generateToken(); $data = [ 'command' => 'attach', 'broker' => $this->broker, 'token' => $this->token, 'checksum' => hash('sha256', 'attach' . $this->token . $this->secret) ] + $_GET; return $this->url . "?" . http_build_query($data + $params); } /** * Attach our session to the user's session on the SSO server. * * @param string|true $returnUrl The URL the client should be returned to after attaching */ public function attach($returnUrl = null) { if ($this->isAttached()) return; if ($returnUrl === true) { $protocol = !empty($_SERVER['HTTPS']) ? 'https://' : 'http://'; $returnUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; } $params = ['return_url' => $returnUrl]; $url = $this->getAttachUrl($params); header("Location: $url", true, 307); echo "You're redirected to $url"; exit(); } /** * Get the request url for a command * * @param string $command * @param array $params Query parameters * @return string */ protected function getRequestUrl($command, $params = []) { $params['command'] = $command; return $this->url . '?' . http_build_query($params); } /** * Execute on SSO server. * * @param string $method HTTP method: 'GET', 'POST', 'DELETE' * @param string $command Command * @param array|string $data Query or post parameters * @return array|object */ protected function request($method, $command, $data = null) { if (!$this->isAttached()) { throw new NotAttachedException('No token'); } $url = $this->getRequestUrl($command, !$data || $method === 'POST' ? [] : $data); $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: application/json', 'Authorization: Bearer '. $this->getSessionID()]); if ($method === 'POST' && !empty($data)) { $post = is_string($data) ? $data : http_build_query($data); curl_setopt($ch, CURLOPT_POSTFIELDS, $post); } $response = curl_exec($ch); if (curl_errno($ch) != 0) { $message = 'Server request failed: ' . curl_error($ch); throw new Exception($message); } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); list($contentType) = explode(';', curl_getinfo($ch, CURLINFO_CONTENT_TYPE)); if ($contentType != 'application/json') { $message = 'Expected application/json response, got ' . $contentType; throw new Exception($message); } $data = json_decode($response, true); if ($httpCode == 403) { $this->clearToken(); throw new NotAttachedException($data['error'] ?: $response, $httpCode); } if ($httpCode >= 400) throw new Exception($data['error'] ?: $response, $httpCode); return $data; } /** * Log the client in at the SSO server. * * Only brokers marked trused can collect and send the user's credentials. Other brokers should omit $username and * $password. * * @param string $username * @param string $password * @return array user info * @throws Exception if login fails eg due to incorrect credentials */ public function login($username = null, $password = null) { if (!isset($username) && isset($_POST['username'])) $username = $_POST['username']; if (!isset($password) && isset($_POST['password'])) $password = $_POST['password']; $result = $this->request('POST', 'login', compact('username', 'password')); $this->userinfo = $result; return $this->userinfo; } /** * Logout at sso server. */ public function logout() { $this->request('POST', 'logout', 'logout'); } /** * Get user information. * * @return object|null */ public function getUserInfo() { if (!isset($this->userinfo)) { $this->userinfo = $this->request('GET', 'userInfo'); } return $this->userinfo; } /** * Magic method to do arbitrary request * * @param string $fn * @param array $args * @return mixed */ public function __call($fn, $args) { $sentence = strtolower(preg_replace('/([a-z0-9])([A-Z])/', '$1 $2', $fn)); $parts = explode(' ', $sentence); $method = count($parts) > 1 && in_array(strtoupper($parts[0]), ['GET', 'DELETE']) ? strtoupper(array_shift($parts)) : 'POST'; $command = join('-', $parts); return $this->request($method, $command, $args); } }