Differential D15621 Diff 37644 src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php
<?php | <?php | ||||
final class PhabricatorOAuthServerAuthController | final class PhabricatorOAuthServerAuthController | ||||
extends PhabricatorOAuthServerController { | extends PhabricatorOAuthServerController { | ||||
protected function buildApplicationCrumbs() { | protected function buildApplicationCrumbs() { | ||||
// We're specifically not putting an "OAuth Server" application crumb | // We're specifically not putting an "OAuth Server" application crumb | ||||
// on the auth pages because it doesn't make sense to send users there. | // on the auth pages because it doesn't make sense to send users there. | ||||
return new PHUICrumbsView(); | return new PHUICrumbsView(); | ||||
} | } | ||||
public function handleRequest(AphrontRequest $request) { | public function handleRequest(AphrontRequest $request) { | ||||
$viewer = $this->getViewer(); | $viewer = $this->getViewer(); | ||||
$server = new PhabricatorOAuthServer(); | $server = new PhabricatorOAuthServer(); | ||||
$client_phid = $request->getStr('client_id'); | $client_phid = $request->getStr('client_id'); | ||||
$scope = $request->getStr('scope'); | |||||
$redirect_uri = $request->getStr('redirect_uri'); | $redirect_uri = $request->getStr('redirect_uri'); | ||||
$response_type = $request->getStr('response_type'); | $response_type = $request->getStr('response_type'); | ||||
// state is an opaque value the client sent us for their own purposes | // state is an opaque value the client sent us for their own purposes | ||||
// we just need to send it right back to them in the response! | // we just need to send it right back to them in the response! | ||||
$state = $request->getStr('state'); | $state = $request->getStr('state'); | ||||
if (!$client_phid) { | if (!$client_phid) { | ||||
▲ Show 20 Lines • Show All 83 Lines • ▼ Show 20 Lines | try { | ||||
pht('Unsupported Response Type'), | pht('Unsupported Response Type'), | ||||
pht( | pht( | ||||
'Request parameter %s specifies an unsupported response type. '. | 'Request parameter %s specifies an unsupported response type. '. | ||||
'Valid response types are: %s.', | 'Valid response types are: %s.', | ||||
phutil_tag('strong', array(), 'response_type'), | phutil_tag('strong', array(), 'response_type'), | ||||
implode(', ', array('code')))); | implode(', ', array('code')))); | ||||
} | } | ||||
if ($scope) { | |||||
if (!PhabricatorOAuthServerScope::validateScopesList($scope)) { | $requested_scope = $request->getStrList('scope'); | ||||
return $this->buildErrorResponse( | $requested_scope = array_fuse($requested_scope); | ||||
'invalid_scope', | |||||
pht('Invalid Scope'), | $scope = PhabricatorOAuthServerScope::filterScope($requested_scope); | ||||
pht( | |||||
'Request parameter %s specifies an unsupported scope.', | |||||
phutil_tag('strong', array(), 'scope'))); | |||||
} | |||||
$scope = PhabricatorOAuthServerScope::scopesListToDict($scope); | |||||
} else { | |||||
return $this->buildErrorResponse( | |||||
'invalid_request', | |||||
pht('Malformed Request'), | |||||
pht( | |||||
'Required parameter %s was not present in the request.', | |||||
phutil_tag('strong', array(), 'scope'))); | |||||
} | |||||
// NOTE: We're always requiring a confirmation dialog to redirect. | // NOTE: We're always requiring a confirmation dialog to redirect. | ||||
// Partly this is a general defense against redirect attacks, and | // Partly this is a general defense against redirect attacks, and | ||||
// partly this shakes off anchors in the URI (which are not shaken | // partly this shakes off anchors in the URI (which are not shaken | ||||
// by 302'ing). | // by 302'ing). | ||||
$auth_info = $server->userHasAuthorizedClient($scope); | $auth_info = $server->userHasAuthorizedClient($scope); | ||||
list($is_authorized, $authorization) = $auth_info; | list($is_authorized, $authorization) = $auth_info; | ||||
if ($request->isFormPost()) { | if ($request->isFormPost()) { | ||||
$scope = PhabricatorOAuthServerScope::getScopesFromRequest($request); | |||||
if ($authorization) { | if ($authorization) { | ||||
$authorization->setScope($scope)->save(); | $authorization->setScope($scope)->save(); | ||||
} else { | } else { | ||||
$authorization = $server->authorizeClient($scope); | $authorization = $server->authorizeClient($scope); | ||||
} | } | ||||
$is_authorized = true; | $is_authorized = true; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 52 Lines • ▼ Show 20 Lines | if ($is_authorized) { | ||||
phutil_tag('strong', array(), $name))) | phutil_tag('strong', array(), $name))) | ||||
->addCancelButton((string)$full_uri, pht('Continue to Application')); | ->addCancelButton((string)$full_uri, pht('Continue to Application')); | ||||
return id(new AphrontDialogResponse())->setDialog($dialog); | return id(new AphrontDialogResponse())->setDialog($dialog); | ||||
} | } | ||||
// Here, we're confirming authorization for the application. | // Here, we're confirming authorization for the application. | ||||
if ($authorization) { | if ($authorization) { | ||||
$desired_scopes = array_merge($scope, $authorization->getScope()); | $missing_scope = array_diff_key($scope, $authorization->getScope()); | ||||
} else { | } else { | ||||
$desired_scopes = $scope; | $missing_scope = $scope; | ||||
} | |||||
if (!PhabricatorOAuthServerScope::validateScopesDict($desired_scopes)) { | |||||
return $this->buildErrorResponse( | |||||
'invalid_scope', | |||||
pht('Invalid Scope'), | |||||
pht('The requested scope is invalid, unknown, or malformed.')); | |||||
} | } | ||||
$form = id(new AphrontFormView()) | $form = id(new AphrontFormView()) | ||||
->addHiddenInput('client_id', $client_phid) | ->addHiddenInput('client_id', $client_phid) | ||||
->addHiddenInput('redirect_uri', $redirect_uri) | ->addHiddenInput('redirect_uri', $redirect_uri) | ||||
->addHiddenInput('response_type', $response_type) | ->addHiddenInput('response_type', $response_type) | ||||
->addHiddenInput('state', $state) | ->addHiddenInput('state', $state) | ||||
->addHiddenInput('scope', $request->getStr('scope')) | ->addHiddenInput('scope', $request->getStr('scope')) | ||||
->setUser($viewer) | ->setUser($viewer); | ||||
->appendChild( | |||||
PhabricatorOAuthServerScope::getCheckboxControl($desired_scopes)); | |||||
$cancel_msg = pht('The user declined to authorize this application.'); | $cancel_msg = pht('The user declined to authorize this application.'); | ||||
$cancel_uri = $this->addQueryParams( | $cancel_uri = $this->addQueryParams( | ||||
$uri, | $uri, | ||||
array( | array( | ||||
'error' => 'access_denied', | 'error' => 'access_denied', | ||||
'error_description' => $cancel_msg, | 'error_description' => $cancel_msg, | ||||
)); | )); | ||||
return $this->newDialog() | $dialog = $this->newDialog() | ||||
->setShortTitle(pht('Authorize Access')) | ->setShortTitle(pht('Authorize Access')) | ||||
->setTitle(pht('Authorize "%s"?', $name)) | ->setTitle(pht('Authorize "%s"?', $name)) | ||||
->setSubmitURI($request->getRequestURI()->getPath()) | ->setSubmitURI($request->getRequestURI()->getPath()) | ||||
->setWidth(AphrontDialogView::WIDTH_FORM) | ->setWidth(AphrontDialogView::WIDTH_FORM) | ||||
->appendParagraph( | ->appendParagraph( | ||||
pht( | pht( | ||||
'Do you want to authorize the external application "%s" to '. | 'Do you want to authorize the external application "%s" to '. | ||||
'access your Phabricator account data, including your primary '. | 'access your Phabricator account data, including your primary '. | ||||
'email address?', | 'email address?', | ||||
phutil_tag('strong', array(), $name))) | phutil_tag('strong', array(), $name))) | ||||
->appendChild($form->buildLayoutView()) | ->appendForm($form) | ||||
->addSubmitButton(pht('Authorize Access')) | ->addSubmitButton(pht('Authorize Access')) | ||||
->addCancelButton((string)$cancel_uri, pht('Do Not Authorize')); | ->addCancelButton((string)$cancel_uri, pht('Do Not Authorize')); | ||||
if ($missing_scope) { | |||||
$dialog->appendParagraph( | |||||
pht( | |||||
'This application has requested these additional permissions. '. | |||||
'Authorizing it will grant it the permissions it requests:')); | |||||
foreach ($missing_scope as $scope_key => $ignored) { | |||||
// TODO: Once we introduce more scopes, explain them here. | |||||
Lint: TODO Comment: This comment has a TODO. | |||||
} | |||||
} | |||||
$unknown_scope = array_diff_key($requested_scope, $scope); | |||||
if ($unknown_scope) { | |||||
$dialog->appendParagraph( | |||||
pht( | |||||
'This application also requested additional unrecognized '. | |||||
'permissions. These permissions may have existed in an older '. | |||||
'version of Phabricator, or may be from a future version of '. | |||||
'Phabricator. They will not be granted.')); | |||||
$unknown_form = id(new AphrontFormView()) | |||||
->setViewer($viewer) | |||||
->appendChild( | |||||
id(new AphrontFormTextControl()) | |||||
->setLabel(pht('Unknown Scope')) | |||||
->setValue(implode(', ', array_keys($unknown_scope))) | |||||
->setDisabled(true)); | |||||
$dialog->appendForm($unknown_form); | |||||
} | |||||
return $dialog; | |||||
} | } | ||||
private function buildErrorResponse($code, $title, $message) { | private function buildErrorResponse($code, $title, $message) { | ||||
$viewer = $this->getRequest()->getUser(); | $viewer = $this->getRequest()->getUser(); | ||||
return $this->newDialog() | return $this->newDialog() | ||||
->setTitle(pht('OAuth: %s', $title)) | ->setTitle(pht('OAuth: %s', $title)) | ||||
Show All 20 Lines |
This comment has a TODO.