@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3/**
4 * Abstract adapter for OAuth1 providers.
5 */
6abstract class PhutilOAuth1AuthAdapter extends PhutilAuthAdapter {
7
8 private $consumerKey;
9 private $consumerSecret;
10 private $token;
11 private $tokenSecret;
12 private $verifier;
13 private $handshakeData;
14 private $callbackURI;
15 private $privateKey;
16
17 public function setPrivateKey(PhutilOpaqueEnvelope $private_key) {
18 $this->privateKey = $private_key;
19 return $this;
20 }
21
22 public function getPrivateKey() {
23 return $this->privateKey;
24 }
25
26 public function setCallbackURI($callback_uri) {
27 $this->callbackURI = $callback_uri;
28 return $this;
29 }
30
31 public function getCallbackURI() {
32 return $this->callbackURI;
33 }
34
35 public function setVerifier($verifier) {
36 $this->verifier = $verifier;
37 return $this;
38 }
39
40 public function getVerifier() {
41 return $this->verifier;
42 }
43
44 public function setConsumerSecret(PhutilOpaqueEnvelope $consumer_secret) {
45 $this->consumerSecret = $consumer_secret;
46 return $this;
47 }
48
49 public function getConsumerSecret() {
50 return $this->consumerSecret;
51 }
52
53 public function setConsumerKey($consumer_key) {
54 $this->consumerKey = $consumer_key;
55 return $this;
56 }
57
58 public function getConsumerKey() {
59 return $this->consumerKey;
60 }
61
62 public function setTokenSecret($token_secret) {
63 $this->tokenSecret = $token_secret;
64 return $this;
65 }
66
67 public function getTokenSecret() {
68 return $this->tokenSecret;
69 }
70
71 public function setToken($token) {
72 $this->token = $token;
73 return $this;
74 }
75
76 public function getToken() {
77 return $this->token;
78 }
79
80 protected function getHandshakeData() {
81 if ($this->handshakeData === null) {
82 $this->finishOAuthHandshake();
83 }
84 return $this->handshakeData;
85 }
86
87 abstract protected function getRequestTokenURI();
88 abstract protected function getAuthorizeTokenURI();
89 abstract protected function getValidateTokenURI();
90
91 protected function getSignatureMethod() {
92 return 'HMAC-SHA1';
93 }
94
95 public function getContentSecurityPolicyFormActions() {
96 return array(
97 $this->getAuthorizeTokenURI(),
98 );
99 }
100
101 protected function newOAuth1Future($uri, $data = array()) {
102 $future = id(new PhutilOAuth1Future($uri, $data))
103 ->addHeader('User-Agent',
104 PhabricatorEnv::getEnvConfig('phabricator.base-uri'))
105 ->setMethod('POST')
106 ->setSignatureMethod($this->getSignatureMethod());
107
108 $consumer_key = $this->getConsumerKey();
109 if (phutil_nonempty_string($consumer_key)) {
110 $future->setConsumerKey($consumer_key);
111 } else {
112 throw new Exception(
113 pht(
114 '%s is required!',
115 'setConsumerKey()'));
116 }
117
118 $consumer_secret = $this->getConsumerSecret();
119 if ($consumer_secret) {
120 $future->setConsumerSecret($consumer_secret);
121 }
122
123 if (phutil_nonempty_string($this->getToken())) {
124 $future->setToken($this->getToken());
125 }
126
127 if (phutil_nonempty_string($this->getTokenSecret())) {
128 $future->setTokenSecret($this->getTokenSecret());
129 }
130
131 if ($this->getPrivateKey()) {
132 $future->setPrivateKey($this->getPrivateKey());
133 }
134
135 return $future;
136 }
137
138 public function getClientRedirectURI() {
139 $request_token_uri = $this->getRequestTokenURI();
140
141 $future = $this->newOAuth1Future($request_token_uri);
142 if (phutil_nonempty_string($this->getCallbackURI())) {
143 $future->setCallbackURI($this->getCallbackURI());
144 }
145
146 list($body) = $future->resolvex();
147 $data = id(new PhutilQueryStringParser())->parseQueryString($body);
148
149 // NOTE: Per the spec, this value MUST be the string 'true'.
150 $confirmed = idx($data, 'oauth_callback_confirmed');
151 if ($confirmed !== 'true') {
152 throw new Exception(
153 pht("Expected '%s' to be '%s'!", 'oauth_callback_confirmed', 'true'));
154 }
155
156 $this->readTokenAndTokenSecret($data);
157
158 $authorize_token_uri = new PhutilURI($this->getAuthorizeTokenURI());
159 $authorize_token_uri->replaceQueryParam('oauth_token', $this->getToken());
160
161 return phutil_string_cast($authorize_token_uri);
162 }
163
164 protected function finishOAuthHandshake() {
165 $this->willFinishOAuthHandshake();
166
167 if (!$this->getToken()) {
168 throw new Exception(pht('Expected token to finish OAuth handshake!'));
169 }
170 if (!$this->getVerifier()) {
171 throw new Exception(pht('Expected verifier to finish OAuth handshake!'));
172 }
173
174 $validate_uri = $this->getValidateTokenURI();
175 $params = array(
176 'oauth_verifier' => $this->getVerifier(),
177 );
178
179 list($body) = $this->newOAuth1Future($validate_uri, $params)->resolvex();
180 $data = id(new PhutilQueryStringParser())->parseQueryString($body);
181
182 $this->readTokenAndTokenSecret($data);
183
184 $this->handshakeData = $data;
185 }
186
187 private function readTokenAndTokenSecret(array $data) {
188 $token = idx($data, 'oauth_token');
189 if (!$token) {
190 throw new Exception(pht("Expected '%s' in response!", 'oauth_token'));
191 }
192
193 $token_secret = idx($data, 'oauth_token_secret');
194 if (!$token_secret) {
195 throw new Exception(
196 pht("Expected '%s' in response!", 'oauth_token_secret'));
197 }
198
199 $this->setToken($token);
200 $this->setTokenSecret($token_secret);
201
202 return $this;
203 }
204
205 /**
206 * Hook that allows subclasses to take actions before the OAuth handshake
207 * is completed.
208 */
209 protected function willFinishOAuthHandshake() {
210 return;
211 }
212
213}