@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
fork

Configure Feed

Select the types of activity you want to include in your feed.

at upstream/main 285 lines 8.4 kB view raw
1<?php 2 3/** 4 * Implements core OAuth 2.0 Server logic. 5 * 6 * This class should be used behind business logic that parses input to 7 * determine pertinent @{class:PhabricatorUser} $user, 8 * @{class:PhabricatorOAuthServerClient} $client(s), 9 * @{class:PhabricatorOAuthServerAuthorizationCode} $code(s), and. 10 * @{class:PhabricatorOAuthServerAccessToken} $token(s). 11 * 12 * For an OAuth 2.0 server, there are two main steps: 13 * 14 * 1) Authorization - the user authorizes a given client to access the data 15 * the OAuth 2.0 server protects. Once this is achieved / if it has 16 * been achived already, the OAuth server sends the client an authorization 17 * code. 18 * 2) Access Token - the client should send the authorization code received in 19 * step 1 along with its id and secret to the OAuth server to receive an 20 * access token. This access token can later be used to access Phabricator 21 * data on behalf of the user. 22 * 23 * @task auth Authorizing @{class:PhabricatorOAuthServerClient}s and 24 * generating @{class:PhabricatorOAuthServerAuthorizationCode}s 25 * @task token Validating @{class:PhabricatorOAuthServerAuthorizationCode}s 26 * and generating @{class:PhabricatorOAuthServerAccessToken}s 27 * @task internal Internals 28 */ 29final class PhabricatorOAuthServer extends Phobject { 30 31 const AUTHORIZATION_CODE_TIMEOUT = 300; 32 33 private $user; 34 private $client; 35 36 private function getUser() { 37 if (!$this->user) { 38 throw new PhutilInvalidStateException('setUser'); 39 } 40 return $this->user; 41 } 42 43 public function setUser(PhabricatorUser $user) { 44 $this->user = $user; 45 return $this; 46 } 47 48 private function getClient() { 49 if (!$this->client) { 50 throw new PhutilInvalidStateException('setClient'); 51 } 52 return $this->client; 53 } 54 55 public function setClient(PhabricatorOAuthServerClient $client) { 56 $this->client = $client; 57 return $this; 58 } 59 60 /** 61 * @task auth 62 * @return array<bool,PhabricatorOAuthClientAuthorization|null> Tuple of 63 * <bool hasAuthorized, ClientAuthorization or null> 64 */ 65 public function userHasAuthorizedClient(array $scope) { 66 67 $authorization = id(new PhabricatorOAuthClientAuthorization()) 68 ->loadOneWhere( 69 'userPHID = %s AND clientPHID = %s', 70 $this->getUser()->getPHID(), 71 $this->getClient()->getPHID()); 72 if (empty($authorization)) { 73 return array(false, null); 74 } 75 76 if ($scope) { 77 $missing_scope = array_diff_key($scope, $authorization->getScope()); 78 } else { 79 $missing_scope = false; 80 } 81 82 if ($missing_scope) { 83 return array(false, $authorization); 84 } 85 86 return array(true, $authorization); 87 } 88 89 /** 90 * @task auth 91 */ 92 public function authorizeClient(array $scope) { 93 $authorization = new PhabricatorOAuthClientAuthorization(); 94 $authorization->setUserPHID($this->getUser()->getPHID()); 95 $authorization->setClientPHID($this->getClient()->getPHID()); 96 $authorization->setScope($scope); 97 $authorization->save(); 98 99 return $authorization; 100 } 101 102 /** 103 * @task auth 104 */ 105 public function generateAuthorizationCode(PhutilURI $redirect_uri) { 106 107 $code = Filesystem::readRandomCharacters(32); 108 $client = $this->getClient(); 109 110 $authorization_code = new PhabricatorOAuthServerAuthorizationCode(); 111 $authorization_code->setCode($code); 112 $authorization_code->setClientPHID($client->getPHID()); 113 $authorization_code->setClientSecret($client->getSecret()); 114 $authorization_code->setUserPHID($this->getUser()->getPHID()); 115 $authorization_code->setRedirectURI((string)$redirect_uri); 116 $authorization_code->save(); 117 118 return $authorization_code; 119 } 120 121 /** 122 * @task token 123 */ 124 public function generateAccessToken() { 125 126 $token = Filesystem::readRandomCharacters(32); 127 128 $access_token = new PhabricatorOAuthServerAccessToken(); 129 $access_token->setToken($token); 130 $access_token->setUserPHID($this->getUser()->getPHID()); 131 $access_token->setClientPHID($this->getClient()->getPHID()); 132 $access_token->save(); 133 134 return $access_token; 135 } 136 137 /** 138 * @task token 139 */ 140 public function validateAuthorizationCode( 141 PhabricatorOAuthServerAuthorizationCode $test_code, 142 PhabricatorOAuthServerAuthorizationCode $valid_code) { 143 144 // check that all the meta data matches 145 if ($test_code->getClientPHID() != $valid_code->getClientPHID()) { 146 return false; 147 } 148 if ($test_code->getClientSecret() != $valid_code->getClientSecret()) { 149 return false; 150 } 151 152 // check that the authorization code hasn't timed out 153 $created_time = $test_code->getDateCreated(); 154 $must_be_used_by = $created_time + self::AUTHORIZATION_CODE_TIMEOUT; 155 return (time() < $must_be_used_by); 156 } 157 158 /** 159 * @task token 160 */ 161 public function authorizeToken( 162 PhabricatorOAuthServerAccessToken $token) { 163 164 $user_phid = $token->getUserPHID(); 165 $client_phid = $token->getClientPHID(); 166 167 $authorization = id(new PhabricatorOAuthClientAuthorizationQuery()) 168 ->setViewer(PhabricatorUser::getOmnipotentUser()) 169 ->withUserPHIDs(array($user_phid)) 170 ->withClientPHIDs(array($client_phid)) 171 ->executeOne(); 172 if (!$authorization) { 173 return null; 174 } 175 176 $application = $authorization->getClient(); 177 if ($application->getIsDisabled()) { 178 return null; 179 } 180 181 return $authorization; 182 } 183 184 public function validateRedirectURI($uri) { 185 try { 186 $this->assertValidRedirectURI($uri); 187 return true; 188 } catch (Exception $ex) { 189 return false; 190 } 191 } 192 193 /** 194 * See https://datatracker.ietf.org/doc/rfc6749/ section 3.1.2 195 * for details on what makes a given redirect URI "valid". 196 */ 197 public function assertValidRedirectURI($raw_uri) { 198 // This covers basics like reasonable formatting and the existence of a 199 // protocol. 200 PhabricatorEnv::requireValidRemoteURIForLink($raw_uri); 201 202 $uri = new PhutilURI($raw_uri); 203 204 $fragment = $uri->getFragment(); 205 if (strlen($fragment)) { 206 throw new Exception( 207 pht( 208 'OAuth application redirect URIs must not contain URI '. 209 'fragments, but the URI "%s" has a fragment ("%s").', 210 $raw_uri, 211 $fragment)); 212 } 213 214 $protocol = $uri->getProtocol(); 215 switch ($protocol) { 216 case 'http': 217 case 'https': 218 break; 219 default: 220 throw new Exception( 221 pht( 222 'OAuth application redirect URIs must only use the "http" or '. 223 '"https" protocols, but the URI "%s" uses the "%s" protocol.', 224 $raw_uri, 225 $protocol)); 226 } 227 } 228 229 /** 230 * If there's a URI specified in an OAuth request, it must be validated in 231 * its own right. Further, it must have the same domain, the same path, the 232 * same port, and (at least) the same query parameters as the primary URI. 233 */ 234 public function validateSecondaryRedirectURI( 235 PhutilURI $secondary_uri, 236 PhutilURI $primary_uri) { 237 238 // The secondary URI must be valid. 239 if (!$this->validateRedirectURI($secondary_uri)) { 240 return false; 241 } 242 243 // Both URIs must point at the same domain. 244 if ($secondary_uri->getDomain() != $primary_uri->getDomain()) { 245 return false; 246 } 247 248 // Both URIs must have the same path 249 if ($secondary_uri->getPath() != $primary_uri->getPath()) { 250 return false; 251 } 252 253 // Both URIs must have the same port 254 if ($secondary_uri->getPort() != $primary_uri->getPort()) { 255 return false; 256 } 257 258 // Any query parameters present in the first URI must be exactly present 259 // in the second URI. 260 $need_params = $primary_uri->getQueryParamsAsMap(); 261 $have_params = $secondary_uri->getQueryParamsAsMap(); 262 263 foreach ($need_params as $key => $value) { 264 if (!array_key_exists($key, $have_params)) { 265 return false; 266 } 267 if ((string)$have_params[$key] != (string)$value) { 268 return false; 269 } 270 } 271 272 // If the first URI is HTTPS, the second URI must also be HTTPS. This 273 // defuses an attack where a third party with control over the network 274 // tricks you into using HTTP to authenticate over a link which is supposed 275 // to be HTTPS only and sniffs all your token cookies. 276 if (strtolower($primary_uri->getProtocol()) == 'https') { 277 if (strtolower($secondary_uri->getProtocol()) != 'https') { 278 return false; 279 } 280 } 281 282 return true; 283 } 284 285}