1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 05:50:55 +01:00

Add a Duo API future

Summary: Depends on D20025. Ref T13231. Although I'm not currently planning to actually upstream a Duo MFA provider, it's probably easiest to put most of the support pieces in the upstream until T5055.

Test Plan: Used a test script to make some (mostly trivial) API calls and got valid results back, so I think the parameter signing is correct.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13231

Differential Revision: https://secure.phabricator.com/D20026
This commit is contained in:
epriestley 2019-01-23 13:29:20 -08:00
parent 98c4cdc5be
commit 069160404f
3 changed files with 153 additions and 1 deletions

View file

@ -2983,6 +2983,7 @@ phutil_register_library_map(array(
'PhabricatorDraftEngine' => 'applications/transactions/draft/PhabricatorDraftEngine.php',
'PhabricatorDraftInterface' => 'applications/transactions/draft/PhabricatorDraftInterface.php',
'PhabricatorDrydockApplication' => 'applications/drydock/application/PhabricatorDrydockApplication.php',
'PhabricatorDuoFuture' => 'applications/auth/future/PhabricatorDuoFuture.php',
'PhabricatorEdgeChangeRecord' => 'infrastructure/edges/util/PhabricatorEdgeChangeRecord.php',
'PhabricatorEdgeChangeRecordTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeChangeRecordTestCase.php',
'PhabricatorEdgeConfig' => 'infrastructure/edges/constants/PhabricatorEdgeConfig.php',
@ -8829,6 +8830,7 @@ phutil_register_library_map(array(
'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
'PhabricatorDraftEngine' => 'Phobject',
'PhabricatorDrydockApplication' => 'PhabricatorApplication',
'PhabricatorDuoFuture' => 'FutureProxy',
'PhabricatorEdgeChangeRecord' => 'Phobject',
'PhabricatorEdgeChangeRecordTestCase' => 'PhabricatorTestCase',
'PhabricatorEdgeConfig' => 'PhabricatorEdgeConstants',

View file

@ -0,0 +1,150 @@
<?php
final class PhabricatorDuoFuture
extends FutureProxy {
private $future;
private $integrationKey;
private $secretKey;
private $apiHostname;
private $httpMethod = 'POST';
private $method;
private $parameters;
private $timeout;
public function __construct() {
parent::__construct(null);
}
public function setIntegrationKey($integration_key) {
$this->integrationKey = $integration_key;
return $this;
}
public function setSecretKey(PhutilOpaqueEnvelope $key) {
$this->secretKey = $key;
return $this;
}
public function setAPIHostname($hostname) {
$this->apiHostname = $hostname;
return $this;
}
public function setMethod($method, array $parameters) {
$this->method = $method;
$this->parameters = $parameters;
return $this;
}
public function setTimeout($timeout) {
$this->timeout = $timeout;
return $this;
}
public function getTimeout() {
return $this->timeout;
}
public function setHTTPMethod($method) {
$this->httpMethod = $method;
return $this;
}
public function getHTTPMethod() {
return $this->httpMethod;
}
protected function getProxiedFuture() {
if (!$this->future) {
if ($this->integrationKey === null) {
throw new PhutilInvalidStateException('setIntegrationKey');
}
if ($this->secretKey === null) {
throw new PhutilInvalidStateException('setSecretKey');
}
if ($this->apiHostname === null) {
throw new PhutilInvalidStateException('setAPIHostname');
}
if ($this->method === null || $this->parameters === null) {
throw new PhutilInvalidStateException('setMethod');
}
$path = (string)urisprintf('/auth/v2/%s', $this->method);
$host = $this->apiHostname;
$host = phutil_utf8_strtolower($host);
$uri = id(new PhutilURI(''))
->setProtocol('https')
->setDomain($host)
->setPath($path);
$data = $this->parameters;
$date = date('r');
$http_method = $this->getHTTPMethod();
ksort($data);
$data_parts = array();
foreach ($data as $key => $value) {
$data_parts[] = rawurlencode($key).'='.rawurlencode($value);
}
$data_parts = implode('&', $data_parts);
$corpus = array(
$date,
$http_method,
$host,
$path,
$data_parts,
);
$corpus = implode("\n", $corpus);
$signature = hash_hmac(
'sha1',
$corpus,
$this->secretKey->openEnvelope());
$signature = new PhutilOpaqueEnvelope($signature);
$future = id(new HTTPSFuture($uri, $data))
->setHTTPBasicAuthCredentials($this->integrationKey, $signature)
->setMethod($http_method)
->addHeader('Accept', 'application/json')
->addHeader('Date', $date);
$timeout = $this->getTimeout();
if ($timeout) {
$future->setTimeout($timeout);
}
$this->future = $future;
}
return $this->future;
}
protected function didReceiveResult($result) {
list($status, $body, $headers) = $result;
if ($status->isError()) {
throw $status;
}
try {
$data = phutil_json_decode($body);
} catch (PhutilJSONParserException $ex) {
throw new PhutilProxyException(
pht('Expected JSON response from Duo.'),
$ex);
}
return $data;
}
}

View file

@ -58,7 +58,7 @@ final class PhabricatorTwilioFuture extends FutureProxy {
$this->accountSID,
$this->method);
$uri = id(new PhutilURI('https://api.twilio.com/2010-04-01/accounts/'))
$uri = id(new PhutilURI('https://api.twilio.com/'))
->setPath($path);
$data = $this->parameters;