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:
		@@ -2983,6 +2983,7 @@ phutil_register_library_map(array(
 | 
				
			|||||||
    'PhabricatorDraftEngine' => 'applications/transactions/draft/PhabricatorDraftEngine.php',
 | 
					    'PhabricatorDraftEngine' => 'applications/transactions/draft/PhabricatorDraftEngine.php',
 | 
				
			||||||
    'PhabricatorDraftInterface' => 'applications/transactions/draft/PhabricatorDraftInterface.php',
 | 
					    'PhabricatorDraftInterface' => 'applications/transactions/draft/PhabricatorDraftInterface.php',
 | 
				
			||||||
    'PhabricatorDrydockApplication' => 'applications/drydock/application/PhabricatorDrydockApplication.php',
 | 
					    'PhabricatorDrydockApplication' => 'applications/drydock/application/PhabricatorDrydockApplication.php',
 | 
				
			||||||
 | 
					    'PhabricatorDuoFuture' => 'applications/auth/future/PhabricatorDuoFuture.php',
 | 
				
			||||||
    'PhabricatorEdgeChangeRecord' => 'infrastructure/edges/util/PhabricatorEdgeChangeRecord.php',
 | 
					    'PhabricatorEdgeChangeRecord' => 'infrastructure/edges/util/PhabricatorEdgeChangeRecord.php',
 | 
				
			||||||
    'PhabricatorEdgeChangeRecordTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeChangeRecordTestCase.php',
 | 
					    'PhabricatorEdgeChangeRecordTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeChangeRecordTestCase.php',
 | 
				
			||||||
    'PhabricatorEdgeConfig' => 'infrastructure/edges/constants/PhabricatorEdgeConfig.php',
 | 
					    'PhabricatorEdgeConfig' => 'infrastructure/edges/constants/PhabricatorEdgeConfig.php',
 | 
				
			||||||
@@ -8829,6 +8830,7 @@ phutil_register_library_map(array(
 | 
				
			|||||||
    'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
 | 
					    'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
 | 
				
			||||||
    'PhabricatorDraftEngine' => 'Phobject',
 | 
					    'PhabricatorDraftEngine' => 'Phobject',
 | 
				
			||||||
    'PhabricatorDrydockApplication' => 'PhabricatorApplication',
 | 
					    'PhabricatorDrydockApplication' => 'PhabricatorApplication',
 | 
				
			||||||
 | 
					    'PhabricatorDuoFuture' => 'FutureProxy',
 | 
				
			||||||
    'PhabricatorEdgeChangeRecord' => 'Phobject',
 | 
					    'PhabricatorEdgeChangeRecord' => 'Phobject',
 | 
				
			||||||
    'PhabricatorEdgeChangeRecordTestCase' => 'PhabricatorTestCase',
 | 
					    'PhabricatorEdgeChangeRecordTestCase' => 'PhabricatorTestCase',
 | 
				
			||||||
    'PhabricatorEdgeConfig' => 'PhabricatorEdgeConstants',
 | 
					    'PhabricatorEdgeConfig' => 'PhabricatorEdgeConstants',
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										150
									
								
								src/applications/auth/future/PhabricatorDuoFuture.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/applications/auth/future/PhabricatorDuoFuture.php
									
									
									
									
									
										Normal 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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -58,7 +58,7 @@ final class PhabricatorTwilioFuture extends FutureProxy {
 | 
				
			|||||||
        $this->accountSID,
 | 
					        $this->accountSID,
 | 
				
			||||||
        $this->method);
 | 
					        $this->method);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      $uri = id(new PhutilURI('https://api.twilio.com/2010-04-01/accounts/'))
 | 
					      $uri = id(new PhutilURI('https://api.twilio.com/'))
 | 
				
			||||||
        ->setPath($path);
 | 
					        ->setPath($path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      $data = $this->parameters;
 | 
					      $data = $this->parameters;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user