Correct Aphlict websocket URI construction after PHP8 compatibility changes
[phabricator.git] / src / applications / auth / adapter / PhutilOAuth1AuthAdapter.php
blob08cc65a235d7325b9e8f41c45e990fad180a2e59
1 <?php
3 /**
4 * Abstract adapter for OAuth1 providers.
5 */
6 abstract class PhutilOAuth1AuthAdapter extends PhutilAuthAdapter {
8 private $consumerKey;
9 private $consumerSecret;
10 private $token;
11 private $tokenSecret;
12 private $verifier;
13 private $handshakeData;
14 private $callbackURI;
15 private $privateKey;
17 public function setPrivateKey(PhutilOpaqueEnvelope $private_key) {
18 $this->privateKey = $private_key;
19 return $this;
22 public function getPrivateKey() {
23 return $this->privateKey;
26 public function setCallbackURI($callback_uri) {
27 $this->callbackURI = $callback_uri;
28 return $this;
31 public function getCallbackURI() {
32 return $this->callbackURI;
35 public function setVerifier($verifier) {
36 $this->verifier = $verifier;
37 return $this;
40 public function getVerifier() {
41 return $this->verifier;
44 public function setConsumerSecret(PhutilOpaqueEnvelope $consumer_secret) {
45 $this->consumerSecret = $consumer_secret;
46 return $this;
49 public function getConsumerSecret() {
50 return $this->consumerSecret;
53 public function setConsumerKey($consumer_key) {
54 $this->consumerKey = $consumer_key;
55 return $this;
58 public function getConsumerKey() {
59 return $this->consumerKey;
62 public function setTokenSecret($token_secret) {
63 $this->tokenSecret = $token_secret;
64 return $this;
67 public function getTokenSecret() {
68 return $this->tokenSecret;
71 public function setToken($token) {
72 $this->token = $token;
73 return $this;
76 public function getToken() {
77 return $this->token;
80 protected function getHandshakeData() {
81 if ($this->handshakeData === null) {
82 $this->finishOAuthHandshake();
84 return $this->handshakeData;
87 abstract protected function getRequestTokenURI();
88 abstract protected function getAuthorizeTokenURI();
89 abstract protected function getValidateTokenURI();
91 protected function getSignatureMethod() {
92 return 'HMAC-SHA1';
95 public function getContentSecurityPolicyFormActions() {
96 return array(
97 $this->getAuthorizeTokenURI(),
101 protected function newOAuth1Future($uri, $data = array()) {
102 $future = id(new PhutilOAuth1Future($uri, $data))
103 ->setMethod('POST')
104 ->setSignatureMethod($this->getSignatureMethod());
106 $consumer_key = $this->getConsumerKey();
107 if (strlen($consumer_key)) {
108 $future->setConsumerKey($consumer_key);
109 } else {
110 throw new Exception(
111 pht(
112 '%s is required!',
113 'setConsumerKey()'));
116 $consumer_secret = $this->getConsumerSecret();
117 if ($consumer_secret) {
118 $future->setConsumerSecret($consumer_secret);
121 if (strlen($this->getToken())) {
122 $future->setToken($this->getToken());
125 if (strlen($this->getTokenSecret())) {
126 $future->setTokenSecret($this->getTokenSecret());
129 if ($this->getPrivateKey()) {
130 $future->setPrivateKey($this->getPrivateKey());
133 return $future;
136 public function getClientRedirectURI() {
137 $request_token_uri = $this->getRequestTokenURI();
139 $future = $this->newOAuth1Future($request_token_uri);
140 if (strlen($this->getCallbackURI())) {
141 $future->setCallbackURI($this->getCallbackURI());
144 list($body) = $future->resolvex();
145 $data = id(new PhutilQueryStringParser())->parseQueryString($body);
147 // NOTE: Per the spec, this value MUST be the string 'true'.
148 $confirmed = idx($data, 'oauth_callback_confirmed');
149 if ($confirmed !== 'true') {
150 throw new Exception(
151 pht("Expected '%s' to be '%s'!", 'oauth_callback_confirmed', 'true'));
154 $this->readTokenAndTokenSecret($data);
156 $authorize_token_uri = new PhutilURI($this->getAuthorizeTokenURI());
157 $authorize_token_uri->replaceQueryParam('oauth_token', $this->getToken());
159 return phutil_string_cast($authorize_token_uri);
162 protected function finishOAuthHandshake() {
163 $this->willFinishOAuthHandshake();
165 if (!$this->getToken()) {
166 throw new Exception(pht('Expected token to finish OAuth handshake!'));
168 if (!$this->getVerifier()) {
169 throw new Exception(pht('Expected verifier to finish OAuth handshake!'));
172 $validate_uri = $this->getValidateTokenURI();
173 $params = array(
174 'oauth_verifier' => $this->getVerifier(),
177 list($body) = $this->newOAuth1Future($validate_uri, $params)->resolvex();
178 $data = id(new PhutilQueryStringParser())->parseQueryString($body);
180 $this->readTokenAndTokenSecret($data);
182 $this->handshakeData = $data;
185 private function readTokenAndTokenSecret(array $data) {
186 $token = idx($data, 'oauth_token');
187 if (!$token) {
188 throw new Exception(pht("Expected '%s' in response!", 'oauth_token'));
191 $token_secret = idx($data, 'oauth_token_secret');
192 if (!$token_secret) {
193 throw new Exception(
194 pht("Expected '%s' in response!", 'oauth_token_secret'));
197 $this->setToken($token);
198 $this->setTokenSecret($token_secret);
200 return $this;
204 * Hook that allows subclasses to take actions before the OAuth handshake
205 * is completed.
207 protected function willFinishOAuthHandshake() {
208 return;