Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/oauthserver/controller/auth/PhabricatorOAuthServerAuthController.php b/src/applications/oauthserver/controller/auth/PhabricatorOAuthServerAuthController.php
index 1fc10c09ef..373c9e167c 100644
--- a/src/applications/oauthserver/controller/auth/PhabricatorOAuthServerAuthController.php
+++ b/src/applications/oauthserver/controller/auth/PhabricatorOAuthServerAuthController.php
@@ -1,215 +1,225 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group oauthserver
*/
final class PhabricatorOAuthServerAuthController
extends PhabricatorAuthController {
public function shouldRequireLogin() {
return true;
}
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);
}
- $authorization = $server->userHasAuthorizedClient($scope);
- if ($authorization) {
+ 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);
- $authorization = $server->authorizeClient($scope);
+ 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->setClientURI($uri);
+ 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) {
- $desired_scopes = $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 = $this->getClientURI($client, $redirect_uri);
+ $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()
+ PhabricatorOAuthServerScope::getCheckboxControl($desired_scopes)
)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Authorize')
->addCancelButton($cancel_uri)
);
$panel->appendChild($form);
return $this->buildStandardPageResponse(
$panel,
array('title' => $title));
}
}
diff --git a/src/applications/oauthserver/controller/client/edit/PhabricatorOAuthClientEditController.php b/src/applications/oauthserver/controller/client/edit/PhabricatorOAuthClientEditController.php
index 9d3063274b..ab3201636e 100644
--- a/src/applications/oauthserver/controller/client/edit/PhabricatorOAuthClientEditController.php
+++ b/src/applications/oauthserver/controller/client/edit/PhabricatorOAuthClientEditController.php
@@ -1,196 +1,198 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group oauthserver
*/
final class PhabricatorOAuthClientEditController
extends PhabricatorOAuthClientBaseController {
private $isEdit;
protected function isClientEdit() {
return $this->isEdit;
}
private function setIsClientEdit($is_edit) {
$this->isEdit = (bool) $is_edit;
return $this;
}
protected function getExtraClientFilters() {
if ($this->isClientEdit()) {
$filters = array(
array('url' => $this->getFilter(),
'label' => 'Edit Client')
);
} else {
$filters = array();
}
return $filters;
}
public function getFilter() {
if ($this->isClientEdit()) {
$filter = 'client/edit/'.$this->getClientPHID();
} else {
$filter = 'client/create';
}
return $filter;
}
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)
);
}
}
diff --git a/src/applications/oauthserver/scope/PhabricatorOAuthServerScope.php b/src/applications/oauthserver/scope/PhabricatorOAuthServerScope.php
index d57905a091..2a34f3675c 100644
--- a/src/applications/oauthserver/scope/PhabricatorOAuthServerScope.php
+++ b/src/applications/oauthserver/scope/PhabricatorOAuthServerScope.php
@@ -1,117 +1,123 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorOAuthServerScope {
const SCOPE_OFFLINE_ACCESS = 'offline_access';
const SCOPE_WHOAMI = 'whoami';
const SCOPE_NOT_ACCESSIBLE = 'not_accessible';
/*
* Note this does not contain SCOPE_NOT_ACCESSIBLE which is magic
* used to simplify code for data that is not currently accessible
* via OAuth.
*/
static public function getScopesDict() {
return array(
self::SCOPE_OFFLINE_ACCESS => 1,
self::SCOPE_WHOAMI => 1,
);
}
static public function getCheckboxControl($current_scopes) {
$scopes = self::getScopesDict();
$scope_keys = array_keys($scopes);
sort($scope_keys);
$checkboxes = new AphrontFormCheckboxControl();
foreach ($scope_keys as $scope) {
$checkboxes->addCheckbox(
$name = $scope,
$value = 1,
$label = self::getCheckboxLabel($scope),
$checked = isset($current_scopes[$scope])
);
}
$checkboxes->setLabel('Scope');
return $checkboxes;
}
static private function getCheckboxLabel($scope) {
$label = null;
switch ($scope) {
case self::SCOPE_OFFLINE_ACCESS:
$label = 'Make access tokens granted to this client never expire.';
break;
case self::SCOPE_WHOAMI:
$label = 'Read access to Conduit method user.whoami.';
break;
}
return $label;
}
static public function getScopesFromRequest(AphrontRequest $request) {
$scopes = self::getScopesDict();
$requested_scopes = array();
foreach ($scopes as $scope => $bit) {
if ($request->getBool($scope)) {
$requested_scopes[$scope] = 1;
}
}
return $requested_scopes;
}
/**
* A scopes list is considered valid if each scope is a known scope
* and each scope is seen only once. Otherwise, the list is invalid.
*/
static public function validateScopesList($scope_list) {
$scopes = explode(' ', $scope_list);
$known_scopes = self::getScopesDict();
$seen_scopes = array();
foreach ($scopes as $scope) {
if (!isset($known_scopes[$scope])) {
return false;
}
if (isset($seen_scopes[$scope])) {
return false;
}
$seen_scopes[$scope] = 1;
}
return true;
}
/**
* A scopes dictionary is considered valid if each key is a known scope.
* Otherwise, the dictionary is invalid.
*/
static public function validateScopesDict($scope_dict) {
$known_scopes = self::getScopesDict();
$unknown_scopes = array_diff_key($scope_dict,
$known_scopes);
return empty($unknown_scopes);
}
- static public function scopesListToDict($scope_list) {
- return array_fill_keys($scope_list, 1);
+ /**
+ * Transforms a space-delimited scopes list into a scopes dict. The list
+ * should be validated by @{method:validateScopesList} before
+ * transformation.
+ */
+ static public function scopesListToDict($scope_list) {
+ $scopes = explode(' ', $scope_list);
+ return array_fill_keys($scopes, 1);
}
}
diff --git a/src/applications/oauthserver/server/PhabricatorOAuthServer.php b/src/applications/oauthserver/server/PhabricatorOAuthServer.php
index ea9d15859c..6f4de14fd7 100644
--- a/src/applications/oauthserver/server/PhabricatorOAuthServer.php
+++ b/src/applications/oauthserver/server/PhabricatorOAuthServer.php
@@ -1,252 +1,256 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* 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
*
* @group oauthserver
*/
final class PhabricatorOAuthServer {
const AUTHORIZATION_CODE_TIMEOUT = 300;
const ACCESS_TOKEN_TIMEOUT = 3600;
private $user;
private $client;
/**
* @group internal
*/
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;
}
/**
* @group internal
*/
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 false;
+ return array(false, $authorization);
}
- return $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)) {
if ($uri->getFragment()) {
return false;
}
if ($uri->getDomain()) {
return true;
}
}
return false;
}
/**
* 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.
*/
public function validateSecondaryRedirectURI(PhutilURI $secondary_uri,
PhutilURI $primary_uri) {
$valid = $this->validateRedirectURI($secondary_uri);
if ($valid) {
$valid_domain = ($secondary_uri->getDomain() ==
$primary_uri->getDomain());
$good_params = $primary_uri->getQueryParams();
$test_params = $secondary_uri->getQueryParams();
$missing_params = array_diff_key($good_params, $test_params);
$valid = $valid_domain && empty($missing_params);
}
return $valid;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 20, 9:44 AM (1 h, 19 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
241611
Default Alt Text
(27 KB)

Event Timeline