[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
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 */ 29 final class PhabricatorOAuthServer { 30 31 const AUTHORIZATION_CODE_TIMEOUT = 300; 32 const ACCESS_TOKEN_TIMEOUT = 3600; 33 34 private $user; 35 private $client; 36 37 private function getUser() { 38 if (!$this->user) { 39 throw new Exception('You must setUser before you can getUser!'); 40 } 41 return $this->user; 42 } 43 44 public function setUser(PhabricatorUser $user) { 45 $this->user = $user; 46 return $this; 47 } 48 49 private function getClient() { 50 if (!$this->client) { 51 throw new Exception('You must setClient before you can getClient!'); 52 } 53 return $this->client; 54 } 55 56 public function setClient(PhabricatorOAuthServerClient $client) { 57 $this->client = $client; 58 return $this; 59 } 60 61 /** 62 * @task auth 63 * @return tuple <bool hasAuthorized, ClientAuthorization or null> 64 */ 65 public function userHasAuthorizedClient(array $scope) { 66 67 $authorization = id(new PhabricatorOAuthClientAuthorization())-> 68 loadOneWhere('userPHID = %s AND clientPHID = %s', 69 $this->getUser()->getPHID(), 70 $this->getClient()->getPHID()); 71 if (empty($authorization)) { 72 return array(false, null); 73 } 74 75 if ($scope) { 76 $missing_scope = array_diff_key($scope, 77 $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 validateAccessToken( 162 PhabricatorOAuthServerAccessToken $token, 163 $required_scope) { 164 165 $created_time = $token->getDateCreated(); 166 $must_be_used_by = $created_time + self::ACCESS_TOKEN_TIMEOUT; 167 $expired = time() > $must_be_used_by; 168 $authorization = id(new PhabricatorOAuthClientAuthorization()) 169 ->loadOneWhere( 170 'userPHID = %s AND clientPHID = %s', 171 $token->getUserPHID(), 172 $token->getClientPHID()); 173 174 if (!$authorization) { 175 return false; 176 } 177 $token_scope = $authorization->getScope(); 178 if (!isset($token_scope[$required_scope])) { 179 return false; 180 } 181 182 $valid = true; 183 if ($expired) { 184 $valid = false; 185 // check if the scope includes "offline_access", which makes the 186 // token valid despite being expired 187 if (isset( 188 $token_scope[PhabricatorOAuthServerScope::SCOPE_OFFLINE_ACCESS])) { 189 $valid = true; 190 } 191 } 192 193 return $valid; 194 } 195 196 /** 197 * See http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-3.1.2 198 * for details on what makes a given redirect URI "valid". 199 */ 200 public function validateRedirectURI(PhutilURI $uri) { 201 if (!PhabricatorEnv::isValidRemoteWebResource($uri)) { 202 return false; 203 } 204 205 if ($uri->getFragment()) { 206 return false; 207 } 208 209 if (!$uri->getDomain()) { 210 return false; 211 } 212 213 return true; 214 } 215 216 /** 217 * If there's a URI specified in an OAuth request, it must be validated in 218 * its own right. Further, it must have the same domain and (at least) the 219 * same query parameters as the primary URI. 220 */ 221 public function validateSecondaryRedirectURI( 222 PhutilURI $secondary_uri, 223 PhutilURI $primary_uri) { 224 225 // The secondary URI must be valid. 226 if (!$this->validateRedirectURI($secondary_uri)) { 227 return false; 228 } 229 230 // Both URIs must point at the same domain. 231 if ($secondary_uri->getDomain() != $primary_uri->getDomain()) { 232 return false; 233 } 234 235 // Any query parameters present in the first URI must be exactly present 236 // in the second URI. 237 $need_params = $primary_uri->getQueryParams(); 238 $have_params = $secondary_uri->getQueryParams(); 239 240 foreach ($need_params as $key => $value) { 241 if (!array_key_exists($key, $have_params)) { 242 return false; 243 } 244 if ((string)$have_params[$key] != (string)$value) { 245 return false; 246 } 247 } 248 249 // If the first URI is HTTPS, the second URI must also be HTTPS. This 250 // defuses an attack where a third party with control over the network 251 // tricks you into using HTTP to authenticate over a link which is supposed 252 // to be HTTPS only and sniffs all your token cookies. 253 if (strtolower($primary_uri->getProtocol()) == 'https') { 254 if (strtolower($secondary_uri->getProtocol()) != 'https') { 255 return false; 256 } 257 } 258 259 return true; 260 } 261 262 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |