Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F36623154
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
13 KB
Subscribers
None
View Options
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
Details
Attached
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
Attached To
rPHUTIL libphutil
Event Timeline
Log In to Comment