Path: blob/master/src/applications/auth/adapter/PhutilOAuthAuthAdapter.php
12256 views
<?php12/**3* Abstract adapter for OAuth2 providers.4*/5abstract class PhutilOAuthAuthAdapter extends PhutilAuthAdapter {67private $clientID;8private $clientSecret;9private $redirectURI;10private $scope;11private $state;12private $code;1314private $accessTokenData;15private $oauthAccountData;1617abstract protected function getAuthenticateBaseURI();18abstract protected function getTokenBaseURI();19abstract protected function loadOAuthAccountData();2021public function getAuthenticateURI() {22$params = array(23'client_id' => $this->getClientID(),24'scope' => $this->getScope(),25'redirect_uri' => $this->getRedirectURI(),26'state' => $this->getState(),27) + $this->getExtraAuthenticateParameters();2829$uri = new PhutilURI($this->getAuthenticateBaseURI(), $params);3031return phutil_string_cast($uri);32}3334public function getAdapterType() {35$this_class = get_class($this);36$type_name = str_replace('PhutilAuthAdapterOAuth', '', $this_class);37return strtolower($type_name);38}3940public function setState($state) {41$this->state = $state;42return $this;43}4445public function getState() {46return $this->state;47}4849public function setCode($code) {50$this->code = $code;51return $this;52}5354public function getCode() {55return $this->code;56}5758public function setRedirectURI($redirect_uri) {59$this->redirectURI = $redirect_uri;60return $this;61}6263public function getRedirectURI() {64return $this->redirectURI;65}6667public function getExtraAuthenticateParameters() {68return array();69}7071public function getExtraTokenParameters() {72return array();73}7475public function getExtraRefreshParameters() {76return array();77}7879public function setScope($scope) {80$this->scope = $scope;81return $this;82}8384public function getScope() {85return $this->scope;86}8788public function setClientSecret(PhutilOpaqueEnvelope $client_secret) {89$this->clientSecret = $client_secret;90return $this;91}9293public function getClientSecret() {94return $this->clientSecret;95}9697public function setClientID($client_id) {98$this->clientID = $client_id;99return $this;100}101102public function getClientID() {103return $this->clientID;104}105106public function getAccessToken() {107return $this->getAccessTokenData('access_token');108}109110public function getAccessTokenExpires() {111return $this->getAccessTokenData('expires_epoch');112}113114public function getRefreshToken() {115return $this->getAccessTokenData('refresh_token');116}117118protected function getAccessTokenData($key, $default = null) {119if ($this->accessTokenData === null) {120$this->accessTokenData = $this->loadAccessTokenData();121}122123return idx($this->accessTokenData, $key, $default);124}125126public function supportsTokenRefresh() {127return false;128}129130public function refreshAccessToken($refresh_token) {131$this->accessTokenData = $this->loadRefreshTokenData($refresh_token);132return $this;133}134135protected function loadRefreshTokenData($refresh_token) {136$params = array(137'refresh_token' => $refresh_token,138) + $this->getExtraRefreshParameters();139140// NOTE: Make sure we return the refresh_token so that subsequent141// calls to getRefreshToken() return it; providers normally do not echo142// it back for token refresh requests.143144return $this->makeTokenRequest($params) + array(145'refresh_token' => $refresh_token,146);147}148149protected function loadAccessTokenData() {150$code = $this->getCode();151if (!$code) {152throw new PhutilInvalidStateException('setCode');153}154155$params = array(156'code' => $this->getCode(),157) + $this->getExtraTokenParameters();158159return $this->makeTokenRequest($params);160}161162private function makeTokenRequest(array $params) {163$uri = $this->getTokenBaseURI();164$query_data = array(165'client_id' => $this->getClientID(),166'client_secret' => $this->getClientSecret()->openEnvelope(),167'redirect_uri' => $this->getRedirectURI(),168) + $params;169170$future = new HTTPSFuture($uri, $query_data);171$future->setMethod('POST');172list($body) = $future->resolvex();173174$data = $this->readAccessTokenResponse($body);175176if (isset($data['expires_in'])) {177$data['expires_epoch'] = $data['expires_in'];178} else if (isset($data['expires'])) {179$data['expires_epoch'] = $data['expires'];180}181182// If we got some "expires" value back, interpret it as an epoch timestamp183// if it's after the year 2010 and as a relative number of seconds184// otherwise.185if (isset($data['expires_epoch'])) {186if ($data['expires_epoch'] < (60 * 60 * 24 * 365 * 40)) {187$data['expires_epoch'] += time();188}189}190191if (isset($data['error'])) {192throw new Exception(pht('Access token error: %s', $data['error']));193}194195return $data;196}197198protected function readAccessTokenResponse($body) {199// NOTE: Most providers either return JSON or HTTP query strings, so try200// both mechanisms. If your provider does something else, override this201// method.202203$data = json_decode($body, true);204205if (!is_array($data)) {206$data = array();207parse_str($body, $data);208}209210if (empty($data['access_token']) &&211empty($data['error'])) {212throw new Exception(213pht('Failed to decode OAuth access token response: %s', $body));214}215216return $data;217}218219protected function getOAuthAccountData($key, $default = null) {220if ($this->oauthAccountData === null) {221$this->oauthAccountData = $this->loadOAuthAccountData();222}223224return idx($this->oauthAccountData, $key, $default);225}226227}228229230