@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
3final class PhabricatorOAuthServerTokenController
4 extends PhabricatorOAuthServerController {
5
6 public function shouldRequireLogin() {
7 return false;
8 }
9
10 public function shouldAllowRestrictedParameter($parameter_name) {
11 if ($parameter_name == 'code') {
12 return true;
13 }
14 return parent::shouldAllowRestrictedParameter($parameter_name);
15 }
16
17 public function handleRequest(AphrontRequest $request) {
18 $grant_type = $request->getStr('grant_type');
19 $code = $request->getStr('code');
20 $redirect_uri = $request->getStr('redirect_uri');
21 $response = new PhabricatorOAuthResponse();
22 $server = new PhabricatorOAuthServer();
23
24 $client_id_parameter = $request->getStr('client_id');
25 $client_id_header = idx($_SERVER, 'PHP_AUTH_USER');
26 if (phutil_nonempty_string($client_id_parameter) &&
27 phutil_nonempty_string($client_id_header)) {
28 if ($client_id_parameter !== $client_id_header) {
29 throw new Exception(
30 pht(
31 'Request included a client_id parameter and an "Authorization" '.
32 'header with a username, but the values "%s" and "%s") disagree. '.
33 'The values must match.',
34 $client_id_parameter,
35 $client_id_header));
36 }
37 }
38
39 $client_secret_parameter = $request->getStr('client_secret');
40 $client_secret_header = idx($_SERVER, 'PHP_AUTH_PW');
41 if (strlen($client_secret_parameter)) {
42 // If the `client_secret` parameter is present, prefer parameters.
43 $client_phid = $client_id_parameter;
44 $client_secret = $client_secret_parameter;
45 } else {
46 // Otherwise, read values from the "Authorization" header.
47 $client_phid = $client_id_header;
48 $client_secret = $client_secret_header;
49 }
50
51 if ($grant_type != 'authorization_code') {
52 $response->setError('unsupported_grant_type');
53 $response->setErrorDescription(
54 pht(
55 'Only %s %s is supported.',
56 'grant_type',
57 'authorization_code'));
58 return $response;
59 }
60
61 if (!$code) {
62 $response->setError('invalid_request');
63 $response->setErrorDescription(pht('Required parameter code missing.'));
64 return $response;
65 }
66
67 if (!$client_phid) {
68 $response->setError('invalid_request');
69 $response->setErrorDescription(
70 pht(
71 'Required parameter %s missing.',
72 'client_id'));
73 return $response;
74 }
75
76 if (!$client_secret) {
77 $response->setError('invalid_request');
78 $response->setErrorDescription(
79 pht(
80 'Required parameter %s missing.',
81 'client_secret'));
82 return $response;
83 }
84
85 // one giant try / catch around all the exciting database stuff so we
86 // can return a 'server_error' response if something goes wrong!
87 try {
88 $auth_code = id(new PhabricatorOAuthServerAuthorizationCode())
89 ->loadOneWhere('code = %s',
90 $code);
91 if (!$auth_code) {
92 $response->setError('invalid_grant');
93 $response->setErrorDescription(
94 pht(
95 'Authorization code %s not found.',
96 $code));
97 return $response;
98 }
99
100 // if we have an auth code redirect URI, there must be a redirect_uri
101 // in the request and it must match the auth code redirect uri *exactly*
102 $auth_code_redirect_uri = $auth_code->getRedirectURI();
103 if ($auth_code_redirect_uri) {
104 $auth_code_redirect_uri = new PhutilURI($auth_code_redirect_uri);
105 $redirect_uri = new PhutilURI($redirect_uri);
106 if (!$redirect_uri->getDomain() ||
107 $redirect_uri != $auth_code_redirect_uri) {
108 $response->setError('invalid_grant');
109 $response->setErrorDescription(
110 pht(
111 'Redirect URI in request must exactly match redirect URI '.
112 'from authorization code.'));
113 return $response;
114 }
115 } else if ($redirect_uri) {
116 $response->setError('invalid_grant');
117 $response->setErrorDescription(
118 pht(
119 'Redirect URI in request and no redirect URI in authorization '.
120 'code. The two must exactly match.'));
121 return $response;
122 }
123
124 $client = id(new PhabricatorOAuthServerClient())
125 ->loadOneWhere('phid = %s', $client_phid);
126 if (!$client) {
127 $response->setError('invalid_client');
128 $response->setErrorDescription(
129 pht(
130 'Client with %s %s not found.',
131 'client_id',
132 $client_phid));
133 return $response;
134 }
135
136 if ($client->getIsDisabled()) {
137 $response->setError('invalid_client');
138 $response->setErrorDescription(
139 pht(
140 'OAuth application "%s" has been disabled.',
141 $client->getName()));
142
143 return $response;
144 }
145
146 $server->setClient($client);
147
148 $user_phid = $auth_code->getUserPHID();
149 $user = id(new PhabricatorUser())
150 ->loadOneWhere('phid = %s', $user_phid);
151 if (!$user) {
152 $response->setError('invalid_grant');
153 $response->setErrorDescription(
154 pht(
155 'User with PHID %s not found.',
156 $user_phid));
157 return $response;
158 }
159 $server->setUser($user);
160
161 $test_code = new PhabricatorOAuthServerAuthorizationCode();
162 $test_code->setClientSecret($client_secret);
163 $test_code->setClientPHID($client_phid);
164 $is_good_code = $server->validateAuthorizationCode(
165 $auth_code,
166 $test_code);
167 if (!$is_good_code) {
168 $response->setError('invalid_grant');
169 $response->setErrorDescription(
170 pht(
171 'Invalid authorization code %s.',
172 $code));
173 return $response;
174 }
175
176 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
177 $access_token = $server->generateAccessToken();
178 $auth_code->delete();
179 unset($unguarded);
180 $result = array(
181 'access_token' => $access_token->getToken(),
182 'token_type' => 'Bearer',
183 );
184 return $response->setContent($result);
185 } catch (Exception $e) {
186 $response->setError('server_error');
187 $response->setErrorDescription(
188 pht(
189 'The authorization server encountered an unexpected condition '.
190 'which prevented it from fulfilling the request.'));
191 return $response;
192 }
193 }
194
195}