server/src/app/Libraries/Handle/HandleClient.php
changeset 153 338bcc78d431
child 154 ded3cf22eef8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/app/Libraries/Handle/HandleClient.php	Fri Apr 22 11:20:17 2016 +0200
@@ -0,0 +1,279 @@
+<?php
+namespace CorpusParole\Libraries\Handle;
+
+use Log;
+
+
+use Illuminate\Pagination\LengthAwarePaginator;
+use Illuminate\Pagination\Paginator;
+
+use phpseclib\Crypt\RSA;
+
+// current_date = datetime.now()
+// current_date_format = unicode(current_date.strftime('%Y-%m-%dT%H:%M:%SZ'))
+// handle_record = {u'values': [
+//     {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'}},
+//     {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'}},
+//     {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'}}
+// ], u'handle': unicode(handle), u'responseCode': 1}
+
+
+
+// class to handle communication with Handle server API.
+// inspired by : https://github.com/theNBS/handleserver-samples
+class HandleClient {
+
+    public function __construct($privateKeyOrCert, $pkpass, $adminId, $handleHost, $handlePort, $httpClient) {
+        $this->session = null;
+        $this->cert = null;
+        $this->adminId = $adminId;
+        $this->privateKeyRes = null;
+        $this->privateKeyOrCert = $privateKeyOrCert;
+        $this->pkpass = $pkpass;
+        $this->handleHost = $handleHost;
+        $this->handlePort = $handlePort;
+        $this->httpClient = $httpClient;
+        $this->guzzleOptions = ['verify' => false,];
+    }
+
+    public function __destruct () {
+        $this->close();
+    }
+
+    public function close() {
+        $this->deleteSession();
+        $this->freeResources();        
+    }
+
+    private function getBaseUrl() {
+        return "https://$this->handleHost:$this->handlePort/api/";
+    }
+
+    private function getSessionAuthHeader() {
+        return "Handle sessionId=$this->session";
+    }
+
+    private function generateClientNonce() {
+        return openssl_random_pseudo_bytes(16);
+    }
+
+    private function getPrivateKeyRes() {
+        if(is_null($this->privateKeyRes)) {
+            $this->privateKeyRes = openssl_pkey_get_private($this->privateKeyOrCert, $this->pkpass);
+        }       
+        return $this->privateKeyRes;
+    }
+
+    private function freeResources() {
+        if(!empty($this->privateKeyRes)) {
+            $privateKeyRes = $this->privateKeyRes;
+            $this->privateKeyRes = null;
+            openssl_free_key($privateKeyRes);
+        }
+    }
+
+    private function signBytesDsa($str) {
+
+        openssl_sign($str, $signature, $this->getPrivateKeyRes(), OPENSSL_ALGO_DSS1);
+
+        return $signature;
+
+    }
+
+    private function signBytesRsa($str) {
+
+        $rsa = new RSA();
+        $rsa->setHash('sha256');
+        if(!empty($this->pkpass)) {
+            $rsa->setPassword($this->pkpass);
+        }
+        $keyContent = $this->privateKeyOrCert;
+        if(is_file($keyContent)) {
+            $keyContent = file_get_contents($keyContent);
+        }
+        $rsa->loadKey($keyContent);
+        $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
+        
+        return $rsa->sign($str);
+
+    }
+
+
+
+    private function createAuthorisationHeaderFromJson($jsonresp) {
+        # Unpick number once (nonce) and session id from server response (this is the challenge)
+        $serverNonce = base64_decode($jsonresp['nonce']);
+        $this->sessionId = $jsonresp['sessionId'];
+
+        # Generate a client number once (cnonce)
+        $clientNonce = $this->generateClientNonce();
+        $clientNonceStr = base64_encode($clientNonce);
+
+        # Our response has to be the signature of server nonce + client nonce
+        $combinedNonce = $serverNonce . $clientNonce;
+        if($this->getPrivateKeyRes() === false) {
+            throw new \Exception("HandleClient: can not read private res");
+        }
+        $keyDetails = openssl_pkey_get_details($this->getPrivateKeyRes());
+        if($keyDetails === false) {
+            throw new \Exception("HandleClient: can not read private key");
+        }
+        if($keyDetails['type']===OPENSSL_KEYTYPE_RSA) {
+            $signature = $this->signBytesRsa($combinedNonce);
+            $signAlg = 'SHA256';
+        } elseif ($keyDetails['type']===OPENSSL_KEYTYPE_DSA) {
+            $signature = $this->signBytesDsa($combinedNonce);
+            $signAlg = 'SHA1';
+        } else {
+            throw new \Exception("HandleClient: $keyDetails[type] Format unknown");
+        }
+        $signatureStr = base64_encode($signature);
+
+        $this->freeResources();
+        # Build the authorisation header to send with the request
+        # Use SHA1 for DSA keys; SHA256 can be used for RSA keys
+        return $this->buildComplexAuthorizationString($signatureStr, $signAlg, $clientNonceStr);
+
+    }
+
+    private function buildComplexAuthorizationString($signatureString, $signAlg, $clientNonceString) {
+        return "Handle " .
+              "version=\"0\", " .
+              "sessionId=\"$this->sessionId\", " .
+              "cnonce=\"$clientNonceString\", " .
+              "id=\"$this->adminId\", " .
+              "type=\"HS_PUBKEY\", " .
+              "alg=\"$signAlg\", " .
+              "signature=\"$signatureString\"";
+    }
+
+    public function initSession() {
+
+        if(!empty($this->session) || !empty($this->cert)) {
+            return;
+        }
+
+        $headers = key_exists('headers', $this->guzzleOptions)?$this->guzzleOptions['headers']:[];
+        $headers = array_merge($headers, [
+            'Content-Type' => 'application/json;charset=UTF-8',
+        ]);
+
+        $certContent = $this->privateKeyOrCert;
+        if(is_file($certContent)) {
+            $certContent = file_get_contents($certContent);
+        }
+        if(openssl_x509_parse($certContent) !== false) {
+            if(!empty($this->pkpass)) {
+                $this->cert = [$this->privateKeyOrCert, $this->pkpass];
+            } else {
+                $this->cert = $this->privateKeyOrCert;
+            }
+            $headers['Authorization'] =  "Handle clientCert=\"true\"";
+        } else {
+            $url = $this->getBaseUrl()."sessions/";
+            $challengeRes = $this->httpClient->post($url, ['verify' => false]);
+
+            $jsonResp = json_decode($challengeRes->getBody(), true);
+            $pkheaders = [
+                'Content-Type' => 'application/json;charset=UTF-8',
+                'Authorization' => $this->createAuthorisationHeaderFromJson($jsonResp)
+            ];
+            # Send the request again with a valid correctly signed Authorization header
+            $sessionResp = $this->httpClient->put($url.'this', ['headers' => $pkheaders, 'verify' => false]);
+            Log::debug('Create session with auth: '.$sessionResp->getStatusCode().' : '.$sessionResp->getReasonPhrase());
+
+            $jsonResp = json_decode($sessionResp->getBody(), true);
+
+            $this->session = $jsonResp['authenticated']?$jsonResp['sessionId']:"";
+            $headers['Authorization'] = "Handle version=\"0\", sessionId=\"$this->session\"";
+
+        }
+
+        $this->guzzleOptions = array_merge($this->guzzleOptions, ['headers' => $headers, 'cert' => $this->cert]);
+
+    }
+
+    // will call a async method. Apart logging we do not really care in the result
+    public function deleteSession() {
+        if(empty($this->session)) {
+            return;
+        }
+        $headers = [
+            'Content-Type' => 'application/json;charset=UTF-8',
+            'Authorization' => $this->getSessionAuthHeader()
+        ];
+        $url = $this->getBaseUrl()."sessions/this";
+        // Do not really care of the response...
+        $this->httpClient->deleteAsync($url, ['headers' => $headers, 'verify' => false]);
+        $this->session = null;
+
+    }
+
+    /**
+     * Paginate all handle as a paginator.
+     *
+     * @param  int  $perPage
+     * @param  string  $pageName
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public function paginateAll($prefix, $perPage = 15, $pageName = 'page', $page = null) {
+
+        $this->initSession();
+
+        $url = $this->getBaseUrl()."handles";
+
+        $params = [
+            'prefix' => $prefix,
+            'page' => is_null($page)?0:$page-1,
+            'pageSize' => $perPage
+        ];
+
+        $paginateRes = $this->httpClient->get($url, array_merge($this->guzzleOptions, ['query' => $params]));
+
+
+        $paginateJson = json_decode($paginateRes->getBody(), true);
+
+        $total = (int)$paginateJson['totalCount'];
+
+        $results = $paginateJson['handles'];
+        
+        return new LengthAwarePaginator($results, $total, $perPage, $page, [
+            'path' => Paginator::resolveCurrentPath(),
+            'pageName' => $pageName,
+        ]);
+   
+    }
+
+    public function deleteHandle($handle) {
+        $this->initSession();
+        
+        $delUrl = $this->getBaseUrl()."handles/$handle";
+        $delRes = $this->httpClient->delete($delUrl, $this->guzzleOptions);
+
+        Log::debug('Delete Handle: '.$delRes->getStatusCode().': '.$delRes->getReasonPhrase());
+    }
+
+    public function createHandleUrlRecord($handle, $url) {
+        $this->initSession();
+        $currentDate = gmstrftime('%Y-%m-%dT%H:%M:%SZ');
+        $handleRecord = [
+          'values' => [
+            ['index' => 1, 'ttl' => 86400, 'type' => 'URL', 'timestamp' => $currentDate, 'data' => ['value'=> $url, 'format'=> 'string']],
+            ['index' => 100, 'ttl' => 86400, 'type' => 'HS_ADMIN', 'timestamp' => $currentDate, 'data' => [
+                'value' => ['index' => 200, 'handle' => $this->adminId],
+                'permissions' => '011111110011',
+                'format' => 'admin'
+              ]
+            ]
+          ],
+          'handle' => $handle,
+        ];
+
+        $submitUrl = $this->getBaseUrl()."handles/$handle";
+
+        $submitRes = $this->httpClient->put($submitUrl, array_merge($this->guzzleOptions, ['json' => $handleRecord, ]));
+
+        Log::debug('Create Handle Url: '.$submitRes->getStatusCode().' : '.$submitRes->getReasonPhrase());
+    }
+
+}