forked from masondixon/interact_sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
interact.php
executable file
·506 lines (409 loc) · 13.1 KB
/
interact.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
<?php
class interact
{
CONST SOAP_NS = 'ws.rsys.com';
CONST SOAP_NS_URSA = 'ws57.responsys';//
CONST SOAP_URI_URSA = 'urn:ws57.responsys';
CONST SOAP_SESS = 'SessionHeader';
CONST AUTH_SOAP_SESS = 'AuthSessionHeader';
CONST SOAP_JID = 'JSESSIONID';
CONST SOAP_ERROR_CLIENT = " Failed to create soap client ";
CONST SOAP_ERROR_LOGIN = " API Login failed, please check credentials and verify wsdl values. ";
CONST SOAP_ERROR_HEADER = " An error occurred during soap client creation - check soap header and cookie ";
CONST SOAP_ERROR_CALL_NAME = " Interact_API requires a soap call name in order to run API. " ;
protected $soapClientClass,
$endPoint,
$wsdl,
$uri,
$soapNameSpace,
$sessionId,
$jsessionId,
$apiCallName,
$apiCallParamData,
$params;
public static $isLoggedIn = false,
$soapClient;
public $result,
$debug = false;
function __construct(){}
/**
* Constructor responsible for creating PHP dynamic soap client
* Will throw an exception if there is a problem...
* @param unknown $pod_number WILL BE A 2 or 5 depending on the account location ws5.responsys.net* or ws2.responsys.net*
* @throws Exception
*/
public function intitializeSoapClient( $wsdl, $end_point, $client_class = "SoapClient" )
{
$this->setSoapParams( $wsdl, $end_point, $client_class );
if( !$this->setSoapClient() )
throw new Exception(" *** Soap Client Init Failed *** ");
}
private function setSoapParams( $wsdl, $end_point, $client_class = "SoapClient" )
{
$this->wsdl = $wsdl;
$this->endPoint = $end_point;
$this->uri = ( stristr("ws3", $wsdl) || stristr("ws4", $wsdl) ) ? self::SOAP_URI_URSA : null;
$this->soapNameSpace = ( stristr("ws3", $wsdl) || stristr("ws4", $wsdl) ) ? self::SOAP_NS_URSA : self::SOAP_NS;
$this->soapClientClass = $client_class;
}
private function setSoapHeaders( $authId = null )
{
$result = false;
$nameSpace = self::SOAP_NS;
$headerArray = array();
$soap_header_name = isset( $authId ) ? 'authSessionId' : 'sessionId';
$this->sessionId = isset( $authId ) ? $authId : $this->sessionId;
$sessionId = array( $soap_header_name => new SoapVar( $this->sessionId, XSD_STRING, null, null, null, $this->soapNameSpace ));
$sessionHeader = new SoapVar($sessionId, SOAP_ENC_OBJECT);
$headerArray[] = new SoapHeader( $this->soapNameSpace, self::SOAP_SESS, $sessionHeader);
// nullify headers here
self::$soapClient->__setSoapHeaders();
// set new values
if ( self::$soapClient->__setSoapHeaders( $headerArray ) )
$result = true;
return $result;
}
private function setSessionCookie()
{
$result = false;
$jsessionId = self::$soapClient->_cookies[ self::SOAP_JID ][ 0 ];
if( $jsessionId )
{
self::$soapClient->__setCookie( self::SOAP_JID, $jsessionId );
$result = true;
}
return $result;
}
private function setSoapClient()
{
$result = false;
$soapClientParams = array( 'location' => $this->endPoint,
'uri' => $this->uri,
'trace' => TRUE,
'connection_timeout' => 500000,
'keep_alive' => TRUE,
//'ssl_method' => SOAP_SSL_METHOD_SSLv3,
//'proxy_host' => "127.0.0.1",
//'proxy_port' => '8888',
'cache_wsdl' => WSDL_CACHE_NONE, ) ;
self::$soapClient = new $this->soapClientClass( $this->wsdl, $soapClientParams );
if( self::$soapClient instanceof SoapClient)
{
$result = true;
}
return $result;
}
private function print_xml()
{
if( $this->debug == true )
{
echo " ************************* \n";
echo "REQUEST HEADERS: \n";
echo $this::$soapClient->__getLastRequestHeaders();
echo " ************************* \n\n";
echo " ************************* \n";
echo "REQUEST: \n";
echo $this::$soapClient->__getLastRequest();
echo " ************************* \n\n";
echo " ************************* \n";
echo " RESPONSE HEADERS: \n";
echo $this::$soapClient->__getLastResponseHeaders();
echo " ************************* \n\n";
echo " ************************* \n";
echo " RESPONSE: \n";
echo $this::$soapClient->__getLastResponse();
echo " ************************* \n\n";
}
}
/**
* The soapClient call depends on class naming convention
* The get_class variable name will be the actual soapRequest method name
*/
public function execute( $instance )
{
$result = null;
try{
$result = self::$soapClient->{ get_class( $instance ) }( $instance->params );
} catch (Exception $ex){
$this->print_xml();
throw $ex;
}
$this->print_xml();
//if( $this->debug == true )
// print_r( $result );
return $result;
}
/**
* Include all native objects for calls
*/
private function loadObjects()
{
// Include the call type classes
$objects = glob( 'objects/*.php');
foreach( $objects as $object )
{
require_once( $object );
}
}
/**
*
* @param unknown $username
* @param unknown $password
* @throws Exception
* @return boolean
*
* Wrapper for login call, that persists session for you
* The sessionId in the soap header and jsessionId cookie need to be persisted in order for subsequent calls to work
*/
public function login( $username, $password )
{
$result = false;
if( !self::$isLoggedIn )
{
$loginParamArray = array( 'username' => $username,'password' => $password );
$sessionDetails = self::$soapClient->login( $loginParamArray );
if( $sessionDetails )
{
$this->sessionId = $sessionDetails->result->sessionId;
/**
* This part is critical to the API session persistence piece..
*/
if( !$this->setSoapHeaders() || !$this->setSessionCookie() )
{
throw new Exception( self::SOAP_ERROR_HEADER );
}
else
{
if ($this->debug){
echo "Logged in -> session_id : " . $this->sessionId . "\n";
}
self::$isLoggedIn = true;
$result = true;
if( $this->debug == true )
$this->print_xml();
}
}
else
{
throw new Exception (self::SOAP_ERROR_LOGIN);
}
}
else
{
if ($this->debug) {
echo "Attempt to login when already logged in";
}
}
return $result;
}
/**
* Wrapper for logout
* There is a max concurrent session allowance which if exceeded, will lock out the api user
* Its IMPORTANT to logout in other words
*/
public function logout()
{
$result = false;
if( self::$isLoggedIn )
{
try {
$loggedOut = self::$soapClient->logout();
if( $this->debug == true )
$this->print_xml();
if( $loggedOut->result == 1 )
{
self::$isLoggedIn = false;
self::$soapClient = null;
if( $this->debug == true ) {
echo "Logged Out from sessionId : " . $this->sessionId . "\n";
}
$result = true;
}
else
{
if( $this->debug == true ) {
echo "Unable to perform logout";
$this->print_xml();
}
}
}
catch( \Exception $ex) {
self::$isLoggedIn = false;
self::$soapClient = null;
if( $this->debug == true ) {
echo "Error logging out: " . $ex->getMessage();
}
throw $ex;
}
}
return $result;
}
public function getSoapFunctions()
{
$functions = self::$soapClient->__getFunctions();
return $functions;
}
/** START LOGINWITHCERTIFICATE FUNCTIONS
* These functions deal exclusively with the loginWithCertificate call
* The call is meant to add extra security to the login process, but actually is more trouble than its worth
* Regular login with password over ssh is the main login method used by most consumers
*/
//FUNCTION THAT RECURSIVELY PACKS ARRAY INTO BINARY STRING
private function packer( $array )
{
return call_user_func_array('pack', array_merge(array('C*'), $array ));
}
/**
* Returns boolean
* The auth session id and challenges will be accessible in $this->result property
*
* This function is called first to receive a temporary session for the actual login call
* Also verifies that user exists and is configured for login with certificate process on the Responsys side
*/
private function authServer( $user, $challenge, $wsdl, $endpoint)
{
$result = null;
$this->setSoapParams( $wsdl, $endpoint, $this->soapClientClass );
if( $this->setSoapClient() )
{
$instance = new authenticateServer($user, $challenge);
$result = $this->execute( $instance );
}
else
{
throw new SoapFault( self::SOAP_ERROR_CLIENT );
}
return $result;
}
/**
*
* @param string $user
* @param string $byte_array_challenge
* @param string $wsdl
* @param string $endpoint
* @param string $responsys_public_cert_path
* @param string $client_private_key_path
*/
public function loginWithCertificate( $user, $byte_array_challenge, $wsdl, $endpoint, $responsys_public_cert_path, $client_private_key_path)
{
if( self::$isLoggedIn ) {
if ($this->debug) {
echo "Attempt to loginWithCertificate when already logged in";
}
return false;
}
// RESPONSYS PUBLIC CERT
$responsys_certificate_file = file_get_contents( $responsys_public_cert_path );
// PRIVATE KEY REQUIRED FOR ENCRYPTING SERVER CHALLENGE - obtained from openssl cert & key creation process
$mdixon_certificate_file = file_get_contents( $client_private_key_path );
$byte_array = array();
$container = array();
$container2 = array();
$container3 = array();
// Convert to integer for java
$len = strlen( $byte_array_challenge );
for( $i = 0; $i < $len; $i++ )
{
$byte_array[] = ord( $byte_array_challenge[ $i ]);
}
// Run authServer call to verify user and get a temporary sessionId for login call
$result = $this->authServer( $user, $byte_array, $wsdl, $endpoint );
//print_r( $result );
$authId = $result->result->authSessionId;
$encServerChallenge = $result->result->encryptedClientChallenge;
$serverChallenge = $result->result->serverChallenge;
// Hack to deal with null returns in challenge strings ( weak! )
// The trick is setting the null value to 128 - which should actually be out of range for java since it uses -128 through 127 but it works...
foreach ( $encServerChallenge as $key => $val )
{
$val = trim($val);
if( isset( $val ) && $val!== null && $val !== "" )
{
$container[] = $val;
}
else
{
$container[] = 128;
}
}
// Now we have to decrypt the binary string
$decryptMe = $this->packer( $container );
// USE RESP CERT TO DECYRPT THE VALUE AND DIFF ON ORIGINAL STRING VALUE !!!
// GET A X509 INSTANCE, THEN GET THE PUBLIC KEY FOR openssl_public_decrypt
$x509 = openssl_x509_read( $responsys_certificate_file );
openssl_x509_export($x509, $newX509 );
$pubKey = openssl_get_publickey( $newX509 );
if ( openssl_public_decrypt( $decryptMe, $decrypted, $pubKey, OPENSSL_PKCS1_PADDING ) )
{
$data = $decrypted;
}
else
{
throw new Exception( "Failed to decrypt the challenge " . openssl_error_string() );
}
// Compare the original string to the decrypted data string
// If this matches then the decryption logic is proper, and we proceed to login call
if( $byte_array_challenge == $data)
{
// Get the private key for encrypting the return
$private_key = openssl_get_privatekey( $mdixon_certificate_file );
foreach( $serverChallenge as $j => $b )
{
$val2 = trim($b);
if( isset( $val2 ) && $val2!== null && $val2 !== "" )
{
$container2[] = $val2;
}
else
{
$container2[] = 128;
}
}
$encryptMe = $this->packer( $container2 );
if ( !openssl_private_encrypt($encryptMe, $encryptedData, $private_key, OPENSSL_PKCS1_PADDING ) )
{
throw new Exception( "Failed to Encrypt data with Private Key - exiting");
}
// Now we have to unpack data which converts unsigned encrypted data to signed byte type
// to play nice with java signed byte type
$unpackedData = unpack('c*', $encryptedData );
// Now iterate through bytes, and set the 128 values to null since API allows / sends nulls
foreach ( $unpackedData as $m => $d )
{
$val3 = trim( $d );
if( $val3 == 128 )
{
$container3[] = null;
}
else
{
$container3[] = $val3;
}
}
// Now we have all of the parts, set the authId as the session id for the login call, and send in the prepared encrypted server challenge
// If all goes well we will get a sessionId in the result of the upcoming call.
$this->setSoapHeaders( $authId );
$this->setSessionCookie();
$loginWithCertObj = new loginWithCertificate( $container3 );
$result = $this->execute( $loginWithCertObj );
if ($result) {
$this->sessionId = $result->result->sessionId;
if( !$this->setSoapHeaders() || !$this->setSessionCookie() )
{
throw new Exception( self::SOAP_ERROR_HEADER );
}
self::$isLoggedIn = true;
}
else {
throw new Exception (self::SOAP_ERROR_LOGIN);
}
//print_r( $result );
return $result;
}
else
{
throw new SoapFault("Problem during handshake - exiting");
}
}
}
?>