public function processRequest() { $phid = $this->getAuthorizationPHID(); $title = 'Edit OAuth Client Authorization'; $request = $this->getRequest(); $current_user = $request->getUser(); $authorization = id(new PhabricatorOAuthClientAuthorization())->loadOneWhere('phid = %s', $phid); if (empty($authorization)) { return new Aphront404Response(); } if ($authorization->getUserPHID() != $current_user->getPHID()) { $message = 'Access denied to client authorization with phid ' . $phid . '. ' . 'Only the user who authorized the client has permission to ' . 'edit the authorization.'; return id(new Aphront403Response())->setForbiddenText($message); } if ($request->isFormPost()) { $scopes = PhabricatorOAuthServerScope::getScopesFromRequest($request); $authorization->setScope($scopes); $authorization->save(); return id(new AphrontRedirectResponse())->setURI('/oauthserver/clientauthorization/?edited=' . $phid); } $client_phid = $authorization->getClientPHID(); $client = id(new PhabricatorOAuthServerClient())->loadOneWhere('phid = %s', $client_phid); $created = phabricator_datetime($authorization->getDateCreated(), $current_user); $updated = phabricator_datetime($authorization->getDateModified(), $current_user); $panel = new AphrontPanelView(); $delete_button = phutil_render_tag('a', array('href' => $authorization->getDeleteURI(), 'class' => 'grey button'), 'Delete OAuth Client Authorization'); $panel->addButton($delete_button); $panel->setHeader($title); $form = id(new AphrontFormView())->setUser($current_user)->appendChild(id(new AphrontFormMarkupControl())->setLabel('Client')->setValue(phutil_render_tag('a', array('href' => $client->getViewURI()), phutil_escape_html($client->getName()))))->appendChild(id(new AphrontFormStaticControl())->setLabel('Created')->setValue($created))->appendChild(id(new AphrontFormStaticControl())->setLabel('Last Updated')->setValue($updated))->appendChild(PhabricatorOAuthServerScope::getCheckboxControl($authorization->getScope()))->appendChild(id(new AphrontFormSubmitControl())->setValue('Save OAuth Client Authorization')->addCancelButton('/oauthserver/clientauthorization/')); $panel->appendChild($form); return $this->buildStandardPageResponse($panel, array('title' => $title)); }
public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $server = new PhabricatorOAuthServer(); $client_phid = $request->getStr('client_id'); $scope = $request->getStr('scope'); $redirect_uri = $request->getStr('redirect_uri'); $response_type = $request->getStr('response_type'); // 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! $state = $request->getStr('state'); if (!$client_phid) { return $this->buildErrorResponse('invalid_request', pht('Malformed Request'), pht('Required parameter %s was not present in the request.', phutil_tag('strong', array(), 'client_id'))); } $server->setUser($viewer); $is_authorized = false; $authorization = null; $uri = null; $name = null; // one giant try / catch around all the exciting database stuff so we // can return a 'server_error' response if something goes wrong! try { try { $client = id(new PhabricatorOAuthServerClientQuery())->setViewer($viewer)->withPHIDs(array($client_phid))->executeOne(); } catch (PhabricatorPolicyException $ex) { // We require that users must be able to see an OAuth application // in order to authorize it. This allows an application's visibility // policy to be used to restrict authorized users. // None of the OAuth error responses are a perfect fit for this, but // 'invalid_client' seems closest. return $this->buildErrorResponse('invalid_client', pht('Not Authorized'), pht('You are not authorized to authenticate.')); } if (!$client) { return $this->buildErrorResponse('invalid_request', pht('Invalid Client Application'), pht('Request parameter %s does not specify a valid client application.', phutil_tag('strong', array(), 'client_id'))); } $name = $client->getName(); $server->setClient($client); if ($redirect_uri) { $client_uri = new PhutilURI($client->getRedirectURI()); $redirect_uri = new PhutilURI($redirect_uri); if (!$server->validateSecondaryRedirectURI($redirect_uri, $client_uri)) { return $this->buildErrorResponse('invalid_request', pht('Invalid Redirect URI'), pht('Request parameter %s specifies an invalid redirect URI. ' . 'The redirect URI must be a fully-qualified domain with no ' . 'fragments, and must have the same domain and at least ' . 'the same query parameters as the redirect URI the client ' . 'registered.', phutil_tag('strong', array(), 'redirect_uri'))); } $uri = $redirect_uri; } else { $uri = new PhutilURI($client->getRedirectURI()); } if (empty($response_type)) { return $this->buildErrorResponse('invalid_request', pht('Invalid Response Type'), pht('Required request parameter %s is missing.', phutil_tag('strong', array(), 'response_type'))); } if ($response_type != 'code') { return $this->buildErrorResponse('unsupported_response_type', pht('Unsupported Response Type'), pht('Request parameter %s specifies an unsupported response type. ' . 'Valid response types are: %s.', phutil_tag('strong', array(), 'response_type'), implode(', ', array('code')))); } if ($scope) { if (!PhabricatorOAuthServerScope::validateScopesList($scope)) { return $this->buildErrorResponse('invalid_scope', pht('Invalid 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. // Partly this is a general defense against redirect attacks, and // partly this shakes off anchors in the URI (which are not shaken // by 302'ing). $auth_info = $server->userHasAuthorizedClient($scope); list($is_authorized, $authorization) = $auth_info; if ($request->isFormPost()) { $scope = PhabricatorOAuthServerScope::getScopesFromRequest($request); if ($authorization) { $authorization->setScope($scope)->save(); } else { $authorization = $server->authorizeClient($scope); } $is_authorized = true; } } catch (Exception $e) { return $this->buildErrorResponse('server_error', pht('Server Error'), pht('The authorization server encountered an unexpected condition ' . 'which prevented it from fulfilling the request.')); } // When we reach this part of the controller, we can be in two states: // // 1. The user has not authorized the application yet. We want to // give them an "Authorize this application?" dialog. // 2. The user has authorized the application. We want to give them // a "Confirm Login" dialog. if ($is_authorized) { // The second case is simpler, so handle it first. The user either // authorized the application previously, or has just authorized the // application. Show them a confirm dialog with a normal link back to // the application. This shakes anchors from the URI. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $auth_code = $server->generateAuthorizationCode($uri); unset($unguarded); $full_uri = $this->addQueryParams($uri, array('code' => $auth_code->getCode(), 'scope' => $authorization->getScopeString(), 'state' => $state)); if ($client->getIsTrusted()) { return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI((string) $full_uri); } // TODO: It would be nice to give the user more options here, like // reviewing permissions, canceling the authorization, or aborting // the workflow. $dialog = id(new AphrontDialogView())->setUser($viewer)->setTitle(pht('Authenticate: %s', $name))->appendParagraph(pht('This application ("%s") is authorized to use your Phabricator ' . 'credentials. Continue to complete the authentication workflow.', phutil_tag('strong', array(), $name)))->addCancelButton((string) $full_uri, pht('Continue to Application')); return id(new AphrontDialogResponse())->setDialog($dialog); } // Here, we're confirming authorization for the application. if ($authorization) { $desired_scopes = array_merge($scope, $authorization->getScope()); } else { $desired_scopes = $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())->addHiddenInput('client_id', $client_phid)->addHiddenInput('redirect_uri', $redirect_uri)->addHiddenInput('response_type', $response_type)->addHiddenInput('state', $state)->addHiddenInput('scope', $request->getStr('scope'))->setUser($viewer)->appendChild(PhabricatorOAuthServerScope::getCheckboxControl($desired_scopes)); $cancel_msg = pht('The user declined to authorize this application.'); $cancel_uri = $this->addQueryParams($uri, array('error' => 'access_denied', 'error_description' => $cancel_msg)); $dialog = id(new AphrontDialogView())->setUser($viewer)->setTitle(pht('Authorize "%s"?', $name))->setSubmitURI($request->getRequestURI()->getPath())->setWidth(AphrontDialogView::WIDTH_FORM)->appendParagraph(pht('Do you want to authorize the external application "%s" to ' . 'access your Phabricator account data, including your primary ' . 'email address?', phutil_tag('strong', array(), $name)))->appendChild($form->buildLayoutView())->addSubmitButton(pht('Authorize Access'))->addCancelButton((string) $cancel_uri, pht('Do Not Authorize')); return id(new AphrontDialogResponse())->setDialog($dialog); }
public function processRequest() { $request = $this->getRequest(); $current_user = $request->getUser(); $server = new PhabricatorOAuthServer(); $client_phid = $request->getStr('client_id'); $scope = $request->getStr('scope'); $redirect_uri = $request->getStr('redirect_uri'); $state = $request->getStr('state'); $response_type = $request->getStr('response_type'); $response = new PhabricatorOAuthResponse(); // 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! if ($state) { $response->setState($state); } if (!$client_phid) { $response->setError('invalid_request'); $response->setErrorDescription('Required parameter client_id not specified.'); return $response; } $server->setUser($current_user); // one giant try / catch around all the exciting database stuff so we // can return a 'server_error' response if something goes wrong! try { $client = id(new PhabricatorOAuthServerClient())->loadOneWhere('phid = %s', $client_phid); if (!$client) { $response->setError('invalid_request'); $response->setErrorDescription('Client with id ' . $client_phid . ' not found.'); return $response; } $server->setClient($client); if ($redirect_uri) { $client_uri = new PhutilURI($client->getRedirectURI()); $redirect_uri = new PhutilURI($redirect_uri); if (!$server->validateSecondaryRedirectURI($redirect_uri, $client_uri)) { $response->setError('invalid_request'); $response->setErrorDescription('The specified redirect URI is invalid. The redirect URI ' . 'must be a fully-qualified domain with no fragments and ' . 'must have the same domain and at least the same query ' . 'parameters as the redirect URI the client registered.'); return $response; } $uri = $redirect_uri; $access_token_uri = $uri; } else { $uri = new PhutilURI($client->getRedirectURI()); $access_token_uri = null; } // we've now validated this request enough overall such that we // can safely redirect to the client with the response $response->setClientURI($uri); if (empty($response_type)) { $response->setError('invalid_request'); $response->setErrorDescription('Required parameter response_type not specified.'); return $response; } if ($response_type != 'code') { $response->setError('unsupported_response_type'); $response->setErrorDescription('The authorization server does not support obtaining an ' . 'authorization code using the specified response_type. ' . 'You must specify the response_type as "code".'); return $response; } if ($scope) { if (!PhabricatorOAuthServerScope::validateScopesList($scope)) { $response->setError('invalid_scope'); $response->setErrorDescription('The requested scope is invalid, unknown, or malformed.'); return $response; } $scope = PhabricatorOAuthServerScope::scopesListToDict($scope); } list($is_authorized, $authorization) = $server->userHasAuthorizedClient($scope); if ($is_authorized) { $return_auth_code = true; $unguarded_write = AphrontWriteGuard::beginScopedUnguardedWrites(); } else { if ($request->isFormPost()) { $scope = PhabricatorOAuthServerScope::getScopesFromRequest($request); if ($authorization) { $authorization->setScope($scope)->save(); } else { $authorization = $server->authorizeClient($scope); } $return_auth_code = true; $unguarded_write = null; } else { $return_auth_code = false; $unguarded_write = null; } } if ($return_auth_code) { // step 1 -- generate authorization code $auth_code = $server->generateAuthorizationCode($access_token_uri); // step 2 return it $content = array('code' => $auth_code->getCode(), 'scope' => $authorization->getScopeString()); $response->setContent($content); return $response; } unset($unguarded_write); } catch (Exception $e) { // Note we could try harder to determine between a server_error // vs temporarily_unavailable. Good enough though. $response->setError('server_error'); $response->setErrorDescription('The authorization server encountered an unexpected condition ' . 'which prevented it from fulfilling the request. '); return $response; } // display time -- make a nice form for the user to grant the client // access to the granularity specified by $scope $name = phutil_escape_html($client->getName()); $title = 'Authorize ' . $name . '?'; $panel = new AphrontPanelView(); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->setHeader($title); $description = "Do want to authorize {$name} to access your " . "Phabricator account data?"; if ($scope) { if ($authorization) { $desired_scopes = array_merge($scope, $authorization->getScope()); } else { $desired_scopes = $scope; } if (!PhabricatorOAuthServerScope::validateScopesDict($desired_scopes)) { $response->setError('invalid_scope'); $response->setErrorDescription('The requested scope is invalid, unknown, or malformed.'); return $response; } } else { $desired_scopes = array(PhabricatorOAuthServerScope::SCOPE_WHOAMI => 1, PhabricatorOAuthServerScope::SCOPE_OFFLINE_ACCESS => 1); } $cancel_uri = clone $uri; $cancel_params = array('error' => 'access_denied', 'error_description' => 'The resource owner (aka the user) denied the request.'); $cancel_uri->setQueryParams($cancel_params); $form = id(new AphrontFormView())->setUser($current_user)->appendChild(id(new AphrontFormStaticControl())->setValue($description))->appendChild(PhabricatorOAuthServerScope::getCheckboxControl($desired_scopes))->appendChild(id(new AphrontFormSubmitControl())->setValue('Authorize')->addCancelButton($cancel_uri)); $panel->appendChild($form); return $this->buildStandardPageResponse($panel, array('title' => $title)); }