1 | <?php |
||
2 | |||
3 | /** |
||
4 | * @author Russell Michell 2018 <[email protected]> |
||
5 | * @package silverstripe-verifiable |
||
6 | */ |
||
7 | |||
8 | namespace PhpTek\Verifiable\Backend\Chainpoint; |
||
9 | |||
10 | use SilverStripe\Core\Config\Configurable; |
||
11 | use GuzzleHttp\Client; |
||
12 | use GuzzleHttp\Exception\RequestException; |
||
13 | use PhpTek\Verifiable\Exception\VerifiableBackendException; |
||
14 | use PhpTek\Verifiable\Exception\VerifiableValidationException; |
||
15 | use PhpTek\Verifiable\Backend\GatewayProvider; |
||
16 | |||
17 | /** |
||
18 | * Calls the endpoints of the Tierion network's ChainPoint service. |
||
19 | * |
||
20 | * @see https://app.swaggerhub.com/apis/chainpoint/node/1.0.0 |
||
21 | * @see https://chainpoint.org |
||
22 | */ |
||
23 | class Gateway implements GatewayProvider |
||
24 | { |
||
25 | use Configurable; |
||
26 | |||
27 | /** |
||
28 | * An array of nodes for submitting hashes to. |
||
29 | * |
||
30 | * @var array |
||
31 | */ |
||
32 | protected static $discovered_nodes = []; |
||
33 | |||
34 | /** |
||
35 | * @return string |
||
36 | */ |
||
37 | public function name() : string |
||
38 | { |
||
39 | return 'chainpoint'; |
||
40 | } |
||
41 | |||
42 | /** |
||
43 | * @return string |
||
44 | */ |
||
45 | public function hashFunc() : string |
||
46 | { |
||
47 | return 'sha256'; |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * Send a single hash_id_node to retrieve a proof in binary format from the |
||
52 | * Tierion network. |
||
53 | * |
||
54 | * GETs to the: "/proofs" REST API endpoint. |
||
55 | * |
||
56 | * @param string $hashIdNode |
||
57 | * @return string A v3 ChainpointProof |
||
58 | */ |
||
59 | public function proofs(string $hashIdNode) : string |
||
60 | { |
||
61 | $response = $this->client("/proofs/$hashIdNode", 'GET'); |
||
62 | |||
63 | return $response->getBody()->getContents() ?? '[]'; |
||
64 | } |
||
65 | |||
66 | /** |
||
67 | * Send an array of hashes for anchoring. |
||
68 | * |
||
69 | * POSTs to the: "/hashes" REST API endpoint. |
||
70 | * |
||
71 | * @param array $hashes |
||
72 | * @return string (From GuzzleHttp\Stream::getContents() |
||
73 | */ |
||
74 | public function hashes(array $hashes) : string |
||
75 | { |
||
76 | $response = $this->client('/hashes', 'POST', ['hashes' => $hashes]); |
||
77 | |||
78 | return $response->getBody()->getContents() ?? '[]'; |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * Submit a chainpoint proof to the backend for verification. |
||
83 | * |
||
84 | * @param string $proof A partial or full JSON string, originally received from, |
||
85 | * or generated on behalf of a backend. |
||
86 | * @return string |
||
87 | * @todo See the returned proof's "uris" key, to be able to call a specific URI for proof verification. |
||
88 | */ |
||
89 | public function verify(string $proof) : string |
||
90 | { |
||
91 | $response = $this->client('/verify', 'POST', ['proofs' => [$proof]]); |
||
92 | |||
93 | return $response->getBody()->getContents() ?? '[]'; |
||
94 | } |
||
95 | |||
96 | /** |
||
97 | * Return a client to use for all RPC traffic to this backend. |
||
98 | * |
||
99 | * @param string $url The absolute or relative URL to make a request to. |
||
100 | * @param string $verb The HTTP verb to use e.g. GET or POST. |
||
101 | * @param array $payload The payload to be sent along in GET/POST requests. |
||
102 | * @param bool $rel Is $url relative? If so, pass "base_uri" to {@link Client}. |
||
103 | * @return Response Guzzle Response object |
||
0 ignored issues
–
show
|
|||
104 | * @throws VerifiableBackendException |
||
105 | */ |
||
106 | protected function client(string $url, string $verb, array $payload = [], bool $rel = true) |
||
107 | { |
||
108 | if ($rel && !$this->getDiscoveredNodes()) { |
||
109 | $this->setDiscoveredNodes(); |
||
110 | |||
111 | if (!$this->getDiscoveredNodes()) { |
||
112 | // This should _never_ happen.. |
||
113 | throw new VerifiableValidationException('No chainpoint nodes discovered!'); |
||
114 | } |
||
115 | } |
||
116 | |||
117 | $verb = strtoupper($verb); |
||
118 | $method = strtolower($verb); |
||
119 | $config = $this->config()->get('client_config'); |
||
120 | $client = new Client([ |
||
121 | 'base_uri' => $rel ? $this->getDiscoveredNodes()[0] : '', // Use a single address only |
||
122 | 'verify' => true, |
||
123 | 'timeout' => $config['timeout'], |
||
124 | 'connect_timeout' => $config['connect_timeout'], |
||
125 | 'allow_redirects' => false, |
||
126 | ]); |
||
127 | |||
128 | try { |
||
129 | // json_encodes POSTed data and sends correct Content-Type header |
||
130 | if ($payload && $verb === 'POST') { |
||
0 ignored issues
–
show
The expression
$payload of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||
131 | $payload['json'] = $payload; |
||
132 | } |
||
133 | |||
134 | if ($rel) { |
||
135 | $url = ($rel ? $this->getDiscoveredNodes()[0] : '') . $url; |
||
0 ignored issues
–
show
|
|||
136 | } |
||
137 | |||
138 | return $client->$method($url, $payload); |
||
139 | } catch (RequestException $e) { |
||
140 | throw new VerifiableValidationException(sprintf('Upstream network problem: %s', $e->getMessage())); |
||
141 | } |
||
142 | } |
||
143 | |||
144 | /** |
||
145 | * The Tierion network comprises many nodes, some of which may or may not be |
||
146 | * online. We therefore randomly select a source of curated (audited) node-IPs |
||
147 | * and for each IP, we ping it until we receive a 200 OK response. For each such |
||
148 | * node, it is then set to $discovered_nodes. |
||
149 | * |
||
150 | * @param array $usedNodes Optionally pass some "pre-known" chainpoint nodes |
||
151 | * @return mixed void | null |
||
152 | * @throws VerifiableBackendException |
||
153 | */ |
||
154 | public function setDiscoveredNodes($usedNodes = null) |
||
155 | { |
||
156 | if ($usedNodes) { |
||
157 | static::$discovered_nodes = $usedNodes; |
||
158 | |||
159 | return; |
||
160 | } |
||
161 | |||
162 | if (count(static::$discovered_nodes)) { |
||
163 | return; |
||
164 | } |
||
165 | |||
166 | $limit = (int) $this->config()->get('discover_node_count') ?: 1; |
||
167 | $chainpointUrls = $this->config()->get('chainpoint_urls'); |
||
168 | $url = $chainpointUrls[rand(0, 2)]; |
||
169 | $response = $this->client($url, 'GET', [], false); |
||
170 | |||
171 | if ($response->getStatusCode() !== 200) { |
||
172 | throw new VerifiableBackendException('Bad response from node source URL'); |
||
173 | } |
||
174 | |||
175 | $i = 0; |
||
176 | |||
177 | foreach (json_decode($response->getBody(), true) as $candidate) { |
||
178 | $response = $this->client($candidate['public_uri'], 'GET', [], false); |
||
179 | |||
180 | if ($response->getStatusCode() !== 200) { |
||
181 | continue; |
||
182 | } |
||
183 | |||
184 | ++$i; // Only increment with succesful requests |
||
185 | |||
186 | static::$discovered_nodes[] = $candidate['public_uri']; |
||
187 | |||
188 | if ($i === $limit) { |
||
189 | break; |
||
190 | } |
||
191 | } |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * @return array |
||
196 | */ |
||
197 | public function getDiscoveredNodes() |
||
198 | { |
||
199 | return static::$discovered_nodes; |
||
200 | } |
||
201 | } |
||
202 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths