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); }