Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/oauthserver/PhabricatorOAuthServer.php b/src/applications/oauthserver/PhabricatorOAuthServer.php
index a903c554f5..049c1cd0c2 100644
--- a/src/applications/oauthserver/PhabricatorOAuthServer.php
+++ b/src/applications/oauthserver/PhabricatorOAuthServer.php
@@ -1,262 +1,272 @@
<?php
/**
* Implements core OAuth 2.0 Server logic.
*
* This class should be used behind business logic that parses input to
* determine pertinent @{class:PhabricatorUser} $user,
* @{class:PhabricatorOAuthServerClient} $client(s),
* @{class:PhabricatorOAuthServerAuthorizationCode} $code(s), and.
* @{class:PhabricatorOAuthServerAccessToken} $token(s).
*
* For an OAuth 2.0 server, there are two main steps:
*
* 1) Authorization - the user authorizes a given client to access the data
* the OAuth 2.0 server protects. Once this is achieved / if it has
* been achived already, the OAuth server sends the client an authorization
* code.
* 2) Access Token - the client should send the authorization code received in
* step 1 along with its id and secret to the OAuth server to receive an
* access token. This access token can later be used to access Phabricator
* data on behalf of the user.
*
* @task auth Authorizing @{class:PhabricatorOAuthServerClient}s and
* generating @{class:PhabricatorOAuthServerAuthorizationCode}s
* @task token Validating @{class:PhabricatorOAuthServerAuthorizationCode}s
* and generating @{class:PhabricatorOAuthServerAccessToken}s
* @task internal Internals
*/
final class PhabricatorOAuthServer {
const AUTHORIZATION_CODE_TIMEOUT = 300;
const ACCESS_TOKEN_TIMEOUT = 3600;
private $user;
private $client;
private function getUser() {
if (!$this->user) {
throw new Exception('You must setUser before you can getUser!');
}
return $this->user;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
private function getClient() {
if (!$this->client) {
throw new Exception('You must setClient before you can getClient!');
}
return $this->client;
}
public function setClient(PhabricatorOAuthServerClient $client) {
$this->client = $client;
return $this;
}
/**
* @task auth
* @return tuple <bool hasAuthorized, ClientAuthorization or null>
*/
public function userHasAuthorizedClient(array $scope) {
$authorization = id(new PhabricatorOAuthClientAuthorization())->
loadOneWhere('userPHID = %s AND clientPHID = %s',
$this->getUser()->getPHID(),
$this->getClient()->getPHID());
if (empty($authorization)) {
return array(false, null);
}
if ($scope) {
$missing_scope = array_diff_key($scope,
$authorization->getScope());
} else {
$missing_scope = false;
}
if ($missing_scope) {
return array(false, $authorization);
}
return array(true, $authorization);
}
/**
* @task auth
*/
public function authorizeClient(array $scope) {
$authorization = new PhabricatorOAuthClientAuthorization();
$authorization->setUserPHID($this->getUser()->getPHID());
$authorization->setClientPHID($this->getClient()->getPHID());
$authorization->setScope($scope);
$authorization->save();
return $authorization;
}
/**
* @task auth
*/
public function generateAuthorizationCode(PhutilURI $redirect_uri) {
$code = Filesystem::readRandomCharacters(32);
$client = $this->getClient();
$authorization_code = new PhabricatorOAuthServerAuthorizationCode();
$authorization_code->setCode($code);
$authorization_code->setClientPHID($client->getPHID());
$authorization_code->setClientSecret($client->getSecret());
$authorization_code->setUserPHID($this->getUser()->getPHID());
$authorization_code->setRedirectURI((string) $redirect_uri);
$authorization_code->save();
return $authorization_code;
}
/**
* @task token
*/
public function generateAccessToken() {
$token = Filesystem::readRandomCharacters(32);
$access_token = new PhabricatorOAuthServerAccessToken();
$access_token->setToken($token);
$access_token->setUserPHID($this->getUser()->getPHID());
$access_token->setClientPHID($this->getClient()->getPHID());
$access_token->save();
return $access_token;
}
/**
* @task token
*/
public function validateAuthorizationCode(
PhabricatorOAuthServerAuthorizationCode $test_code,
PhabricatorOAuthServerAuthorizationCode $valid_code) {
// check that all the meta data matches
if ($test_code->getClientPHID() != $valid_code->getClientPHID()) {
return false;
}
if ($test_code->getClientSecret() != $valid_code->getClientSecret()) {
return false;
}
// check that the authorization code hasn't timed out
$created_time = $test_code->getDateCreated();
$must_be_used_by = $created_time + self::AUTHORIZATION_CODE_TIMEOUT;
return (time() < $must_be_used_by);
}
/**
* @task token
*/
public function validateAccessToken(
PhabricatorOAuthServerAccessToken $token,
$required_scope) {
$created_time = $token->getDateCreated();
$must_be_used_by = $created_time + self::ACCESS_TOKEN_TIMEOUT;
$expired = time() > $must_be_used_by;
$authorization = id(new PhabricatorOAuthClientAuthorization())
->loadOneWhere(
'userPHID = %s AND clientPHID = %s',
$token->getUserPHID(),
$token->getClientPHID());
if (!$authorization) {
return false;
}
$token_scope = $authorization->getScope();
if (!isset($token_scope[$required_scope])) {
return false;
}
$valid = true;
if ($expired) {
$valid = false;
// check if the scope includes "offline_access", which makes the
// token valid despite being expired
if (isset(
$token_scope[PhabricatorOAuthServerScope::SCOPE_OFFLINE_ACCESS])) {
$valid = true;
}
}
return $valid;
}
/**
* See http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-3.1.2
* for details on what makes a given redirect URI "valid".
*/
public function validateRedirectURI(PhutilURI $uri) {
if (!PhabricatorEnv::isValidRemoteWebResource($uri)) {
return false;
}
if ($uri->getFragment()) {
return false;
}
if (!$uri->getDomain()) {
return false;
}
return true;
}
/**
* If there's a URI specified in an OAuth request, it must be validated in
- * its own right. Further, it must have the same domain and (at least) the
- * same query parameters as the primary URI.
+ * its own right. Further, it must have the same domain, the same path, the
+ * same port, and (at least) the same query parameters as the primary URI.
*/
public function validateSecondaryRedirectURI(
PhutilURI $secondary_uri,
PhutilURI $primary_uri) {
// The secondary URI must be valid.
if (!$this->validateRedirectURI($secondary_uri)) {
return false;
}
// Both URIs must point at the same domain.
if ($secondary_uri->getDomain() != $primary_uri->getDomain()) {
return false;
}
+ // Both URIs must have the same path
+ if ($secondary_uri->getPath() != $primary_uri->getPath()) {
+ return false;
+ }
+
+ // Both URIs must have the same port
+ if ($secondary_uri->getPort() != $primary_uri->getPort()) {
+ return false;
+ }
+
// Any query parameters present in the first URI must be exactly present
// in the second URI.
$need_params = $primary_uri->getQueryParams();
$have_params = $secondary_uri->getQueryParams();
foreach ($need_params as $key => $value) {
if (!array_key_exists($key, $have_params)) {
return false;
}
if ((string)$have_params[$key] != (string)$value) {
return false;
}
}
// If the first URI is HTTPS, the second URI must also be HTTPS. This
// defuses an attack where a third party with control over the network
// tricks you into using HTTP to authenticate over a link which is supposed
// to be HTTPS only and sniffs all your token cookies.
if (strtolower($primary_uri->getProtocol()) == 'https') {
if (strtolower($secondary_uri->getProtocol()) != 'https') {
return false;
}
}
return true;
}
}
diff --git a/src/applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php b/src/applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php
index d9589e7149..30fea69a94 100644
--- a/src/applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php
+++ b/src/applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php
@@ -1,96 +1,96 @@
<?php
final class PhabricatorOAuthServerTestCase
extends PhabricatorTestCase {
public function testValidateRedirectURI() {
static $map = array(
'http://www.google.com' => true,
'http://www.google.com/' => true,
'http://www.google.com/auth' => true,
'www.google.com' => false,
'http://www.google.com/auth#invalid' => false,
);
$server = new PhabricatorOAuthServer();
foreach ($map as $input => $expected) {
$uri = new PhutilURI($input);
$result = $server->validateRedirectURI($uri);
$this->assertEqual(
$expected,
$result,
"Validation of redirect URI '{$input}'");
}
}
public function testValidateSecondaryRedirectURI() {
$server = new PhabricatorOAuthServer();
- $primary_uri = new PhutilURI('http://www.google.com');
+ $primary_uri = new PhutilURI('http://www.google.com/');
static $test_domain_map = array(
- 'http://www.google.com' => true,
+ 'http://www.google.com' => false,
'http://www.google.com/' => true,
- 'http://www.google.com/auth' => true,
+ 'http://www.google.com/auth' => false,
'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,
+ '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}");
}
}
}
diff --git a/src/docs/contributor/using_oauthserver.diviner b/src/docs/contributor/using_oauthserver.diviner
index f460ed52bb..dc8b377671 100644
--- a/src/docs/contributor/using_oauthserver.diviner
+++ b/src/docs/contributor/using_oauthserver.diviner
@@ -1,120 +1,120 @@
@title Using the Phabricator OAuth Server
@group developer
How to use the Phabricator OAuth Server.
= Overview =
Phabricator includes an OAuth Server which supports the
`Authorization Code Grant` flow as described in the OAuth 2.0
specification:
http://tools.ietf.org/html/draft-ietf-oauth-v2-23
This functionality can allow clients to integrate with a given
Phabricator instance in a secure way with granular data access.
For example, Phabricator can be used as a central identity store for any
clients that implement OAuth 2.0.
= Vocabulary =
-- **Access token** - a token which allows a client to ask for data on
-behalf of a resource owner. A given client will only be able to access
-data included in the scope(s) the resource owner authorized that client for.
+- **Access token** - a token which allows a client to ask for data on behalf
+ of a resource owner. A given client will only be able to access data included
+ in the scope(s) the resource owner authorized that client for.
- **Authorization code** - a short-lived code which allows an authenticated
-client to ask for an access token on behalf of some resource owner.
+ client to ask for an access token on behalf of some resource owner.
- **Client** - this is the application or system asking for data from the
-OAuth Server on behalf of the resource owner.
+ OAuth Server on behalf of the resource owner.
- **Resource owner** - this is the user the client and OAuth Server are
-concerned with on a given request.
+ concerned with on a given request.
- **Scope** - this defines a specific piece of granular data a client can
-or can not access on behalf of a user. For example, if authorized for the
-"whoami" scope on behalf of a given resource owner, the client can get the
-results of Conduit.whoami for that resource owner when authenticated with
-a valid access token.
+ or can not access on behalf of a user. For example, if authorized for the
+ "whoami" scope on behalf of a given resource owner, the client can get the
+ results of Conduit.whoami for that resource owner when authenticated with
+ a valid access token.
= Setup - Creating a Client =
# Visit https://phabricator.example.com/oauthserver/client/create/
# Fill out the form
# Profit
= Obtaining an Authorization Code =
POST or GET https://phabricator.example.com/oauthserver/auth/ with the
following parameters:
- Required - **client_id** - the id of the newly registered client.
- Required - **response_type** - the desired type of authorization code
-response. Only code is supported at this time.
+ response. Only code is supported at this time.
- Optional - **redirect_uri** - override the redirect_uri the client
-registered. This redirect_uri must have the same fully-qualified domain
-and have at least the same query parameters as the redirect_uri the client
-registered, as well as have no fragments.
+ registered. This redirect_uri must have the same fully-qualified domain,
+ path, port and have at least the same query parameters as the redirect_uri
+ the client registered, as well as have no fragments.
- Optional - **scope** - specify what scope(s) the client needs access to
-in a space-delimited list.
+ in a space-delimited list.
- Optional - **state** - an opaque value the client can send to the server
-for programmatic excellence. Some clients use this value to implement XSRF
-protection or for debugging purposes.
+ for programmatic excellence. Some clients use this value to implement XSRF
+ protection or for debugging purposes.
If done correctly and the resource owner has not yet authorized the client
for the desired scope, then the resource owner will be presented with an
interface to authorize the client for the desired scope. The OAuth Server
will redirect to the pertinent redirect_uri with an authorization code or
an error indicating the resource owner did not authorize the client, depending.
If done correctly and the resource owner has already authorized the client for
the desired scope, then the OAuth Server will redirect to the pertinent
redirect_uri with a valid authorization code.
If there is an error, the OAuth Server will return a descriptive error
message. This error will be presented to the resource owner on the
Phabricator domain if there is reason to believe there is something fishy
with the client. For example, if there is an issue with the redirect_uri.
Otherwise, the OAuth Server will redirect to the pertinent redirect_uri
and include the pertinent error information.
= Obtaining an Access Token =
POST or GET https://phabricator.example.com/oauthserver/token/
with the following parameters:
- Required - **client_id** - the id of the client
- Required - **client_secret** - the secret of the client.
-This is used to authenticate the client.
+ This is used to authenticate the client.
- Required - **code** - the authorization code obtained earlier.
- Required - **grant_type** - the desired type of access grant.
-Only token is supported at this time.
+ Only token is supported at this time.
- Optional - **redirect_uri** - should be the exact same redirect_uri as
-the redirect_uri specified to obtain the authorization code. If no
-redirect_uri was specified to obtain the authorization code then this
-should not be specified.
+ the redirect_uri specified to obtain the authorization code. If no
+ redirect_uri was specified to obtain the authorization code then this
+ should not be specified.
If done correctly, the OAuth Server will redirect to the pertinent
redirect_uri with an access token.
If there is an error, the OAuth Server will return a descriptive error
message.
= Using an Access Token =
Simply include a query param with the key of "access_token" and the value
as the earlier obtained access token. For example:
https://phabricator.example.com/api/user.whoami?access_token=ykc7ly7vtibj334oga4fnfbuvnwz4ocp
If the token has expired or is otherwise invalid, the client will receive
an error indicating as such. In these cases, the client should re-initiate
the entire ##Authorization Code Grant## flow.
NOTE: See "Scopes" section below for more information on what data is
currently exposed through the OAuth Server.
= Scopes =
There are only two scopes supported at this time.
- **offline_access** - allows an access token to work indefinitely without
-expiring.
+ expiring.
- **whoami** - allows the client to access the results of Conduit.whoami on
-behalf of the resource owner.
+ behalf of the resource owner.

File Metadata

Mime Type
text/x-diff
Expires
Thu, Nov 6, 5:02 AM (13 h, 14 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
321647
Default Alt Text
(19 KB)

Event Timeline