Page MenuHome GnuPG

No OneTemporary

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

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

Event Timeline