[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/oauthserver/ -> PhabricatorOAuthServer.php (source)

   1  <?php
   2  
   3  /**
   4   * Implements core OAuth 2.0 Server logic.
   5   *
   6   * This class should be used behind business logic that parses input to
   7   * determine pertinent @{class:PhabricatorUser} $user,
   8   * @{class:PhabricatorOAuthServerClient} $client(s),
   9   * @{class:PhabricatorOAuthServerAuthorizationCode} $code(s), and.
  10   * @{class:PhabricatorOAuthServerAccessToken} $token(s).
  11   *
  12   * For an OAuth 2.0 server, there are two main steps:
  13   *
  14   * 1) Authorization - the user authorizes a given client to access the data
  15   * the OAuth 2.0 server protects. Once this is achieved / if it has
  16   * been achived already, the OAuth server sends the client an authorization
  17   * code.
  18   * 2) Access Token - the client should send the authorization code received in
  19   * step 1 along with its id and secret to the OAuth server to receive an
  20   * access token. This access token can later be used to access Phabricator
  21   * data on behalf of the user.
  22   *
  23   * @task auth Authorizing @{class:PhabricatorOAuthServerClient}s and
  24   *            generating @{class:PhabricatorOAuthServerAuthorizationCode}s
  25   * @task token Validating @{class:PhabricatorOAuthServerAuthorizationCode}s
  26   *             and generating @{class:PhabricatorOAuthServerAccessToken}s
  27   * @task internal Internals
  28   */
  29  final class PhabricatorOAuthServer {
  30  
  31    const AUTHORIZATION_CODE_TIMEOUT = 300;
  32    const ACCESS_TOKEN_TIMEOUT       = 3600;
  33  
  34    private $user;
  35    private $client;
  36  
  37    private function getUser() {
  38      if (!$this->user) {
  39        throw new Exception('You must setUser before you can getUser!');
  40      }
  41      return $this->user;
  42    }
  43  
  44    public function setUser(PhabricatorUser $user) {
  45      $this->user = $user;
  46      return $this;
  47    }
  48  
  49    private function getClient() {
  50      if (!$this->client) {
  51        throw new Exception('You must setClient before you can getClient!');
  52      }
  53      return $this->client;
  54    }
  55  
  56    public function setClient(PhabricatorOAuthServerClient $client) {
  57      $this->client = $client;
  58      return $this;
  59    }
  60  
  61    /**
  62     * @task auth
  63     * @return tuple <bool hasAuthorized, ClientAuthorization or null>
  64     */
  65    public function userHasAuthorizedClient(array $scope) {
  66  
  67      $authorization = id(new PhabricatorOAuthClientAuthorization())->
  68        loadOneWhere('userPHID = %s AND clientPHID = %s',
  69                     $this->getUser()->getPHID(),
  70                     $this->getClient()->getPHID());
  71      if (empty($authorization)) {
  72        return array(false, null);
  73      }
  74  
  75      if ($scope) {
  76        $missing_scope = array_diff_key($scope,
  77                                        $authorization->getScope());
  78      } else {
  79        $missing_scope = false;
  80      }
  81  
  82      if ($missing_scope) {
  83        return array(false, $authorization);
  84      }
  85  
  86      return array(true, $authorization);
  87    }
  88  
  89    /**
  90     * @task auth
  91     */
  92    public function authorizeClient(array $scope) {
  93      $authorization = new PhabricatorOAuthClientAuthorization();
  94      $authorization->setUserPHID($this->getUser()->getPHID());
  95      $authorization->setClientPHID($this->getClient()->getPHID());
  96      $authorization->setScope($scope);
  97      $authorization->save();
  98  
  99      return $authorization;
 100    }
 101  
 102    /**
 103     * @task auth
 104     */
 105    public function generateAuthorizationCode(PhutilURI $redirect_uri) {
 106  
 107      $code   = Filesystem::readRandomCharacters(32);
 108      $client = $this->getClient();
 109  
 110      $authorization_code = new PhabricatorOAuthServerAuthorizationCode();
 111      $authorization_code->setCode($code);
 112      $authorization_code->setClientPHID($client->getPHID());
 113      $authorization_code->setClientSecret($client->getSecret());
 114      $authorization_code->setUserPHID($this->getUser()->getPHID());
 115      $authorization_code->setRedirectURI((string) $redirect_uri);
 116      $authorization_code->save();
 117  
 118      return $authorization_code;
 119    }
 120  
 121    /**
 122     * @task token
 123     */
 124    public function generateAccessToken() {
 125  
 126      $token = Filesystem::readRandomCharacters(32);
 127  
 128      $access_token = new PhabricatorOAuthServerAccessToken();
 129      $access_token->setToken($token);
 130      $access_token->setUserPHID($this->getUser()->getPHID());
 131      $access_token->setClientPHID($this->getClient()->getPHID());
 132      $access_token->save();
 133  
 134      return $access_token;
 135    }
 136  
 137    /**
 138     * @task token
 139     */
 140    public function validateAuthorizationCode(
 141      PhabricatorOAuthServerAuthorizationCode $test_code,
 142      PhabricatorOAuthServerAuthorizationCode $valid_code) {
 143  
 144      // check that all the meta data matches
 145      if ($test_code->getClientPHID() != $valid_code->getClientPHID()) {
 146        return false;
 147      }
 148      if ($test_code->getClientSecret() != $valid_code->getClientSecret()) {
 149        return false;
 150      }
 151  
 152      // check that the authorization code hasn't timed out
 153      $created_time = $test_code->getDateCreated();
 154      $must_be_used_by = $created_time + self::AUTHORIZATION_CODE_TIMEOUT;
 155      return (time() < $must_be_used_by);
 156    }
 157  
 158    /**
 159     * @task token
 160     */
 161    public function validateAccessToken(
 162      PhabricatorOAuthServerAccessToken $token,
 163      $required_scope) {
 164  
 165      $created_time    = $token->getDateCreated();
 166      $must_be_used_by = $created_time + self::ACCESS_TOKEN_TIMEOUT;
 167      $expired         = time() > $must_be_used_by;
 168      $authorization   = id(new PhabricatorOAuthClientAuthorization())
 169                           ->loadOneWhere(
 170                             'userPHID = %s AND clientPHID = %s',
 171                             $token->getUserPHID(),
 172                             $token->getClientPHID());
 173  
 174      if (!$authorization) {
 175        return false;
 176      }
 177      $token_scope = $authorization->getScope();
 178      if (!isset($token_scope[$required_scope])) {
 179        return false;
 180      }
 181  
 182      $valid = true;
 183      if ($expired) {
 184        $valid = false;
 185        // check if the scope includes "offline_access", which makes the
 186        // token valid despite being expired
 187        if (isset(
 188          $token_scope[PhabricatorOAuthServerScope::SCOPE_OFFLINE_ACCESS])) {
 189          $valid = true;
 190        }
 191      }
 192  
 193      return $valid;
 194    }
 195  
 196    /**
 197     * See http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-3.1.2
 198     * for details on what makes a given redirect URI "valid".
 199     */
 200    public function validateRedirectURI(PhutilURI $uri) {
 201      if (!PhabricatorEnv::isValidRemoteWebResource($uri)) {
 202        return false;
 203      }
 204  
 205      if ($uri->getFragment()) {
 206        return false;
 207      }
 208  
 209      if (!$uri->getDomain()) {
 210        return false;
 211      }
 212  
 213      return true;
 214    }
 215  
 216    /**
 217     * If there's a URI specified in an OAuth request, it must be validated in
 218     * its own right. Further, it must have the same domain and (at least) the
 219     * same query parameters as the primary URI.
 220     */
 221    public function validateSecondaryRedirectURI(
 222      PhutilURI $secondary_uri,
 223      PhutilURI $primary_uri) {
 224  
 225      // The secondary URI must be valid.
 226      if (!$this->validateRedirectURI($secondary_uri)) {
 227        return false;
 228      }
 229  
 230      // Both URIs must point at the same domain.
 231      if ($secondary_uri->getDomain() != $primary_uri->getDomain()) {
 232        return false;
 233      }
 234  
 235      // Any query parameters present in the first URI must be exactly present
 236      // in the second URI.
 237      $need_params = $primary_uri->getQueryParams();
 238      $have_params = $secondary_uri->getQueryParams();
 239  
 240      foreach ($need_params as $key => $value) {
 241        if (!array_key_exists($key, $have_params)) {
 242          return false;
 243        }
 244        if ((string)$have_params[$key] != (string)$value) {
 245          return false;
 246        }
 247      }
 248  
 249      // If the first URI is HTTPS, the second URI must also be HTTPS. This
 250      // defuses an attack where a third party with control over the network
 251      // tricks you into using HTTP to authenticate over a link which is supposed
 252      // to be HTTPS only and sniffs all your token cookies.
 253      if (strtolower($primary_uri->getProtocol()) == 'https') {
 254        if (strtolower($secondary_uri->getProtocol()) != 'https') {
 255          return false;
 256        }
 257      }
 258  
 259      return true;
 260    }
 261  
 262  }


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