[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/oauthserver/controller/ -> PhabricatorOAuthServerAuthController.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1