public function shouldRequireHTTPS() { // If this is an intracluster request, it's okay for it to use HTTP even // if the site otherwise requires HTTPS. It is common to terminate SSL at // a load balancer and use plain HTTP from then on, and administrators are // usually not concerned about attackers observing traffic within a // datacenter. if (PhabricatorEnv::isClusterRemoteAddress()) { return false; } return PhabricatorEnv::getEnvConfig('security.require-https'); }
/** * 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 canEstablishAPISessions() { if ($this->getIsDisabled()) { return false; } // Intracluster requests are permitted even if the user is logged out: // in particular, public users are allowed to issue intracluster requests // when browsing Diffusion. if (PhabricatorEnv::isClusterRemoteAddress()) { if (!$this->isLoggedIn()) { return true; } } if (!$this->isUserActivated()) { return false; } if ($this->getIsMailingList()) { return false; } return true; }