Page MenuHome GnuPG

No OneTemporary

diff --git a/src/auth/PhutilAuthAdapterOAuthJIRA.php b/src/auth/PhutilAuthAdapterOAuthJIRA.php
index 9827c59..1d4b640 100644
--- a/src/auth/PhutilAuthAdapterOAuthJIRA.php
+++ b/src/auth/PhutilAuthAdapterOAuthJIRA.php
@@ -1,88 +1,145 @@
<?php
final class PhutilAuthAdapterOAuthJIRA extends PhutilAuthAdapterOAuth1 {
+ // TODO: JIRA tokens expire (after 5 years) and we could surface and store
+ // that.
+
+ // TODO: If a user clicks "Deny" on the JIRA auth dialog, they get sent to
+ // the callback URI with `oauth_verifier=denied`. We should handle this
+ // better.
+
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() {
- var_dump($this->getHandshakeData());
- throw new Exception("TODO: NOT IMPLEMENTED");
+ // 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() {
- $uri = new PhutilURI($this->jiraBaseURI);
-
- $domain = $uri->getDomain();
- if ($uri->getPort()) {
- $domain .= ':'.$uri->getPort();
- }
-
- if ($uri->getPath() && $uri->getPath() != '/') {
- $domain .= $uri->getPath();
- }
+ return $this->adapterDomain;
+ }
- return $domain;
+ 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')
+ ->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'],
+ ));
+
+ $data = $this->newOAuth1Future($uri)
+ ->setMethod('GET')
+ ->addHeader('Content-Type', 'application/json')
+ ->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);
}
}
diff --git a/src/auth/PhutilAuthAdapterOAuthTwitter.php b/src/auth/PhutilAuthAdapterOAuthTwitter.php
index c127eef..c1b36db 100644
--- a/src/auth/PhutilAuthAdapterOAuthTwitter.php
+++ b/src/auth/PhutilAuthAdapterOAuthTwitter.php
@@ -1,77 +1,71 @@
<?php
final class PhutilAuthAdapterOAuthTwitter extends PhutilAuthAdapterOAuth1 {
private $userInfo;
public function getAccountID() {
return idx($this->getHandshakeData(), 'user_id');
}
public function getAccountName() {
return idx($this->getHandshakeData(), 'screen_name');
}
public function getAccountURI() {
$name = $this->getAccountName();
if (strlen($name)) {
return 'https://twitter.com/'.$name;
}
return null;
}
public function getAccountImageURI() {
$info = $this->getUserInfo();
return idx($info, 'profile_image_url');
}
public function getAccountRealName() {
$info = $this->getUserInfo();
return idx($info, 'name');
}
public function getAdapterType() {
return 'twitter';
}
public function getAdapterDomain() {
return 'twitter.com';
}
protected function getRequestTokenURI() {
return 'https://api.twitter.com/oauth/request_token';
}
protected function getAuthorizeTokenURI() {
return 'https://api.twitter.com/oauth/authorize';
}
protected function getValidateTokenURI() {
return 'https://api.twitter.com/oauth/access_token';
}
private function getUserInfo() {
if ($this->userInfo === null) {
-
$uri = new PhutilURI('https://api.twitter.com/1.1/users/show.json');
$uri->setQueryParams(
array(
'user_id' => $this->getAccountID(),
));
- list($body) = $this->newOAuth1Future($uri)
+ $data = $this->newOAuth1Future($uri)
->setMethod('GET')
- ->resolvex();
-
- $data = json_decode($body, true);
- if (!is_array($data)) {
- throw new Exception("Expect JSON, got: {$body}");
- }
+ ->resolveJSON();
$this->userInfo = $data;
}
return $this->userInfo;
}
}
diff --git a/src/future/oauth/PhutilOAuth1Future.php b/src/future/oauth/PhutilOAuth1Future.php
index 70c201d..1a511c4 100644
--- a/src/future/oauth/PhutilOAuth1Future.php
+++ b/src/future/oauth/PhutilOAuth1Future.php
@@ -1,238 +1,255 @@
<?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;
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);
+ 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);
$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
Thu, Feb 26, 6:41 PM (12 h, 36 s)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
dc/1c/a5d5d5b17d72c03d28d8b479f2c2

Event Timeline