· 9 years ago · Jan 15, 2017, 10:34 AM
1<?php
2
3/*
4 Latch PHP SDK - Set of reusable classes to allow developers integrate Latch on
5 their applications.
6 Copyright (C) 2013 Eleven Paths
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23
24 abstract class LatchAuth {
25 private static $API_VERSION = "1.1";
26 public static $API_HOST = "https://latch.elevenpaths.com";
27
28 // App API
29 public static $API_CHECK_STATUS_URL = "/api/1.1/status";
30 public static $API_PAIR_URL = "/api/1.1/pair";
31 public static $API_PAIR_WITH_ID_URL = "/api/1.1/pairWithId";
32 public static $API_UNPAIR_URL = "/api/1.1/unpair";
33 public static $API_LOCK_URL = "/api/1.1/lock";
34 public static $API_UNLOCK_URL = "/api/1.1/unlock";
35 public static $API_HISTORY_URL = "/api/1.1/history";
36 public static $API_OPERATION_URL = "/api/1.1/operation";
37 public static $API_INSTANCE_URL = "/api/1.1/instance";
38
39 // User API
40 public static $API_APPLICATION_URL = "/api/1.1/application";
41 public static $API_SUBSCRIPTION_URL = "/api/1.1/subscription";
42
43 public static $AUTHORIZATION_HEADER_NAME = "Authorization";
44 public static $DATE_HEADER_NAME = "X-11Paths-Date";
45 public static $AUTHORIZATION_METHOD = "11PATHS";
46 public static $AUTHORIZATION_HEADER_FIELD_SEPARATOR = " ";
47
48 public static $UTC_STRING_FORMAT = "Y-m-d H:i:s";
49
50 private static $HMAC_ALGORITHM = "sha1";
51
52 public static $PROXY_HOST = NULL;
53 public static $CA_CERTIFICATE_PATH = NULL;
54 public static $X_11PATHS_HEADER_PREFIX = "X-11Paths-";
55 private static $X_11PATHS_HEADER_SEPARATOR = ":";
56
57 public static function setHost($host) {
58 self::$API_HOST = $host;
59 }
60
61 public static function setProxy($host) {
62 self::$PROXY_HOST = $host;
63 }
64
65 public static function setCACertificatePath($certificatePath) {
66 self::$CA_CERTIFICATE_PATH = $certificatePath;
67 }
68
69 /**
70 * The custom header consists of three parts, the method, the appId and the signature.
71 * This method returns the specified part if it exists.
72 * @param integer $part The zero indexed part to be returned
73 * @param string $header The HTTP header value from which to extract the part
74 * @return string the specified part from the header or an empty string if not existent
75 */
76 private static function getPartFromHeader($part, $header) {
77 $result_to_return = "";
78 if (!empty($header)) {
79 $parts = explode(self::$AUTHORIZATION_HEADER_FIELD_SEPARATOR, $header);
80 if(count($parts) > $part) {
81 $result_to_return = $parts[$part];
82 }
83 }
84 return $result_to_return;
85 }
86
87 /**
88 *
89 * @param string $authorizationHeader Authorization HTTP Header
90 * @return string the Authorization method. Typical values are "Basic", "Digest" or "11PATHS"
91 */
92 public static function getAuthMethodFromHeader($authorizationHeader) {
93 return self::getPartFromHeader(0, $authorizationHeader);
94 }
95
96 /**
97 *
98 * @param string $authorizationHeader Authorization HTTP Header
99 * @return string the requesting application Id. Identifies the application using the API
100 */
101 public static function getAppIdFromHeader($authorizationHeader) {
102 return self::getPartFromHeader(1, $authorizationHeader);
103 }
104
105 /**
106 *
107 * @param string $authorizationHeader Authorization HTTP Header
108 * @return string the signature of the current request. Verifies the identity of the application using the API
109 */
110 public static function getSignatureFromHeader($authorizationHeader) {
111 return self::getPartFromHeader(2, $authorizationHeader);
112 }
113
114
115
116 protected $appId;
117 protected $secretKey;
118
119 /**
120 * Create an instance of the class with the Application/User ID and secret obtained from Eleven Paths
121 * @param $appId
122 * @param $secretKey
123 */
124 protected function __construct($appId, $secretKey) {
125 $this->appId = $appId;
126 $this->secretKey = $secretKey;
127 }
128
129 public function HTTP($method, $url, $headers, $params) {
130 $curlHeaders = array();
131 foreach ($headers as $hkey=>$hvalue) {
132 array_push($curlHeaders, $hkey . ":" . $hvalue);
133 }
134
135 $ch = curl_init($url);
136 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
137 curl_setopt($ch, CURLOPT_HTTPHEADER, $curlHeaders);
138 curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
139 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
140 curl_setopt($ch, CURLOPT_PROXY, self::$PROXY_HOST);
141
142 if ($method == "PUT" || $method == "POST"){
143 $params_string = self::getSerializedParams($params);
144 rtrim($params_string, '&');
145 curl_setopt($ch,CURLOPT_POST, count($params));
146 curl_setopt($ch,CURLOPT_POSTFIELDS, $params_string);
147 }
148
149 if (self::$CA_CERTIFICATE_PATH != NULL) {
150 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
151 curl_setopt($ch, CURLOPT_CAINFO, self::$CA_CERTIFICATE_PATH);
152 } else {
153 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
154 }
155
156 $response = curl_exec($ch);
157 curl_close($ch);
158
159 return $response;
160 }
161
162 protected function HTTP_GET_proxy($url) {
163 return new LatchResponse($this->HTTP("GET", self::$API_HOST . $url, $this->authenticationHeaders("GET", $url, null), null));
164 }
165
166 protected function HTTP_POST_proxy($url, $params) {
167 return new LatchResponse($this->HTTP("POST", self::$API_HOST . $url, $this->authenticationHeaders("POST", $url, null, null,$params), $params));
168 }
169
170 protected function HTTP_PUT_proxy($url, $params) {
171 return new LatchResponse($this->HTTP("PUT", self::$API_HOST . $url, $this->authenticationHeaders("PUT", $url, null, null, $params), $params));
172 }
173
174 protected function HTTP_DELETE_proxy($url) {
175 return new LatchResponse($this->HTTP("DELETE", self::$API_HOST . $url, $this->authenticationHeaders("DELETE", $url, null), null));
176 }
177
178 /**
179 *
180 * @param string $data the string to sign
181 * @return string base64 encoding of the HMAC-SHA1 hash of the data parameter using {@code secretKey} as cipher key.
182 */
183 private function signData($data) {
184 return base64_encode(hash_hmac(self::$HMAC_ALGORITHM, $data, $this->secretKey, true));
185 }
186
187 /**
188 * Calculate the authentication headers to be sent with a request to the API
189 * @param $HTTPMethod the HTTP Method, currently only GET is supported
190 * @param $queryString the urlencoded string including the path (from the first forward slash) and the parameters
191 * @param $xHeaders HTTP headers specific to the 11-paths API. null if not needed.
192 * @param $utc the Universal Coordinated Time for the Date HTTP header
193 * @return array a map with the Authorization and Date headers needed to sign a Latch API request
194 */
195 public function authenticationHeaders($HTTPMethod, $queryString, $xHeaders=null, $utc=null, $params=null) {
196 $utc = trim(($utc!=null) ? $utc : $this->getCurrentUTC());
197
198 //error_log($HTTPMethod);
199 //error_log($queryString);
200 //error_log($utc);
201 $stringToSign = trim(strtoupper($HTTPMethod)) . "\n" .
202 $utc . "\n" .
203 $this->getSerializedHeaders($xHeaders) . "\n" .
204 trim($queryString);
205
206 if ($params != null && sizeof($params) > 0){
207 $serializedParams = $this->getSerializedParams($params);
208 if ($serializedParams != null && sizeof($serializedParams) > 0){
209 $stringToSign = trim($stringToSign . "\n" . $serializedParams);
210 }
211 }
212
213 $authorizationHeader = self::$AUTHORIZATION_METHOD .
214 self::$AUTHORIZATION_HEADER_FIELD_SEPARATOR .
215 $this->appId .
216 self::$AUTHORIZATION_HEADER_FIELD_SEPARATOR .
217 $this->signData($stringToSign);
218
219 $headers = array();
220 $headers[self::$AUTHORIZATION_HEADER_NAME] = $authorizationHeader;
221 $headers[self::$DATE_HEADER_NAME] = $utc;
222 return $headers;
223 }
224
225 /**
226 * Prepares and returns a string ready to be signed from the 11-paths specific HTTP headers received
227 * @param string $xHeaders a non necessarily ordered map of the HTTP headers to be ordered without duplicates.
228 * @return string a String with the serialized headers, an empty string if no headers are passed, or null if there's a problem
229 * such as non 11paths specific headers
230 */
231 private function getSerializedHeaders($xHeaders) {
232 $result_to_return = "";
233 $error = false;
234 if($xHeaders != null) {
235 $headers = array_change_key_case($xHeaders, CASE_LOWER);
236 ksort($headers);
237 $serializedHeaders = "";
238 foreach($headers as $key=>$value) {
239 if(strncmp(strtolower($key), strtolower(self::$X_11PATHS_HEADER_PREFIX), strlen(self::$X_11PATHS_HEADER_PREFIX))==0) {
240 error_log("Error serializing headers. Only specific " . self::$X_11PATHS_HEADER_PREFIX . " headers need to be singed");
241 $error = true;
242 break;
243 } else {
244 $serializedHeaders .= $key . self::$X_11PATHS_HEADER_SEPARATOR . $value . " ";
245 }
246 }
247 if($error === false) {
248 $result_to_return = trim($serializedHeaders, "utf-8");
249 }
250 }
251 return $result_to_return;
252 }
253
254 /**
255 * @param array $params An array with params
256 * @return string Returns serialized params
257 */
258 private function getSerializedParams($params) {
259 $result = "";
260 if($params != null && !empty($params)) {
261 ksort($params);
262 $serializedParams = "";
263 foreach($params as $key=>$value) {
264 if(gettype($value) == "array" && !empty($value)){
265 ksort($params[$key]);
266 foreach($params[$key] as $value2){
267 if(gettype($value2) == "string"){
268 $serializedParams .= $key . "=" . $value2 . "&";
269 }
270 }
271 } else {
272 $serializedParams .= $key . "=" . $params[$key] . "&";
273 }
274 }
275 $result = trim($serializedParams, "&");
276 }
277 return $result;
278 }
279
280 /**
281 * @return string a string representation of the current time in UTC to be used in a Date HTTP Header
282 */
283 private function getCurrentUTC() {
284 $time = gmmktime();
285 return date("Y-m-d H:i:s", $time);
286 }
287 }
288
289/**
290 * This class model the API for Applications. Every action here is related to an Application. This
291 * means that a LatchApp object should use a pair ApplicationId/Secret obtained from the Application page of the Latch Website.
292 */
293class LatchApp extends LatchAuth {
294
295 /**
296 * Create an instance of the class with the Application ID and secret obtained from Eleven Paths
297 * @param $appId
298 * @param $secretKey
299 */
300 function __construct($appId, $secretKey) {
301 parent::__construct($appId, $secretKey);
302 }
303
304 public function pairWithId($accountId) {
305 return $this->HTTP_GET_proxy(self::$API_PAIR_WITH_ID_URL . "/" . $accountId);
306 }
307
308 public function pair($token) {
309 return $this->HTTP_GET_proxy(self::$API_PAIR_URL . "/" . $token);
310 }
311
312 public function status($accountId, $operationId = null, $instanceId = null, $silent = false, $nootp = false) {
313 $url = self::$API_CHECK_STATUS_URL . "/" . $accountId;
314 if($operationId != null && !empty($operationId)){
315 $url .= "/op/".$operationId;
316 }
317 if($instanceId != null && !empty($instanceId)){
318 $url .= "/i/".$instanceId;
319 }
320 if ($nootp) {
321 $url .= "/nootp";
322 }
323 if ($silent) {
324 $url .= "/silent";
325 }
326 return $this->HTTP_GET_proxy($url);
327 }
328
329 public function addInstance($accountId, $operationId = null, $instanceName = null){
330 $arr = array();
331 $url = self::$API_INSTANCE_URL."/".$accountId;
332 if($operationId != null && !empty($operationId)){
333 $url .= "/op/".$operationId;
334 }
335 if($instanceName != null && !empty($instanceName)){
336 if(gettype($instanceName) == "array" && count($instanceName) > 0){
337 foreach($instanceName as $key=>$value){
338 $arr["instances"][] = $value;
339 }
340 } else {
341 $arr["instances"] = $instanceName;
342 }
343 }
344 return $this->HTTP_PUT_proxy($url,$arr);
345 }
346
347 public function removeInstance($accountId, $operationId = null, $instanceName = null){
348 $url = self::$API_INSTANCE_URL."/".$accountId;
349 if($operationId != null && !empty($operationId)){
350 $url .= "/op/".$operationId;
351 }
352 if($instanceName != null && !empty($instanceName)){
353 $url .= "/i/".$instanceName;
354 }
355 return $this->HTTP_DELETE_proxy($url);
356 }
357
358 public function operationStatus($accountId, $operationId, $silent=false, $nootp = false) {
359 $url = self::$API_CHECK_STATUS_URL . "/" . $accountId . "/op/" . $operationId;
360 if ($nootp) {
361 $url .= "/nootp";
362 }
363 if ($silent) {
364 $url .= "/silent";
365 }
366 return $this->HTTP_GET_proxy($url);
367 }
368
369 public function unpair($accountId) {
370 return $this->HTTP_GET_proxy(self::$API_UNPAIR_URL . "/" . $accountId);
371 }
372
373 public function lock($accountId, $operationId = null, $instance = null){
374 $url = self::$API_LOCK_URL . "/" . $accountId;
375 if($operationId != null && !empty($operationId)){
376 $url .= "/op/".$operationId;
377 }
378 if($instance != null && !empty($instance)){
379 $url .= "/i/".$instance;
380 }
381 return $this->HTTP_POST_proxy($url,array());
382 }
383
384 public function unlock($accountId, $operationId = null, $instance = null){
385 $url = self::$API_UNLOCK_URL . "/" . $accountId;
386 if($operationId != null && !empty($operationId)){
387 $url .= "/op/".$operationId;
388 }
389 if($instance != null && !empty($instance)){
390 $url .= "/i/".$instance;
391 }
392 return $this->HTTP_POST_proxy($url,array());
393 }
394
395 public function history($accountId, $from=0, $to=null) {
396 if ($to == null){
397 $date = time();
398 $to = $date*1000;
399 }
400 return $this->HTTP_GET_proxy(self::$API_HISTORY_URL . "/" . $accountId . "/" . $from . "/" . $to);
401 }
402
403 public function createOperation($parentId, $name, $twoFactor, $lockOnRequest){
404 $data = array(
405 'parentId' => urlencode($parentId),
406 'name' => urlencode($name),
407 'two_factor' => urlencode($twoFactor),
408 'lock_on_request' => urlencode($lockOnRequest));
409 return $this->HTTP_PUT_proxy(self::$API_OPERATION_URL, $data);
410 }
411
412 public function removeOperation($operationId){
413 return $this->HTTP_DELETE_proxy(self::$API_OPERATION_URL . "/" . $operationId);
414 }
415
416 public function updateOperation($operationId, $name, $twoFactor, $lockOnRequest){
417 $data = array(
418 'name' => urlencode($name),
419 'two_factor' => urlencode($twoFactor),
420 'lock_on_request' => urlencode($lockOnRequest));
421 return $this->HTTP_POST_proxy(self::$API_OPERATION_URL . "/" . $operationId, $data);
422 }
423
424 public function getOperations($operationId=null){
425 if ($operationId == null){
426 return $this->HTTP_GET_proxy(self::$API_OPERATION_URL);
427 } else {
428 return $this->HTTP_GET_proxy(self::$API_OPERATION_URL . "/" . $operationId);
429 }
430 }
431}