Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/auth/adapter/PhutilOAuthAuthAdapter.php
12256 views
1
<?php
2
3
/**
4
* Abstract adapter for OAuth2 providers.
5
*/
6
abstract class PhutilOAuthAuthAdapter extends PhutilAuthAdapter {
7
8
private $clientID;
9
private $clientSecret;
10
private $redirectURI;
11
private $scope;
12
private $state;
13
private $code;
14
15
private $accessTokenData;
16
private $oauthAccountData;
17
18
abstract protected function getAuthenticateBaseURI();
19
abstract protected function getTokenBaseURI();
20
abstract protected function loadOAuthAccountData();
21
22
public function getAuthenticateURI() {
23
$params = array(
24
'client_id' => $this->getClientID(),
25
'scope' => $this->getScope(),
26
'redirect_uri' => $this->getRedirectURI(),
27
'state' => $this->getState(),
28
) + $this->getExtraAuthenticateParameters();
29
30
$uri = new PhutilURI($this->getAuthenticateBaseURI(), $params);
31
32
return phutil_string_cast($uri);
33
}
34
35
public function getAdapterType() {
36
$this_class = get_class($this);
37
$type_name = str_replace('PhutilAuthAdapterOAuth', '', $this_class);
38
return strtolower($type_name);
39
}
40
41
public function setState($state) {
42
$this->state = $state;
43
return $this;
44
}
45
46
public function getState() {
47
return $this->state;
48
}
49
50
public function setCode($code) {
51
$this->code = $code;
52
return $this;
53
}
54
55
public function getCode() {
56
return $this->code;
57
}
58
59
public function setRedirectURI($redirect_uri) {
60
$this->redirectURI = $redirect_uri;
61
return $this;
62
}
63
64
public function getRedirectURI() {
65
return $this->redirectURI;
66
}
67
68
public function getExtraAuthenticateParameters() {
69
return array();
70
}
71
72
public function getExtraTokenParameters() {
73
return array();
74
}
75
76
public function getExtraRefreshParameters() {
77
return array();
78
}
79
80
public function setScope($scope) {
81
$this->scope = $scope;
82
return $this;
83
}
84
85
public function getScope() {
86
return $this->scope;
87
}
88
89
public function setClientSecret(PhutilOpaqueEnvelope $client_secret) {
90
$this->clientSecret = $client_secret;
91
return $this;
92
}
93
94
public function getClientSecret() {
95
return $this->clientSecret;
96
}
97
98
public function setClientID($client_id) {
99
$this->clientID = $client_id;
100
return $this;
101
}
102
103
public function getClientID() {
104
return $this->clientID;
105
}
106
107
public function getAccessToken() {
108
return $this->getAccessTokenData('access_token');
109
}
110
111
public function getAccessTokenExpires() {
112
return $this->getAccessTokenData('expires_epoch');
113
}
114
115
public function getRefreshToken() {
116
return $this->getAccessTokenData('refresh_token');
117
}
118
119
protected function getAccessTokenData($key, $default = null) {
120
if ($this->accessTokenData === null) {
121
$this->accessTokenData = $this->loadAccessTokenData();
122
}
123
124
return idx($this->accessTokenData, $key, $default);
125
}
126
127
public function supportsTokenRefresh() {
128
return false;
129
}
130
131
public function refreshAccessToken($refresh_token) {
132
$this->accessTokenData = $this->loadRefreshTokenData($refresh_token);
133
return $this;
134
}
135
136
protected function loadRefreshTokenData($refresh_token) {
137
$params = array(
138
'refresh_token' => $refresh_token,
139
) + $this->getExtraRefreshParameters();
140
141
// NOTE: Make sure we return the refresh_token so that subsequent
142
// calls to getRefreshToken() return it; providers normally do not echo
143
// it back for token refresh requests.
144
145
return $this->makeTokenRequest($params) + array(
146
'refresh_token' => $refresh_token,
147
);
148
}
149
150
protected function loadAccessTokenData() {
151
$code = $this->getCode();
152
if (!$code) {
153
throw new PhutilInvalidStateException('setCode');
154
}
155
156
$params = array(
157
'code' => $this->getCode(),
158
) + $this->getExtraTokenParameters();
159
160
return $this->makeTokenRequest($params);
161
}
162
163
private function makeTokenRequest(array $params) {
164
$uri = $this->getTokenBaseURI();
165
$query_data = array(
166
'client_id' => $this->getClientID(),
167
'client_secret' => $this->getClientSecret()->openEnvelope(),
168
'redirect_uri' => $this->getRedirectURI(),
169
) + $params;
170
171
$future = new HTTPSFuture($uri, $query_data);
172
$future->setMethod('POST');
173
list($body) = $future->resolvex();
174
175
$data = $this->readAccessTokenResponse($body);
176
177
if (isset($data['expires_in'])) {
178
$data['expires_epoch'] = $data['expires_in'];
179
} else if (isset($data['expires'])) {
180
$data['expires_epoch'] = $data['expires'];
181
}
182
183
// If we got some "expires" value back, interpret it as an epoch timestamp
184
// if it's after the year 2010 and as a relative number of seconds
185
// otherwise.
186
if (isset($data['expires_epoch'])) {
187
if ($data['expires_epoch'] < (60 * 60 * 24 * 365 * 40)) {
188
$data['expires_epoch'] += time();
189
}
190
}
191
192
if (isset($data['error'])) {
193
throw new Exception(pht('Access token error: %s', $data['error']));
194
}
195
196
return $data;
197
}
198
199
protected function readAccessTokenResponse($body) {
200
// NOTE: Most providers either return JSON or HTTP query strings, so try
201
// both mechanisms. If your provider does something else, override this
202
// method.
203
204
$data = json_decode($body, true);
205
206
if (!is_array($data)) {
207
$data = array();
208
parse_str($body, $data);
209
}
210
211
if (empty($data['access_token']) &&
212
empty($data['error'])) {
213
throw new Exception(
214
pht('Failed to decode OAuth access token response: %s', $body));
215
}
216
217
return $data;
218
}
219
220
protected function getOAuthAccountData($key, $default = null) {
221
if ($this->oauthAccountData === null) {
222
$this->oauthAccountData = $this->loadOAuthAccountData();
223
}
224
225
return idx($this->oauthAccountData, $key, $default);
226
}
227
228
}
229
230