server/src/app/Libraries/Handle/HandleClient.php
changeset 153 338bcc78d431
child 154 ded3cf22eef8
equal deleted inserted replaced
152:dd6b3adde73b 153:338bcc78d431
       
     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 }