@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class PhabricatorDuoFuture
4 extends FutureProxy {
5
6 private $future;
7
8 private $integrationKey;
9 private $secretKey;
10 private $apiHostname;
11
12 private $httpMethod = 'POST';
13 private $method;
14 private $parameters;
15 private $timeout;
16
17 public function __construct() {
18 parent::__construct(null);
19 }
20
21 public function setIntegrationKey($integration_key) {
22 $this->integrationKey = $integration_key;
23 return $this;
24 }
25
26 public function setSecretKey(PhutilOpaqueEnvelope $key) {
27 $this->secretKey = $key;
28 return $this;
29 }
30
31 public function setAPIHostname($hostname) {
32 $this->apiHostname = $hostname;
33 return $this;
34 }
35
36 public function setMethod($method, array $parameters) {
37 $this->method = $method;
38 $this->parameters = $parameters;
39 return $this;
40 }
41
42 public function setTimeout($timeout) {
43 $this->timeout = $timeout;
44 return $this;
45 }
46
47 public function getTimeout() {
48 return $this->timeout;
49 }
50
51 public function setHTTPMethod($method) {
52 $this->httpMethod = $method;
53 return $this;
54 }
55
56 public function getHTTPMethod() {
57 return $this->httpMethod;
58 }
59
60 protected function getProxiedFuture() {
61 if (!$this->future) {
62 if ($this->integrationKey === null) {
63 throw new PhutilInvalidStateException('setIntegrationKey');
64 }
65
66 if ($this->secretKey === null) {
67 throw new PhutilInvalidStateException('setSecretKey');
68 }
69
70 if ($this->apiHostname === null) {
71 throw new PhutilInvalidStateException('setAPIHostname');
72 }
73
74 if ($this->method === null || $this->parameters === null) {
75 throw new PhutilInvalidStateException('setMethod');
76 }
77
78 $path = (string)urisprintf('/auth/v2/%s', $this->method);
79
80 $host = $this->apiHostname;
81 $host = phutil_utf8_strtolower($host);
82
83 $data = $this->parameters;
84 $date = date('r');
85
86 $http_method = $this->getHTTPMethod();
87
88 ksort($data);
89 $data_parts = phutil_build_http_querystring($data);
90
91 $corpus = array(
92 $date,
93 $http_method,
94 $host,
95 $path,
96 $data_parts,
97 );
98 $corpus = implode("\n", $corpus);
99
100 $signature = hash_hmac(
101 'sha1',
102 $corpus,
103 $this->secretKey->openEnvelope());
104 $signature = new PhutilOpaqueEnvelope($signature);
105
106 if ($http_method === 'GET') {
107 $uri_data = $data;
108 $body_data = array();
109 } else {
110 $uri_data = array();
111 $body_data = $data;
112 }
113
114 $uri = id(new PhutilURI('', $uri_data))
115 ->setProtocol('https')
116 ->setDomain($host)
117 ->setPath($path);
118
119 $future = id(new HTTPSFuture($uri, $body_data))
120 ->setHTTPBasicAuthCredentials($this->integrationKey, $signature)
121 ->setMethod($http_method)
122 ->addHeader('Accept', 'application/json')
123 ->addHeader('Date', $date);
124
125 $timeout = $this->getTimeout();
126 if ($timeout) {
127 $future->setTimeout($timeout);
128 }
129
130 $this->future = $future;
131 }
132
133 return $this->future;
134 }
135
136 protected function didReceiveResult($result) {
137 list($status, $body, $headers) = $result;
138
139 if ($status->isError()) {
140 throw $status;
141 }
142
143 try {
144 $data = phutil_json_decode($body);
145 } catch (PhutilJSONParserException $ex) {
146 throw new Exception(
147 pht('Expected JSON response from Duo.'),
148 0,
149 $ex);
150 }
151
152 return $data;
153 }
154
155}