public function processRequest()
 {
     $request = $this->getRequest();
     $current_user = $request->getUser();
     $server = new PhabricatorOAuthServer($current_user);
     $panels = array();
     $results = array();
     if ($request->isFormPost()) {
         $action = $request->getStr('action');
         switch ($action) {
             case 'testclientauthorization':
                 $user_phid = $current_user->getPHID();
                 $client_phid = $request->getStr('client_phid');
                 $client = id(new PhabricatorOAuthServerClient())->loadOneWhere('phid = %s', $client_phid);
                 if (!$client) {
                     throw new Exception('Failed to load client!');
                 }
                 if ($client->getCreatorPHID() != $user_phid || $current_user->getPHID() != $user_phid) {
                     throw new Exception('Only allowed to make test data for yourself ' . 'for clients you own!');
                 }
                 // blankclientauthorizations don't get scope
                 $scope = array();
                 $server->setUser($current_user);
                 $server->setClient($client);
                 $authorization = $server->authorizeClient($scope);
                 return id(new AphrontRedirectResponse())->setURI('/oauthserver/clientauthorization/?edited=' . $authorization->getPHID());
                 break;
             default:
                 break;
         }
     }
 }
 public function testValidateSecondaryRedirectURI()
 {
     $server = new PhabricatorOAuthServer();
     $primary_uri = new PhutilURI('http://www.google.com');
     static $test_domain_map = array('http://www.google.com' => true, 'http://www.google.com/' => true, 'http://www.google.com/auth' => true, 'http://www.google.com/?auth' => true, 'www.google.com' => false, 'http://www.google.com/auth#invalid' => false, 'http://www.example.com' => false);
     foreach ($test_domain_map as $input => $expected) {
         $uri = new PhutilURI($input);
         $this->assertEqual($expected, $server->validateSecondaryRedirectURI($uri, $primary_uri), "Validation of redirect URI '{$input}' " . "relative to '{$primary_uri}'");
     }
     $primary_uri = new PhutilURI('http://www.google.com/?auth');
     static $test_query_map = array('http://www.google.com' => false, 'http://www.google.com/' => false, 'http://www.google.com/auth' => false, 'http://www.google.com/?auth' => true, 'http://www.google.com/?auth&stuff' => true, 'http://www.google.com/?stuff' => false);
     foreach ($test_query_map as $input => $expected) {
         $uri = new PhutilURI($input);
         $this->assertEqual($expected, $server->validateSecondaryRedirectURI($uri, $primary_uri), "Validation of secondary redirect URI '{$input}' " . "relative to '{$primary_uri}'");
     }
     $primary_uri = new PhutilURI('https://secure.example.com/');
     $tests = array('https://secure.example.com/' => true, 'http://secure.example.com/' => false);
     foreach ($tests as $input => $expected) {
         $uri = new PhutilURI($input);
         $this->assertEqual($expected, $server->validateSecondaryRedirectURI($uri, $primary_uri), "Validation (https): {$input}");
     }
     $primary_uri = new PhutilURI('http://example.com/?z=2&y=3');
     $tests = array('http://example.com?z=2&y=3' => true, 'http://example.com?y=3&z=2' => true, 'http://example.com?y=3&z=2&x=1' => true, 'http://example.com?y=2&z=3' => false, 'http://example.com?y&x' => false, 'http://example.com?z=2&x=3' => false);
     foreach ($tests as $input => $expected) {
         $uri = new PhutilURI($input);
         $this->assertEqual($expected, $server->validateSecondaryRedirectURI($uri, $primary_uri), "Validation (params): {$input}");
     }
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $phid = $this->getClientPHID();
     if ($phid) {
         $client = id(new PhabricatorOAuthServerClientQuery())->setViewer($viewer)->withPHIDs(array($phid))->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->executeOne();
         if (!$client) {
             return new Aphront404Response();
         }
         $title = pht('Edit OAuth Application: %s', $client->getName());
         $submit_button = pht('Save Application');
         $crumb_text = pht('Edit');
         $cancel_uri = $client->getViewURI();
         $is_new = false;
     } else {
         $this->requireApplicationCapability(PhabricatorOAuthServerCreateClientsCapability::CAPABILITY);
         $client = PhabricatorOAuthServerClient::initializeNewClient($viewer);
         $title = pht('Create OAuth Application');
         $submit_button = pht('Create Application');
         $crumb_text = pht('Create Application');
         $cancel_uri = $this->getApplicationURI();
         $is_new = true;
     }
     $errors = array();
     $e_redirect = true;
     $e_name = true;
     if ($request->isFormPost()) {
         $redirect_uri = $request->getStr('redirect_uri');
         $client->setName($request->getStr('name'));
         $client->setRedirectURI($redirect_uri);
         if (!strlen($client->getName())) {
             $errors[] = pht('You must choose a name for this OAuth application.');
             $e_name = pht('Required');
         }
         $server = new PhabricatorOAuthServer();
         $uri = new PhutilURI($redirect_uri);
         if (!$server->validateRedirectURI($uri)) {
             $errors[] = pht('Redirect URI must be a fully qualified domain name ' . 'with no fragments. See %s for more information on the correct ' . 'format.', 'http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-3.1.2');
             $e_redirect = pht('Invalid');
         }
         $client->setViewPolicy($request->getStr('viewPolicy'));
         $client->setEditPolicy($request->getStr('editPolicy'));
         if (!$errors) {
             $client->save();
             $view_uri = $client->getViewURI();
             return id(new AphrontRedirectResponse())->setURI($view_uri);
         }
     }
     $policies = id(new PhabricatorPolicyQuery())->setViewer($viewer)->setObject($client)->execute();
     $form = id(new AphrontFormView())->setUser($viewer)->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Name'))->setName('name')->setValue($client->getName())->setError($e_name))->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Redirect URI'))->setName('redirect_uri')->setValue($client->getRedirectURI())->setError($e_redirect))->appendChild(id(new AphrontFormPolicyControl())->setUser($viewer)->setCapability(PhabricatorPolicyCapability::CAN_VIEW)->setPolicyObject($client)->setPolicies($policies)->setName('viewPolicy'))->appendChild(id(new AphrontFormPolicyControl())->setUser($viewer)->setCapability(PhabricatorPolicyCapability::CAN_EDIT)->setPolicyObject($client)->setPolicies($policies)->setName('editPolicy'))->appendChild(id(new AphrontFormSubmitControl())->addCancelButton($cancel_uri)->setValue($submit_button));
     $crumbs = $this->buildApplicationCrumbs();
     if (!$is_new) {
         $crumbs->addTextCrumb($client->getName(), $client->getViewURI());
     }
     $crumbs->addTextCrumb($crumb_text);
     $box = id(new PHUIObjectBoxView())->setHeaderText($title)->setFormErrors($errors)->setForm($form);
     return $this->buildApplicationPage(array($crumbs, $box), array('title' => $title));
 }
 /**
  * Authenticate the client making the request to a Phabricator user account.
  *
  * @param   ConduitAPIRequest Request being executed.
  * @param   dict              Request metadata.
  * @return  null|pair         Null to indicate successful authentication, or
  *                            an error code and error message pair.
  */
 private function authenticateUser(ConduitAPIRequest $api_request, array $metadata)
 {
     $request = $this->getRequest();
     if ($request->getUser()->getPHID()) {
         $request->validateCSRF();
         return $this->validateAuthenticatedUser($api_request, $request->getUser());
     }
     // handle oauth
     $access_token = $request->getStr('access_token');
     $method_scope = $metadata['scope'];
     if ($access_token && $method_scope != PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE) {
         $token = id(new PhabricatorOAuthServerAccessToken())->loadOneWhere('token = %s', $access_token);
         if (!$token) {
             return array('ERR-INVALID-AUTH', 'Access token does not exist.');
         }
         $oauth_server = new PhabricatorOAuthServer();
         $valid = $oauth_server->validateAccessToken($token, $method_scope);
         if (!$valid) {
             return array('ERR-INVALID-AUTH', 'Access token is invalid.');
         }
         // valid token, so let's log in the user!
         $user_phid = $token->getUserPHID();
         $user = id(new PhabricatorUser())->loadOneWhere('phid = %s', $user_phid);
         if (!$user) {
             return array('ERR-INVALID-AUTH', 'Access token is for invalid user.');
         }
         return $this->validateAuthenticatedUser($api_request, $user);
     }
     // Handle sessionless auth. TOOD: This is super messy.
     if (isset($metadata['authUser'])) {
         $user = id(new PhabricatorUser())->loadOneWhere('userName = %s', $metadata['authUser']);
         if (!$user) {
             return array('ERR-INVALID-AUTH', 'Authentication is invalid.');
         }
         $token = idx($metadata, 'authToken');
         $signature = idx($metadata, 'authSignature');
         $certificate = $user->getConduitCertificate();
         if (sha1($token . $certificate) !== $signature) {
             return array('ERR-INVALID-AUTH', 'Authentication is invalid.');
         }
         return $this->validateAuthenticatedUser($api_request, $user);
     }
     $session_key = idx($metadata, 'sessionKey');
     if (!$session_key) {
         return array('ERR-INVALID-SESSION', 'Session key is not present.');
     }
     $session = queryfx_one(id(new PhabricatorUser())->establishConnection('r'), 'SELECT * FROM %T WHERE sessionKey = %s', PhabricatorUser::SESSION_TABLE, $session_key);
     if (!$session) {
         return array('ERR-INVALID-SESSION', 'Session key is invalid.');
     }
     // TODO: Make sessions timeout.
     // TODO: When we pull a session, read connectionID from the session table.
     $user = id(new PhabricatorUser())->loadOneWhere('phid = %s', $session['userPHID']);
     if (!$user) {
         return array('ERR-INVALID-SESSION', 'Session is for nonexistent user.');
     }
     return $this->validateAuthenticatedUser($api_request, $user);
 }
 /**
  * Authenticate the client making the request to a Phabricator user account.
  *
  * @param   ConduitAPIRequest Request being executed.
  * @param   dict              Request metadata.
  * @return  null|pair         Null to indicate successful authentication, or
  *                            an error code and error message pair.
  */
 private function authenticateUser(ConduitAPIRequest $api_request, array $metadata)
 {
     $request = $this->getRequest();
     if ($request->getUser()->getPHID()) {
         $request->validateCSRF();
         return $this->validateAuthenticatedUser($api_request, $request->getUser());
     }
     $auth_type = idx($metadata, 'auth.type');
     if ($auth_type === ConduitClient::AUTH_ASYMMETRIC) {
         $host = idx($metadata, 'auth.host');
         if (!$host) {
             return array('ERR-INVALID-AUTH', pht('Request is missing required "%s" parameter.', 'auth.host'));
         }
         // TODO: Validate that we are the host!
         $raw_key = idx($metadata, 'auth.key');
         $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($raw_key);
         $ssl_public_key = $public_key->toPKCS8();
         // First, verify the signature.
         try {
             $protocol_data = $metadata;
             // TODO: We should stop writing this into the protocol data when
             // processing a request.
             unset($protocol_data['scope']);
             ConduitClient::verifySignature($this->method, $api_request->getAllParameters(), $protocol_data, $ssl_public_key);
         } catch (Exception $ex) {
             return array('ERR-INVALID-AUTH', pht('Signature verification failure. %s', $ex->getMessage()));
         }
         // If the signature is valid, find the user or device which is
         // associated with this public key.
         $stored_key = id(new PhabricatorAuthSSHKeyQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withKeys(array($public_key))->executeOne();
         if (!$stored_key) {
             return array('ERR-INVALID-AUTH', pht('No user or device is associated with that public key.'));
         }
         $object = $stored_key->getObject();
         if ($object instanceof PhabricatorUser) {
             $user = $object;
         } else {
             if (!$stored_key->getIsTrusted()) {
                 return array('ERR-INVALID-AUTH', pht('The key which signed this request is not trusted. Only ' . 'trusted keys can be used to sign API calls.'));
             }
             if (!PhabricatorEnv::isClusterRemoteAddress()) {
                 return array('ERR-INVALID-AUTH', pht('This request originates from outside of the Phabricator ' . 'cluster address range. Requests signed with trusted ' . 'device keys must originate from within the cluster.'));
             }
             $user = PhabricatorUser::getOmnipotentUser();
             // Flag this as an intracluster request.
             $api_request->setIsClusterRequest(true);
         }
         return $this->validateAuthenticatedUser($api_request, $user);
     } else {
         if ($auth_type === null) {
             // No specified authentication type, continue with other authentication
             // methods below.
         } else {
             return array('ERR-INVALID-AUTH', pht('Provided "%s" ("%s") is not recognized.', 'auth.type', $auth_type));
         }
     }
     $token_string = idx($metadata, 'token');
     if (strlen($token_string)) {
         if (strlen($token_string) != 32) {
             return array('ERR-INVALID-AUTH', pht('API token "%s" has the wrong length. API tokens should be ' . '32 characters long.', $token_string));
         }
         $type = head(explode('-', $token_string));
         $valid_types = PhabricatorConduitToken::getAllTokenTypes();
         $valid_types = array_fuse($valid_types);
         if (empty($valid_types[$type])) {
             return array('ERR-INVALID-AUTH', pht('API token "%s" has the wrong format. API tokens should be ' . '32 characters long and begin with one of these prefixes: %s.', $token_string, implode(', ', $valid_types)));
         }
         $token = id(new PhabricatorConduitTokenQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withTokens(array($token_string))->withExpired(false)->executeOne();
         if (!$token) {
             $token = id(new PhabricatorConduitTokenQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withTokens(array($token_string))->withExpired(true)->executeOne();
             if ($token) {
                 return array('ERR-INVALID-AUTH', pht('API token "%s" was previously valid, but has expired.', $token_string));
             } else {
                 return array('ERR-INVALID-AUTH', pht('API token "%s" is not valid.', $token_string));
             }
         }
         // If this is a "cli-" token, it expires shortly after it is generated
         // by default. Once it is actually used, we extend its lifetime and make
         // it permanent. This allows stray tokens to get cleaned up automatically
         // if they aren't being used.
         if ($token->getTokenType() == PhabricatorConduitToken::TYPE_COMMANDLINE) {
             if ($token->getExpires()) {
                 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
                 $token->setExpires(null);
                 $token->save();
                 unset($unguarded);
             }
         }
         // If this is a "clr-" token, Phabricator must be configured in cluster
         // mode and the remote address must be a cluster node.
         if ($token->getTokenType() == PhabricatorConduitToken::TYPE_CLUSTER) {
             if (!PhabricatorEnv::isClusterRemoteAddress()) {
                 return array('ERR-INVALID-AUTH', pht('This request originates from outside of the Phabricator ' . 'cluster address range. Requests signed with cluster API ' . 'tokens must originate from within the cluster.'));
             }
             // Flag this as an intracluster request.
             $api_request->setIsClusterRequest(true);
         }
         $user = $token->getObject();
         if (!$user instanceof PhabricatorUser) {
             return array('ERR-INVALID-AUTH', pht('API token is not associated with a valid user.'));
         }
         return $this->validateAuthenticatedUser($api_request, $user);
     }
     // handle oauth
     $access_token = idx($metadata, 'access_token');
     $method_scope = idx($metadata, 'scope');
     if ($access_token && $method_scope != PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE) {
         $token = id(new PhabricatorOAuthServerAccessToken())->loadOneWhere('token = %s', $access_token);
         if (!$token) {
             return array('ERR-INVALID-AUTH', pht('Access token does not exist.'));
         }
         $oauth_server = new PhabricatorOAuthServer();
         $valid = $oauth_server->validateAccessToken($token, $method_scope);
         if (!$valid) {
             return array('ERR-INVALID-AUTH', pht('Access token is invalid.'));
         }
         // valid token, so let's log in the user!
         $user_phid = $token->getUserPHID();
         $user = id(new PhabricatorUser())->loadOneWhere('phid = %s', $user_phid);
         if (!$user) {
             return array('ERR-INVALID-AUTH', pht('Access token is for invalid user.'));
         }
         return $this->validateAuthenticatedUser($api_request, $user);
     }
     // Handle sessionless auth.
     // TODO: This is super messy.
     // TODO: Remove this in favor of token-based auth.
     if (isset($metadata['authUser'])) {
         $user = id(new PhabricatorUser())->loadOneWhere('userName = %s', $metadata['authUser']);
         if (!$user) {
             return array('ERR-INVALID-AUTH', pht('Authentication is invalid.'));
         }
         $token = idx($metadata, 'authToken');
         $signature = idx($metadata, 'authSignature');
         $certificate = $user->getConduitCertificate();
         $hash = sha1($token . $certificate);
         if (!phutil_hashes_are_identical($hash, $signature)) {
             return array('ERR-INVALID-AUTH', pht('Authentication is invalid.'));
         }
         return $this->validateAuthenticatedUser($api_request, $user);
     }
     // Handle session-based auth.
     // TODO: Remove this in favor of token-based auth.
     $session_key = idx($metadata, 'sessionKey');
     if (!$session_key) {
         return array('ERR-INVALID-SESSION', pht('Session key is not present.'));
     }
     $user = id(new PhabricatorAuthSessionEngine())->loadUserForSession(PhabricatorAuthSession::TYPE_CONDUIT, $session_key);
     if (!$user) {
         return array('ERR-INVALID-SESSION', pht('Session key is invalid.'));
     }
     return $this->validateAuthenticatedUser($api_request, $user);
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $current_user = $request->getUser();
     $error = null;
     $bad_redirect = false;
     $phid = $this->getClientPHID();
     // if we have a phid, then we're editing
     $this->setIsClientEdit($phid);
     if ($this->isClientEdit()) {
         $client = id(new PhabricatorOAuthServerClient())->loadOneWhere('phid = %s', $phid);
         $title = 'Edit OAuth Client';
         // validate the client
         if (empty($client)) {
             return new Aphront404Response();
         }
         if ($client->getCreatorPHID() != $current_user->getPHID()) {
             $message = 'Access denied to edit client with id ' . $phid . '. ' . 'Only the user who created the client has permission to ' . 'edit the client.';
             return id(new Aphront403Response())->setForbiddenText($message);
         }
         $submit_button = 'Save OAuth Client';
         $secret = null;
         // new client - much simpler
     } else {
         $client = new PhabricatorOAuthServerClient();
         $title = 'Create OAuth Client';
         $submit_button = 'Create OAuth Client';
         $secret = Filesystem::readRandomCharacters(32);
     }
     if ($request->isFormPost()) {
         $redirect_uri = $request->getStr('redirect_uri');
         $client->setName($request->getStr('name'));
         $client->setRedirectURI($redirect_uri);
         if ($secret) {
             $client->setSecret($secret);
         }
         $client->setCreatorPHID($current_user->getPHID());
         $uri = new PhutilURI($redirect_uri);
         $server = new PhabricatorOAuthServer();
         if (!$server->validateRedirectURI($uri)) {
             $error = new AphrontErrorView();
             $error->setSeverity(AphrontErrorView::SEVERITY_ERROR);
             $error->setTitle('Redirect URI must be a fully qualified domain name ' . 'with no fragments. See ' . 'http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-3.1.2 ' . 'for more information on the correct format.');
             $bad_redirect = true;
         } else {
             $client->save();
             // refresh the phid in case its a create
             $phid = $client->getPHID();
             if ($this->isClientEdit()) {
                 return id(new AphrontRedirectResponse())->setURI('/oauthserver/client/?edited=' . $phid);
             } else {
                 return id(new AphrontRedirectResponse())->setURI('/oauthserver/client/?new=' . $phid);
             }
         }
     }
     $panel = new AphrontPanelView();
     if ($this->isClientEdit()) {
         $delete_button = phutil_render_tag('a', array('href' => $client->getDeleteURI(), 'class' => 'grey button'), 'Delete OAuth Client');
         $panel->addButton($delete_button);
     }
     $panel->setHeader($title);
     $form = id(new AphrontFormView())->setUser($current_user)->appendChild(id(new AphrontFormTextControl())->setLabel('Name')->setName('name')->setValue($client->getName()));
     if ($this->isClientEdit()) {
         $form->appendChild(id(new AphrontFormTextControl())->setLabel('ID')->setValue($phid))->appendChild(id(new AphrontFormStaticControl())->setLabel('Secret')->setValue($client->getSecret()));
     }
     $form->appendChild(id(new AphrontFormTextControl())->setLabel('Redirect URI')->setName('redirect_uri')->setValue($client->getRedirectURI())->setError($bad_redirect));
     if ($this->isClientEdit()) {
         $created = phabricator_datetime($client->getDateCreated(), $current_user);
         $updated = phabricator_datetime($client->getDateModified(), $current_user);
         $form->appendChild(id(new AphrontFormStaticControl())->setLabel('Created')->setValue($created))->appendChild(id(new AphrontFormStaticControl())->setLabel('Last Updated')->setValue($updated));
     }
     $form->appendChild(id(new AphrontFormSubmitControl())->setValue($submit_button));
     $panel->appendChild($form);
     return $this->buildStandardPageResponse(array($error, $panel), array('title' => $title));
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $grant_type = $request->getStr('grant_type');
     $code = $request->getStr('code');
     $redirect_uri = $request->getStr('redirect_uri');
     $client_phid = $request->getStr('client_id');
     $client_secret = $request->getStr('client_secret');
     $response = new PhabricatorOAuthResponse();
     $server = new PhabricatorOAuthServer();
     if ($grant_type != 'authorization_code') {
         $response->setError('unsupported_grant_type');
         $response->setErrorDescription(pht('Only %s %s is supported.', 'grant_type', 'authorization_code'));
         return $response;
     }
     if (!$code) {
         $response->setError('invalid_request');
         $response->setErrorDescription(pht('Required parameter code missing.'));
         return $response;
     }
     if (!$client_phid) {
         $response->setError('invalid_request');
         $response->setErrorDescription(pht('Required parameter %s missing.', 'client_id'));
         return $response;
     }
     if (!$client_secret) {
         $response->setError('invalid_request');
         $response->setErrorDescription(pht('Required parameter %s missing.', 'client_secret'));
         return $response;
     }
     // one giant try / catch around all the exciting database stuff so we
     // can return a 'server_error' response if something goes wrong!
     try {
         $auth_code = id(new PhabricatorOAuthServerAuthorizationCode())->loadOneWhere('code = %s', $code);
         if (!$auth_code) {
             $response->setError('invalid_grant');
             $response->setErrorDescription(pht('Authorization code %d not found.', $code));
             return $response;
         }
         // if we have an auth code redirect URI, there must be a redirect_uri
         // in the request and it must match the auth code redirect uri *exactly*
         $auth_code_redirect_uri = $auth_code->getRedirectURI();
         if ($auth_code_redirect_uri) {
             $auth_code_redirect_uri = new PhutilURI($auth_code_redirect_uri);
             $redirect_uri = new PhutilURI($redirect_uri);
             if (!$redirect_uri->getDomain() || $redirect_uri != $auth_code_redirect_uri) {
                 $response->setError('invalid_grant');
                 $response->setErrorDescription(pht('Redirect URI in request must exactly match redirect URI ' . 'from authorization code.'));
                 return $response;
             }
         } else {
             if ($redirect_uri) {
                 $response->setError('invalid_grant');
                 $response->setErrorDescription(pht('Redirect URI in request and no redirect URI in authorization ' . 'code. The two must exactly match.'));
                 return $response;
             }
         }
         $client = id(new PhabricatorOAuthServerClient())->loadOneWhere('phid = %s', $client_phid);
         if (!$client) {
             $response->setError('invalid_client');
             $response->setErrorDescription(pht('Client with %s %d not found.', 'client_id', $client_phid));
             return $response;
         }
         $server->setClient($client);
         $user_phid = $auth_code->getUserPHID();
         $user = id(new PhabricatorUser())->loadOneWhere('phid = %s', $user_phid);
         if (!$user) {
             $response->setError('invalid_grant');
             $response->setErrorDescription(pht('User with PHID %d not found.', $user_phid));
             return $response;
         }
         $server->setUser($user);
         $test_code = new PhabricatorOAuthServerAuthorizationCode();
         $test_code->setClientSecret($client_secret);
         $test_code->setClientPHID($client_phid);
         $is_good_code = $server->validateAuthorizationCode($auth_code, $test_code);
         if (!$is_good_code) {
             $response->setError('invalid_grant');
             $response->setErrorDescription(pht('Invalid authorization code %d.', $code));
             return $response;
         }
         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         $access_token = $server->generateAccessToken();
         $auth_code->delete();
         unset($unguarded);
         $result = array('access_token' => $access_token->getToken(), 'token_type' => 'Bearer', 'expires_in' => PhabricatorOAuthServer::ACCESS_TOKEN_TIMEOUT);
         return $response->setContent($result);
     } catch (Exception $e) {
         $response->setError('server_error');
         $response->setErrorDescription(pht('The authorization server encountered an unexpected condition ' . 'which prevented it from fulfilling the request.'));
         return $response;
     }
 }
 public function handleRequest(AphrontRequest $request)
 {
     $grant_type = $request->getStr('grant_type');
     $code = $request->getStr('code');
     $redirect_uri = $request->getStr('redirect_uri');
     $response = new PhabricatorOAuthResponse();
     $server = new PhabricatorOAuthServer();
     $client_id_parameter = $request->getStr('client_id');
     $client_id_header = idx($_SERVER, 'PHP_AUTH_USER');
     if (strlen($client_id_parameter) && strlen($client_id_header)) {
         if ($client_id_parameter !== $client_id_header) {
             throw new Exception(pht('Request included a client_id parameter and an "Authorization" ' . 'header with a username, but the values "%s" and "%s") disagree. ' . 'The values must match.', $client_id_parameter, $client_id_header));
         }
     }
     $client_secret_parameter = $request->getStr('client_secret');
     $client_secret_header = idx($_SERVER, 'PHP_AUTH_PW');
     if (strlen($client_secret_parameter)) {
         // If the `client_secret` parameter is present, prefer parameters.
         $client_phid = $client_id_parameter;
         $client_secret = $client_secret_parameter;
     } else {
         // Otherwise, read values from the "Authorization" header.
         $client_phid = $client_id_header;
         $client_secret = $client_secret_header;
     }
     if ($grant_type != 'authorization_code') {
         $response->setError('unsupported_grant_type');
         $response->setErrorDescription(pht('Only %s %s is supported.', 'grant_type', 'authorization_code'));
         return $response;
     }
     if (!$code) {
         $response->setError('invalid_request');
         $response->setErrorDescription(pht('Required parameter code missing.'));
         return $response;
     }
     if (!$client_phid) {
         $response->setError('invalid_request');
         $response->setErrorDescription(pht('Required parameter %s missing.', 'client_id'));
         return $response;
     }
     if (!$client_secret) {
         $response->setError('invalid_request');
         $response->setErrorDescription(pht('Required parameter %s missing.', 'client_secret'));
         return $response;
     }
     // one giant try / catch around all the exciting database stuff so we
     // can return a 'server_error' response if something goes wrong!
     try {
         $auth_code = id(new PhabricatorOAuthServerAuthorizationCode())->loadOneWhere('code = %s', $code);
         if (!$auth_code) {
             $response->setError('invalid_grant');
             $response->setErrorDescription(pht('Authorization code %s not found.', $code));
             return $response;
         }
         // if we have an auth code redirect URI, there must be a redirect_uri
         // in the request and it must match the auth code redirect uri *exactly*
         $auth_code_redirect_uri = $auth_code->getRedirectURI();
         if ($auth_code_redirect_uri) {
             $auth_code_redirect_uri = new PhutilURI($auth_code_redirect_uri);
             $redirect_uri = new PhutilURI($redirect_uri);
             if (!$redirect_uri->getDomain() || $redirect_uri != $auth_code_redirect_uri) {
                 $response->setError('invalid_grant');
                 $response->setErrorDescription(pht('Redirect URI in request must exactly match redirect URI ' . 'from authorization code.'));
                 return $response;
             }
         } else {
             if ($redirect_uri) {
                 $response->setError('invalid_grant');
                 $response->setErrorDescription(pht('Redirect URI in request and no redirect URI in authorization ' . 'code. The two must exactly match.'));
                 return $response;
             }
         }
         $client = id(new PhabricatorOAuthServerClient())->loadOneWhere('phid = %s', $client_phid);
         if (!$client) {
             $response->setError('invalid_client');
             $response->setErrorDescription(pht('Client with %s %s not found.', 'client_id', $client_phid));
             return $response;
         }
         if ($client->getIsDisabled()) {
             $response->setError('invalid_client');
             $response->setErrorDescription(pht('OAuth application "%s" has been disabled.', $client->getName()));
             return $response;
         }
         $server->setClient($client);
         $user_phid = $auth_code->getUserPHID();
         $user = id(new PhabricatorUser())->loadOneWhere('phid = %s', $user_phid);
         if (!$user) {
             $response->setError('invalid_grant');
             $response->setErrorDescription(pht('User with PHID %s not found.', $user_phid));
             return $response;
         }
         $server->setUser($user);
         $test_code = new PhabricatorOAuthServerAuthorizationCode();
         $test_code->setClientSecret($client_secret);
         $test_code->setClientPHID($client_phid);
         $is_good_code = $server->validateAuthorizationCode($auth_code, $test_code);
         if (!$is_good_code) {
             $response->setError('invalid_grant');
             $response->setErrorDescription(pht('Invalid authorization code %s.', $code));
             return $response;
         }
         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         $access_token = $server->generateAccessToken();
         $auth_code->delete();
         unset($unguarded);
         $result = array('access_token' => $access_token->getToken(), 'token_type' => 'Bearer');
         return $response->setContent($result);
     } catch (Exception $e) {
         $response->setError('server_error');
         $response->setErrorDescription(pht('The authorization server encountered an unexpected condition ' . 'which prevented it from fulfilling the request.'));
         return $response;
     }
 }
 protected function validateTransaction(PhabricatorLiskDAO $object, $type, array $xactions)
 {
     $errors = parent::validateTransaction($object, $type, $xactions);
     switch ($type) {
         case PhabricatorOAuthServerTransaction::TYPE_NAME:
             $missing = $this->validateIsEmptyTextField($object->getName(), $xactions);
             if ($missing) {
                 $error = new PhabricatorApplicationTransactionValidationError($type, pht('Required'), pht('OAuth applications must have a name.'), nonempty(last($xactions), null));
                 $error->setIsMissingFieldError(true);
                 $errors[] = $error;
             }
             break;
         case PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI:
             $missing = $this->validateIsEmptyTextField($object->getRedirectURI(), $xactions);
             if ($missing) {
                 $error = new PhabricatorApplicationTransactionValidationError($type, pht('Required'), pht('OAuth applications must have a valid redirect URI.'), nonempty(last($xactions), null));
                 $error->setIsMissingFieldError(true);
                 $errors[] = $error;
             } else {
                 foreach ($xactions as $xaction) {
                     $redirect_uri = $xaction->getNewValue();
                     try {
                         $server = new PhabricatorOAuthServer();
                         $server->assertValidRedirectURI($redirect_uri);
                     } catch (Exception $ex) {
                         $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), $ex->getMessage(), $xaction);
                     }
                 }
             }
             break;
     }
     return $errors;
 }
 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));
 }
 /**
  * Authenticate the client making the request to a Phabricator user account.
  *
  * @param   ConduitAPIRequest Request being executed.
  * @param   dict              Request metadata.
  * @return  null|pair         Null to indicate successful authentication, or
  *                            an error code and error message pair.
  */
 private function authenticateUser(ConduitAPIRequest $api_request, array $metadata)
 {
     $request = $this->getRequest();
     if ($request->getUser()->getPHID()) {
         $request->validateCSRF();
         return $this->validateAuthenticatedUser($api_request, $request->getUser());
     }
     // handle oauth
     $access_token = $request->getStr('access_token');
     $method_scope = $metadata['scope'];
     if ($access_token && $method_scope != PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE) {
         $token = id(new PhabricatorOAuthServerAccessToken())->loadOneWhere('token = %s', $access_token);
         if (!$token) {
             return array('ERR-INVALID-AUTH', 'Access token does not exist.');
         }
         $oauth_server = new PhabricatorOAuthServer();
         $valid = $oauth_server->validateAccessToken($token, $method_scope);
         if (!$valid) {
             return array('ERR-INVALID-AUTH', 'Access token is invalid.');
         }
         // valid token, so let's log in the user!
         $user_phid = $token->getUserPHID();
         $user = id(new PhabricatorUser())->loadOneWhere('phid = %s', $user_phid);
         if (!$user) {
             return array('ERR-INVALID-AUTH', 'Access token is for invalid user.');
         }
         return $this->validateAuthenticatedUser($api_request, $user);
     }
     // Handle sessionless auth. TOOD: This is super messy.
     if (isset($metadata['authUser'])) {
         $user = id(new PhabricatorUser())->loadOneWhere('userName = %s', $metadata['authUser']);
         if (!$user) {
             return array('ERR-INVALID-AUTH', 'Authentication is invalid.');
         }
         $token = idx($metadata, 'authToken');
         $signature = idx($metadata, 'authSignature');
         $certificate = $user->getConduitCertificate();
         if (sha1($token . $certificate) !== $signature) {
             return array('ERR-INVALID-AUTH', 'Authentication is invalid.');
         }
         return $this->validateAuthenticatedUser($api_request, $user);
     }
     $session_key = idx($metadata, 'sessionKey');
     if (!$session_key) {
         return array('ERR-INVALID-SESSION', 'Session key is not present.');
     }
     $user = id(new PhabricatorAuthSessionEngine())->loadUserForSession(PhabricatorAuthSession::TYPE_CONDUIT, $session_key);
     if (!$user) {
         return array('ERR-INVALID-SESSION', 'Session key is invalid.');
     }
     return $this->validateAuthenticatedUser($api_request, $user);
 }