|
1 <?php |
|
2 namespace CorpusParole\Libraries\Handle; |
|
3 |
|
4 use Log; |
|
5 |
|
6 |
|
7 use Illuminate\Pagination\LengthAwarePaginator; |
|
8 use Illuminate\Pagination\Paginator; |
|
9 |
|
10 use phpseclib\Crypt\RSA; |
|
11 |
|
12 // current_date = datetime.now() |
|
13 // current_date_format = unicode(current_date.strftime('%Y-%m-%dT%H:%M:%SZ')) |
|
14 // handle_record = {u'values': [ |
|
15 // {u'index': 1, u'ttl': 86400, u'type': u'URL', u'timestamp': current_date_format, u'data': {u'value': u'http://www.ribaenterprises.com', u'format': u'string'}}, |
|
16 // {u'index': 2, u'ttl': 86400, u'type': u'EMAIL', u'timestamp': current_date_format, u'data': {u'value': u'info@ribaenterprises.com', u'format': u'string'}}, |
|
17 // {u'index': 100, u'ttl': 86400, u'type': u'HS_ADMIN', u'timestamp': current_date_format, u'data': {u'value': {u'index': 200, u'handle': unicode(auth_id), u'permissions': u'011111110011'}, u'format': u'admin'}} |
|
18 // ], u'handle': unicode(handle), u'responseCode': 1} |
|
19 |
|
20 |
|
21 |
|
22 // class to handle communication with Handle server API. |
|
23 // inspired by : https://github.com/theNBS/handleserver-samples |
|
24 class HandleClient { |
|
25 |
|
26 public function __construct($privateKeyOrCert, $pkpass, $adminId, $handleHost, $handlePort, $httpClient) { |
|
27 $this->session = null; |
|
28 $this->cert = null; |
|
29 $this->adminId = $adminId; |
|
30 $this->privateKeyRes = null; |
|
31 $this->privateKeyOrCert = $privateKeyOrCert; |
|
32 $this->pkpass = $pkpass; |
|
33 $this->handleHost = $handleHost; |
|
34 $this->handlePort = $handlePort; |
|
35 $this->httpClient = $httpClient; |
|
36 $this->guzzleOptions = ['verify' => false,]; |
|
37 } |
|
38 |
|
39 public function __destruct () { |
|
40 $this->close(); |
|
41 } |
|
42 |
|
43 public function close() { |
|
44 $this->deleteSession(); |
|
45 $this->freeResources(); |
|
46 } |
|
47 |
|
48 private function getBaseUrl() { |
|
49 return "https://$this->handleHost:$this->handlePort/api/"; |
|
50 } |
|
51 |
|
52 private function getSessionAuthHeader() { |
|
53 return "Handle sessionId=$this->session"; |
|
54 } |
|
55 |
|
56 private function generateClientNonce() { |
|
57 return openssl_random_pseudo_bytes(16); |
|
58 } |
|
59 |
|
60 private function getPrivateKeyRes() { |
|
61 if(is_null($this->privateKeyRes)) { |
|
62 $this->privateKeyRes = openssl_pkey_get_private($this->privateKeyOrCert, $this->pkpass); |
|
63 } |
|
64 return $this->privateKeyRes; |
|
65 } |
|
66 |
|
67 private function freeResources() { |
|
68 if(!empty($this->privateKeyRes)) { |
|
69 $privateKeyRes = $this->privateKeyRes; |
|
70 $this->privateKeyRes = null; |
|
71 openssl_free_key($privateKeyRes); |
|
72 } |
|
73 } |
|
74 |
|
75 private function signBytesDsa($str) { |
|
76 |
|
77 openssl_sign($str, $signature, $this->getPrivateKeyRes(), OPENSSL_ALGO_DSS1); |
|
78 |
|
79 return $signature; |
|
80 |
|
81 } |
|
82 |
|
83 private function signBytesRsa($str) { |
|
84 |
|
85 $rsa = new RSA(); |
|
86 $rsa->setHash('sha256'); |
|
87 if(!empty($this->pkpass)) { |
|
88 $rsa->setPassword($this->pkpass); |
|
89 } |
|
90 $keyContent = $this->privateKeyOrCert; |
|
91 if(is_file($keyContent)) { |
|
92 $keyContent = file_get_contents($keyContent); |
|
93 } |
|
94 $rsa->loadKey($keyContent); |
|
95 $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); |
|
96 |
|
97 return $rsa->sign($str); |
|
98 |
|
99 } |
|
100 |
|
101 |
|
102 |
|
103 private function createAuthorisationHeaderFromJson($jsonresp) { |
|
104 # Unpick number once (nonce) and session id from server response (this is the challenge) |
|
105 $serverNonce = base64_decode($jsonresp['nonce']); |
|
106 $this->sessionId = $jsonresp['sessionId']; |
|
107 |
|
108 # Generate a client number once (cnonce) |
|
109 $clientNonce = $this->generateClientNonce(); |
|
110 $clientNonceStr = base64_encode($clientNonce); |
|
111 |
|
112 # Our response has to be the signature of server nonce + client nonce |
|
113 $combinedNonce = $serverNonce . $clientNonce; |
|
114 if($this->getPrivateKeyRes() === false) { |
|
115 throw new \Exception("HandleClient: can not read private res"); |
|
116 } |
|
117 $keyDetails = openssl_pkey_get_details($this->getPrivateKeyRes()); |
|
118 if($keyDetails === false) { |
|
119 throw new \Exception("HandleClient: can not read private key"); |
|
120 } |
|
121 if($keyDetails['type']===OPENSSL_KEYTYPE_RSA) { |
|
122 $signature = $this->signBytesRsa($combinedNonce); |
|
123 $signAlg = 'SHA256'; |
|
124 } elseif ($keyDetails['type']===OPENSSL_KEYTYPE_DSA) { |
|
125 $signature = $this->signBytesDsa($combinedNonce); |
|
126 $signAlg = 'SHA1'; |
|
127 } else { |
|
128 throw new \Exception("HandleClient: $keyDetails[type] Format unknown"); |
|
129 } |
|
130 $signatureStr = base64_encode($signature); |
|
131 |
|
132 $this->freeResources(); |
|
133 # Build the authorisation header to send with the request |
|
134 # Use SHA1 for DSA keys; SHA256 can be used for RSA keys |
|
135 return $this->buildComplexAuthorizationString($signatureStr, $signAlg, $clientNonceStr); |
|
136 |
|
137 } |
|
138 |
|
139 private function buildComplexAuthorizationString($signatureString, $signAlg, $clientNonceString) { |
|
140 return "Handle " . |
|
141 "version=\"0\", " . |
|
142 "sessionId=\"$this->sessionId\", " . |
|
143 "cnonce=\"$clientNonceString\", " . |
|
144 "id=\"$this->adminId\", " . |
|
145 "type=\"HS_PUBKEY\", " . |
|
146 "alg=\"$signAlg\", " . |
|
147 "signature=\"$signatureString\""; |
|
148 } |
|
149 |
|
150 public function initSession() { |
|
151 |
|
152 if(!empty($this->session) || !empty($this->cert)) { |
|
153 return; |
|
154 } |
|
155 |
|
156 $headers = key_exists('headers', $this->guzzleOptions)?$this->guzzleOptions['headers']:[]; |
|
157 $headers = array_merge($headers, [ |
|
158 'Content-Type' => 'application/json;charset=UTF-8', |
|
159 ]); |
|
160 |
|
161 $certContent = $this->privateKeyOrCert; |
|
162 if(is_file($certContent)) { |
|
163 $certContent = file_get_contents($certContent); |
|
164 } |
|
165 if(openssl_x509_parse($certContent) !== false) { |
|
166 if(!empty($this->pkpass)) { |
|
167 $this->cert = [$this->privateKeyOrCert, $this->pkpass]; |
|
168 } else { |
|
169 $this->cert = $this->privateKeyOrCert; |
|
170 } |
|
171 $headers['Authorization'] = "Handle clientCert=\"true\""; |
|
172 } else { |
|
173 $url = $this->getBaseUrl()."sessions/"; |
|
174 $challengeRes = $this->httpClient->post($url, ['verify' => false]); |
|
175 |
|
176 $jsonResp = json_decode($challengeRes->getBody(), true); |
|
177 $pkheaders = [ |
|
178 'Content-Type' => 'application/json;charset=UTF-8', |
|
179 'Authorization' => $this->createAuthorisationHeaderFromJson($jsonResp) |
|
180 ]; |
|
181 # Send the request again with a valid correctly signed Authorization header |
|
182 $sessionResp = $this->httpClient->put($url.'this', ['headers' => $pkheaders, 'verify' => false]); |
|
183 Log::debug('Create session with auth: '.$sessionResp->getStatusCode().' : '.$sessionResp->getReasonPhrase()); |
|
184 |
|
185 $jsonResp = json_decode($sessionResp->getBody(), true); |
|
186 |
|
187 $this->session = $jsonResp['authenticated']?$jsonResp['sessionId']:""; |
|
188 $headers['Authorization'] = "Handle version=\"0\", sessionId=\"$this->session\""; |
|
189 |
|
190 } |
|
191 |
|
192 $this->guzzleOptions = array_merge($this->guzzleOptions, ['headers' => $headers, 'cert' => $this->cert]); |
|
193 |
|
194 } |
|
195 |
|
196 // will call a async method. Apart logging we do not really care in the result |
|
197 public function deleteSession() { |
|
198 if(empty($this->session)) { |
|
199 return; |
|
200 } |
|
201 $headers = [ |
|
202 'Content-Type' => 'application/json;charset=UTF-8', |
|
203 'Authorization' => $this->getSessionAuthHeader() |
|
204 ]; |
|
205 $url = $this->getBaseUrl()."sessions/this"; |
|
206 // Do not really care of the response... |
|
207 $this->httpClient->deleteAsync($url, ['headers' => $headers, 'verify' => false]); |
|
208 $this->session = null; |
|
209 |
|
210 } |
|
211 |
|
212 /** |
|
213 * Paginate all handle as a paginator. |
|
214 * |
|
215 * @param int $perPage |
|
216 * @param string $pageName |
|
217 * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator |
|
218 */ |
|
219 public function paginateAll($prefix, $perPage = 15, $pageName = 'page', $page = null) { |
|
220 |
|
221 $this->initSession(); |
|
222 |
|
223 $url = $this->getBaseUrl()."handles"; |
|
224 |
|
225 $params = [ |
|
226 'prefix' => $prefix, |
|
227 'page' => is_null($page)?0:$page-1, |
|
228 'pageSize' => $perPage |
|
229 ]; |
|
230 |
|
231 $paginateRes = $this->httpClient->get($url, array_merge($this->guzzleOptions, ['query' => $params])); |
|
232 |
|
233 |
|
234 $paginateJson = json_decode($paginateRes->getBody(), true); |
|
235 |
|
236 $total = (int)$paginateJson['totalCount']; |
|
237 |
|
238 $results = $paginateJson['handles']; |
|
239 |
|
240 return new LengthAwarePaginator($results, $total, $perPage, $page, [ |
|
241 'path' => Paginator::resolveCurrentPath(), |
|
242 'pageName' => $pageName, |
|
243 ]); |
|
244 |
|
245 } |
|
246 |
|
247 public function deleteHandle($handle) { |
|
248 $this->initSession(); |
|
249 |
|
250 $delUrl = $this->getBaseUrl()."handles/$handle"; |
|
251 $delRes = $this->httpClient->delete($delUrl, $this->guzzleOptions); |
|
252 |
|
253 Log::debug('Delete Handle: '.$delRes->getStatusCode().': '.$delRes->getReasonPhrase()); |
|
254 } |
|
255 |
|
256 public function createHandleUrlRecord($handle, $url) { |
|
257 $this->initSession(); |
|
258 $currentDate = gmstrftime('%Y-%m-%dT%H:%M:%SZ'); |
|
259 $handleRecord = [ |
|
260 'values' => [ |
|
261 ['index' => 1, 'ttl' => 86400, 'type' => 'URL', 'timestamp' => $currentDate, 'data' => ['value'=> $url, 'format'=> 'string']], |
|
262 ['index' => 100, 'ttl' => 86400, 'type' => 'HS_ADMIN', 'timestamp' => $currentDate, 'data' => [ |
|
263 'value' => ['index' => 200, 'handle' => $this->adminId], |
|
264 'permissions' => '011111110011', |
|
265 'format' => 'admin' |
|
266 ] |
|
267 ] |
|
268 ], |
|
269 'handle' => $handle, |
|
270 ]; |
|
271 |
|
272 $submitUrl = $this->getBaseUrl()."handles/$handle"; |
|
273 |
|
274 $submitRes = $this->httpClient->put($submitUrl, array_merge($this->guzzleOptions, ['json' => $handleRecord, ])); |
|
275 |
|
276 Log::debug('Create Handle Url: '.$submitRes->getStatusCode().' : '.$submitRes->getReasonPhrase()); |
|
277 } |
|
278 |
|
279 } |