[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class PhabricatorOAuthServerAuthController 4 extends PhabricatorAuthController { 5 6 public function shouldRequireLogin() { 7 return true; 8 } 9 10 public function processRequest() { 11 $request = $this->getRequest(); 12 $viewer = $request->getUser(); 13 14 $server = new PhabricatorOAuthServer(); 15 $client_phid = $request->getStr('client_id'); 16 $scope = $request->getStr('scope', array()); 17 $redirect_uri = $request->getStr('redirect_uri'); 18 $response_type = $request->getStr('response_type'); 19 20 // state is an opaque value the client sent us for their own purposes 21 // we just need to send it right back to them in the response! 22 $state = $request->getStr('state'); 23 24 if (!$client_phid) { 25 return $this->buildErrorResponse( 26 'invalid_request', 27 pht('Malformed Request'), 28 pht( 29 'Required parameter %s was not present in the request.', 30 phutil_tag('strong', array(), 'client_id'))); 31 } 32 33 $server->setUser($viewer); 34 $is_authorized = false; 35 $authorization = null; 36 $uri = null; 37 $name = null; 38 39 // one giant try / catch around all the exciting database stuff so we 40 // can return a 'server_error' response if something goes wrong! 41 try { 42 $client = id(new PhabricatorOAuthServerClient()) 43 ->loadOneWhere('phid = %s', $client_phid); 44 45 if (!$client) { 46 return $this->buildErrorResponse( 47 'invalid_request', 48 pht('Invalid Client Application'), 49 pht( 50 'Request parameter %s does not specify a valid client application.', 51 phutil_tag('strong', array(), 'client_id'))); 52 } 53 54 $name = $client->getName(); 55 $server->setClient($client); 56 if ($redirect_uri) { 57 $client_uri = new PhutilURI($client->getRedirectURI()); 58 $redirect_uri = new PhutilURI($redirect_uri); 59 if (!($server->validateSecondaryRedirectURI($redirect_uri, 60 $client_uri))) { 61 return $this->buildErrorResponse( 62 'invalid_request', 63 pht('Invalid Redirect URI'), 64 pht( 65 'Request parameter %s specifies an invalid redirect URI. '. 66 'The redirect URI must be a fully-qualified domain with no '. 67 'fragments, and must have the same domain and at least '. 68 'the same query parameters as the redirect URI the client '. 69 'registered.', 70 phutil_tag('strong', array(), 'redirect_uri'))); 71 } 72 $uri = $redirect_uri; 73 } else { 74 $uri = new PhutilURI($client->getRedirectURI()); 75 } 76 77 if (empty($response_type)) { 78 return $this->buildErrorResponse( 79 'invalid_request', 80 pht('Invalid Response Type'), 81 pht( 82 'Required request parameter %s is missing.', 83 phutil_tag('strong', array(), 'response_type'))); 84 } 85 86 if ($response_type != 'code') { 87 return $this->buildErrorResponse( 88 'unsupported_response_type', 89 pht('Unsupported Response Type'), 90 pht( 91 'Request parameter %s specifies an unsupported response type. '. 92 'Valid response types are: %s.', 93 phutil_tag('strong', array(), 'response_type'), 94 implode(', ', array('code')))); 95 } 96 97 if ($scope) { 98 if (!PhabricatorOAuthServerScope::validateScopesList($scope)) { 99 return $this->buildErrorResponse( 100 'invalid_scope', 101 pht('Invalid Scope'), 102 pht( 103 'Request parameter %s specifies an unsupported scope.', 104 phutil_tag('strong', array(), 'scope'))); 105 } 106 $scope = PhabricatorOAuthServerScope::scopesListToDict($scope); 107 } 108 109 // NOTE: We're always requiring a confirmation dialog to redirect. 110 // Partly this is a general defense against redirect attacks, and 111 // partly this shakes off anchors in the URI (which are not shaken 112 // by 302'ing). 113 114 $auth_info = $server->userHasAuthorizedClient($scope); 115 list($is_authorized, $authorization) = $auth_info; 116 117 if ($request->isFormPost()) { 118 // TODO: We should probably validate this more? It feels a little funky. 119 $scope = PhabricatorOAuthServerScope::getScopesFromRequest($request); 120 121 if ($authorization) { 122 $authorization->setScope($scope)->save(); 123 } else { 124 $authorization = $server->authorizeClient($scope); 125 } 126 127 $is_authorized = true; 128 } 129 } catch (Exception $e) { 130 return $this->buildErrorResponse( 131 'server_error', 132 pht('Server Error'), 133 pht( 134 'The authorization server encountered an unexpected condition '. 135 'which prevented it from fulfilling the request.')); 136 } 137 138 // When we reach this part of the controller, we can be in two states: 139 // 140 // 1. The user has not authorized the application yet. We want to 141 // give them an "Authorize this application?" dialog. 142 // 2. The user has authorized the application. We want to give them 143 // a "Confirm Login" dialog. 144 145 if ($is_authorized) { 146 147 // The second case is simpler, so handle it first. The user either 148 // authorized the application previously, or has just authorized the 149 // application. Show them a confirm dialog with a normal link back to 150 // the application. This shakes anchors from the URI. 151 152 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 153 $auth_code = $server->generateAuthorizationCode($uri); 154 unset($unguarded); 155 156 $full_uri = $this->addQueryParams( 157 $uri, 158 array( 159 'code' => $auth_code->getCode(), 160 'scope' => $authorization->getScopeString(), 161 'state' => $state, 162 )); 163 164 // TODO: It would be nice to give the user more options here, like 165 // reviewing permissions, canceling the authorization, or aborting 166 // the workflow. 167 168 $dialog = id(new AphrontDialogView()) 169 ->setUser($viewer) 170 ->setTitle(pht('Authenticate: %s', $name)) 171 ->appendParagraph( 172 pht( 173 'This application ("%s") is authorized to use your Phabricator '. 174 'credentials. Continue to complete the authentication workflow.', 175 phutil_tag('strong', array(), $name))) 176 ->addCancelButton((string)$full_uri, pht('Continue to Application')); 177 178 return id(new AphrontDialogResponse())->setDialog($dialog); 179 } 180 181 // Here, we're confirming authorization for the application. 182 183 if ($scope) { 184 if ($authorization) { 185 $desired_scopes = array_merge($scope, 186 $authorization->getScope()); 187 } else { 188 $desired_scopes = $scope; 189 } 190 if (!PhabricatorOAuthServerScope::validateScopesDict($desired_scopes)) { 191 return $this->buildErrorResponse( 192 'invalid_scope', 193 pht('Invalid Scope'), 194 pht('The requested scope is invalid, unknown, or malformed.')); 195 } 196 } else { 197 $desired_scopes = array( 198 PhabricatorOAuthServerScope::SCOPE_WHOAMI => 1, 199 PhabricatorOAuthServerScope::SCOPE_OFFLINE_ACCESS => 1, 200 ); 201 } 202 203 $form = id(new AphrontFormView()) 204 ->addHiddenInput('client_id', $client_phid) 205 ->addHiddenInput('redirect_uri', $redirect_uri) 206 ->addHiddenInput('response_type', $response_type) 207 ->addHiddenInput('state', $state) 208 ->addHiddenInput('scope', $request->getStr('scope')) 209 ->setUser($viewer) 210 ->appendChild( 211 PhabricatorOAuthServerScope::getCheckboxControl($desired_scopes)); 212 213 $cancel_msg = pht('The user declined to authorize this application.'); 214 $cancel_uri = $this->addQueryParams( 215 $uri, 216 array( 217 'error' => 'access_denied', 218 'error_description' => $cancel_msg, 219 )); 220 221 $dialog = id(new AphrontDialogView()) 222 ->setUser($viewer) 223 ->setTitle(pht('Authorize "%s"?', $name)) 224 ->setSubmitURI($request->getRequestURI()->getPath()) 225 ->setWidth(AphrontDialogView::WIDTH_FORM) 226 ->appendParagraph( 227 pht( 228 'Do you want to authorize the external application "%s" to '. 229 'access your Phabricator account data?', 230 phutil_tag('strong', array(), $name))) 231 ->appendChild($form->buildLayoutView()) 232 ->addSubmitButton(pht('Authorize Access')) 233 ->addCancelButton((string)$cancel_uri, pht('Do Not Authorize')); 234 235 return id(new AphrontDialogResponse())->setDialog($dialog); 236 } 237 238 239 private function buildErrorResponse($code, $title, $message) { 240 $viewer = $this->getRequest()->getUser(); 241 242 $dialog = id(new AphrontDialogView()) 243 ->setUser($viewer) 244 ->setTitle(pht('OAuth: %s', $title)) 245 ->appendParagraph($message) 246 ->appendParagraph( 247 pht('OAuth Error Code: %s', phutil_tag('tt', array(), $code))) 248 ->addCancelButton('/', pht('Alas!')); 249 250 return id(new AphrontDialogResponse())->setDialog($dialog); 251 } 252 253 254 private function addQueryParams(PhutilURI $uri, array $params) { 255 $full_uri = clone $uri; 256 257 foreach ($params as $key => $value) { 258 if (strlen($value)) { 259 $full_uri->setQueryParam($key, $value); 260 } 261 } 262 263 return $full_uri; 264 } 265 266 }
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 |