Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34134343
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
12 KB
Subscribers
None
View Options
diff --git a/src/auth/PhutilAuthAdapterOAuthJIRA.php b/src/auth/PhutilAuthAdapterOAuthJIRA.php
index b01784a..5fd0c92 100644
--- a/src/auth/PhutilAuthAdapterOAuthJIRA.php
+++ b/src/auth/PhutilAuthAdapterOAuthJIRA.php
@@ -1,153 +1,155 @@
<?php
final class PhutilAuthAdapterOAuthJIRA extends PhutilAuthAdapterOAuth1 {
// TODO: JIRA tokens expire (after 5 years) and we could surface and store
// that.
private $jiraBaseURI;
private $adapterDomain;
private $currentSession;
private $userInfo;
public function setJIRABaseURI($jira_base_uri) {
$this->jiraBaseURI = $jira_base_uri;
return $this;
}
public function getJIRABaseURI() {
return $this->jiraBaseURI;
}
public function getAccountID() {
// Make sure the handshake is finished; this method is used for its
// side effect by Auth providers.
$this->getHandshakeData();
return idx($this->getUserInfo(), 'key');
}
public function getAccountName() {
return idx($this->getUserInfo(), 'name');
}
public function getAccountImageURI() {
$avatars = idx($this->getUserInfo(), 'avatarUrls');
if ($avatars) {
return idx($avatars, '48x48');
}
return null;
}
public function getAccountRealName() {
return idx($this->getUserInfo(), 'displayName');
}
public function getAccountEmail() {
return idx($this->getUserInfo(), 'emailAddress');
}
public function getAdapterType() {
return 'jira';
}
public function getAdapterDomain() {
return $this->adapterDomain;
}
public function setAdapterDomain($domain) {
$this->adapterDomain = $domain;
return $this;
}
protected function getSignatureMethod() {
return 'RSA-SHA1';
}
protected function getRequestTokenURI() {
return $this->getJIRAURI('plugins/servlet/oauth/request-token');
}
protected function getAuthorizeTokenURI() {
return $this->getJIRAURI('plugins/servlet/oauth/authorize');
}
protected function getValidateTokenURI() {
return $this->getJIRAURI('plugins/servlet/oauth/access-token');
}
private function getJIRAURI($path) {
return rtrim($this->jiraBaseURI, '/').'/'.ltrim($path, '/');
}
private function getUserInfo() {
if ($this->userInfo === null) {
- $uri = $this->getJIRAURI('rest/auth/1/session');
- $data = $this->newOAuth1Future($uri)
- ->setMethod('GET')
- ->addHeader('Content-Type', 'application/json')
+ $this->currentSession = $this->newJIRAFuture('rest/auth/1/session', 'GET')
->resolveJSON();
- $this->currentSession = $data;
-
// The session call gives us the username, but not the user key or other
// information. Make a second call to get additional information.
- $uri = new PhutilURI($this->getJIRAURI('rest/api/2/user'));
- $uri->setQueryParams(
- array(
- 'username' => $this->currentSession['name'],
- ));
+ $params = array(
+ 'username' => $this->currentSession['name'],
+ );
- $data = $this->newOAuth1Future($uri)
- ->setMethod('GET')
- ->addHeader('Content-Type', 'application/json')
+ $this->userInfo = $this->newJIRAFuture('rest/api/2/user', 'GET', $params)
->resolveJSON();
-
- $this->userInfo = $data;
}
return $this->userInfo;
}
public static function newJIRAKeypair() {
$config = array(
'digest_alg' => 'sha512',
'private_key_bits' => 4096,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
);
$res = openssl_pkey_new($config);
if (!$res) {
throw new Exception('openssl_pkey_new() failed!');
}
$private_key = null;
$ok = openssl_pkey_export($res, $private_key);
if (!$ok) {
throw new Exception('openssl_pkey_export() failed!');
}
$public_key = openssl_pkey_get_details($res);
if (!$ok || empty($public_key['key'])) {
throw new Exception('openssl_pkey_get_details() failed!');
}
$public_key = $public_key['key'];
return array($public_key, $private_key);
}
/**
* JIRA indicates that the user has clicked the "Deny" button by passing a
* well known `oauth_verifier` value ("denied"), which we check for here.
*/
protected function willFinishOAuthHandshake() {
$jira_magic_word = "denied";
if ($this->getVerifier() == $jira_magic_word) {
throw new PhutilAuthUserAbortedException();
}
}
+ public function newJIRAFuture($path, $method, $params = array()) {
+ $uri = new PhutilURI($this->getJIRAURI($path));
+ if ($method == 'GET') {
+ $uri->setQueryParams($params);
+ $params = array();
+ }
+
+ // JIRA returns a 415 error if we don't provide a Content-Type header.
+
+ return $this->newOAuth1Future($uri, $params)
+ ->setMethod($method)
+ ->addHeader('Content-Type', 'application/json');
+ }
}
diff --git a/src/future/oauth/PhutilOAuth1Future.php b/src/future/oauth/PhutilOAuth1Future.php
index 1a511c4..d474232 100644
--- a/src/future/oauth/PhutilOAuth1Future.php
+++ b/src/future/oauth/PhutilOAuth1Future.php
@@ -1,255 +1,269 @@
<?php
/**
* Proxy future that implements OAuth1 request signing. For references, see:
*
* RFC 5849: http://tools.ietf.org/html/rfc5849
* Guzzle: https://github.com/guzzle/guzzle/blob/master/src/Guzzle/Plugin/Oauth/OauthPlugin.php
*
*/
final class PhutilOAuth1Future extends FutureProxy {
private $uri;
private $data;
private $consumerKey;
private $consumerSecret;
private $signatureMethod;
private $privateKey;
private $method = 'POST';
private $token;
private $tokenSecret;
private $nonce;
private $timestamp;
private $hasConstructedFuture;
private $callbackURI;
+ private $headers = array();
public function setCallbackURI($callback_uri) {
$this->callbackURI = $callback_uri;
return $this;
}
public function setTimestamp($timestamp) {
$this->timestamp = $timestamp;
return $this;
}
public function setNonce($nonce) {
$this->nonce = $nonce;
return $this;
}
public function setTokenSecret($token_secret) {
$this->tokenSecret = $token_secret;
return $this;
}
public function setToken($token) {
$this->token = $token;
return $this;
}
public function setPrivateKey(PhutilOpaqueEnvelope $private_key) {
$this->privateKey = $private_key;
return $this;
}
public function setSignatureMethod($signature_method) {
$this->signatureMethod = $signature_method;
return $this;
}
public function setConsumerKey($consumer_key) {
$this->consumerKey = $consumer_key;
return $this;
}
public function setConsumerSecret(PhutilOpaqueEnvelope $consumer_secret) {
$this->consumerSecret = $consumer_secret;
return $this;
}
public function setMethod($method) {
$this->method = $method;
return $this;
}
public function __construct($uri, $data = array()) {
$this->uri = new PhutilURI((string)$uri);
$this->data = $data;
$this->setProxiedFuture(new HTTPSFuture($uri, $data));
}
public function getSignature() {
$params = $this->data
+ $this->uri->getQueryParams()
+ $this->getOAuth1Headers();
return $this->sign($params);
}
public function addHeader($name, $value) {
- $this->getProxiedFuture()->addHeader($name, $value);
+ // If we haven't built the future yet, hold on to the header until after
+ // we do, since there might be more changes coming which will affect the
+ // signature process.
+
+ if (!$this->hasConstructedFuture) {
+ $this->headers[] = array($name, $value);
+ } else {
+ $this->getProxiedFuture()->addHeader($name, $value);
+ }
return $this;
}
public function getProxiedFuture() {
$future = parent::getProxiedFuture();
if (!$this->hasConstructedFuture) {
$future->setMethod($this->method);
$oauth_headers = $this->getOAuth1Headers();
$oauth_headers['oauth_signature'] = $this->getSignature();
$full_oauth_header = array();
foreach ($oauth_headers as $header => $value) {
$full_oauth_header[] = $header.'="'.urlencode($value).'"';
}
$full_oauth_header = 'OAuth '.implode(", ", $full_oauth_header);
$future->addHeader('Authorization', $full_oauth_header);
+ foreach ($this->headers as $header) {
+ $future->addHeader($header[0], $header[1]);
+ }
+ $this->headers = array();
+
$this->hasConstructedFuture = true;
}
return $future;
}
protected function didReceiveResult($result) {
return $result;
}
private function getOAuth1Headers() {
if (!$this->nonce) {
$this->nonce = Filesystem::readRandomCharacters(32);
}
if (!$this->timestamp) {
$this->timestamp = time();
}
$oauth_headers = array(
'oauth_consumer_key' => $this->consumerKey,
'oauth_signature_method' => $this->signatureMethod,
'oauth_timestamp' => $this->timestamp,
'oauth_nonce' => $this->nonce,
'oauth_version' => '1.0',
);
if ($this->callbackURI) {
$oauth_headers['oauth_callback'] = (string)$this->callbackURI;
}
if ($this->token) {
$oauth_headers['oauth_token'] = $this->token;
}
return $oauth_headers;
}
private function sign(array $params) {
ksort($params);
$pstr = array();
foreach ($params as $key => $value) {
$pstr[] = rawurlencode($key).'='.rawurlencode($value);
}
$pstr = implode('&', $pstr);
$sign_uri = clone $this->uri;
$sign_uri->setFragment('');
$sign_uri->setQueryParams(array());
$sign_uri->setProtocol(phutil_utf8_strtolower($sign_uri->getProtocol()));
$protocol = $sign_uri->getProtocol();
switch ($protocol) {
case 'http':
if ($sign_uri->getPort() == 80) {
$sign_uri->setPort(null);
}
break;
case 'https':
if ($sign_uri->getPort() == 443) {
$sign_uri->setPort(null);
}
break;
}
$method = rawurlencode(phutil_utf8_strtoupper($this->method));
$sign_uri = rawurlencode((string)$sign_uri);
$pstr = rawurlencode($pstr);
$sign_input = "{$method}&{$sign_uri}&{$pstr}";
return $this->signString($sign_input);
}
private function signString($string) {
$consumer_secret = null;
if ($this->consumerSecret) {
$consumer_secret = $this->consumerSecret->openEnvelope();
}
$key = urlencode($consumer_secret).'&'.urlencode($this->tokenSecret);
switch ($this->signatureMethod) {
case 'HMAC-SHA1':
if (!$this->consumerSecret) {
throw new Exception(
"Signature method 'HMAC-SHA1' requires setConsumerSecret()!");
}
$hash = hash_hmac('sha1', $string, $key, true);
return base64_encode($hash);
case 'RSA-SHA1':
if (!$this->privateKey) {
throw new Exception(
"Signature method 'RSA-SHA1' requires setPrivateKey()!");
}
$cert = @openssl_pkey_get_private($this->privateKey->openEnvelope());
if (!$cert) {
throw new Exception('openssl_pkey_get_private() failed!');
}
$pkey = @openssl_get_privatekey($cert);
if (!$pkey) {
throw new Exception('openssl_get_privatekey() failed!');
}
$signature = null;
$ok = openssl_sign($string, $signature, $pkey, OPENSSL_ALGO_SHA1);
if (!$ok) {
throw new Exception('openssl_sign() failed!');
}
openssl_free_key($pkey);
return base64_encode($signature);
case 'PLAINTEXT':
if (!$this->consumerSecret) {
throw new Exception(
"Signature method 'PLAINTEXT' requires setConsumerSecret()!");
}
return $key;
default:
throw new Exception("Unknown signature method '{$string}'!");
}
}
public function resolvex() {
$result = $this->getProxiedFuture()->resolvex();
return $this->didReceiveResult($result);
}
public function resolveJSON() {
$result = $this->getProxiedFuture()->resolvex();
$result = $this->didReceiveResult($result);
list($body) = $result;
$data = json_decode($body, true);
if (!is_array($data)) {
throw new Exception("Expected JSON, got: {$body}!");
}
return $data;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Dec 8, 7:12 AM (1 d, 2 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
f8/fd/bcde109c8d149c9bd43b99fb029d
Attached To
rPHUTIL libphutil
Event Timeline
Log In to Comment