3 # MoneyDesktop data feed using MDX 5 specifications 8 # note that request will include either userkey (indicating SSO) 9 # or login/password (indicating aggregated access) 11 # login : login id, or possibly cu:member 12 # password : password for aggregated account 13 # userkey : token for SSO access (replaces both login and password) 14 # start_date : Start date, default to 30days past 15 # page : selected page of possible pagination. We always use page=1 of 1 18 $serviceMinimal =
true;
19 $serviceShowInfo =
false;
20 $serviceLoadMenu =
false;
21 $serviceShowMenu =
false;
25 require_once(dirname(__FILE__) .
'/../library/hcuService.i');
26 require_once(dirname(__FILE__) .
'/../library/hcuDispFunctions.i');
27 require_once(
'hcuAuthShared.i');
28 require_once(
'MDesk_API.i');
29 require_once(
'cutrusted.i');
30 require_once(
'LogSSO.i');
59 # php 5 import variables 64 "start_date" => array(
'filter' => FILTER_SANITIZE_STRING),
65 "page" => array(
'filter' => FILTER_SANITIZE_STRING)
68 HCU_ImportVars($inPost,
"", $varOk);
71 $SENDAS =
'XML'; #
set a sane
default - can be overridden in trusted detail -
72 # but need a value in case of early error. 73 # consider trusted detail settings for these ? 74 $client_source_override =
'MDX'; # unique
for Mx
76 function trim_item(&$item, $key) {
81 array_walk_recursive($inPost,
'trim_item');
83 $mxReqPathArr = explode(
"/", $_SERVER[
'PATH_INFO']);
85 $CU = HCU_array_key_value(1, $mxReqPathArr);
86 # look a little closer at that CU value -- 87 # ctype_alnum ensures not empty / null, and contains only letters / digits 88 if (!ctype_alnum($CU)) {
89 throw new Exception(
"Invalid Request ", 9401); # empty ORG
92 if (!hcu_checkService($dbh,
"MDESK")) {
93 $omsg = hcu_checkServiceMsg($dbh,
"MDESK");
94 throw new Exception(
"$omsg", 9503);
97 # cutrusted_read will fall back to master if detail not found 98 $mxTrusted = cutrusted_read($dbh, array(
'Cu' => $CU,
'trustedid' =>
'MDesk3'));
100 if ($mxTrusted[
'status'][
'Response'] ==
'false') {
101 throw new Exception(
"Client not configured for remote Mx access", 9500); # missing trusted detail
103 if (HCU_array_key_exists(
'data', $mxTrusted)) {
104 $mxParms = $mxTrusted[
'data'];
106 throw new Exception(
"Client not configured for remote Mx access", 9500); # missing trusted detail parms
108 # previous hcu_checkService looks for all MDesk services down at server level 109 # now look for MDesk service down for this particular client 110 if (HCU_array_key_value(
'mxSvsDown', $mxParms)) {
111 $omsg = (HCU_array_key_value(
'mxDownMsg', $mxParms) ? $mxParms[
'mxDownMsg'] :
'Service temporarily unavailable. Please try again later');
112 throw new Exception(
"$omsg", 9503);
115 # mdTokenKey used to hash Mx user key 116 $mdtokenkey = HCU_array_key_value(
'mdTokenKey', $mxParms);
118 if (empty($mdtokenkey)) {
119 throw new Exception(
"Client misconfigured for remote Mx access", 9500); # missing trusted detail parms (keys)
122 # revisit this code - trying to ensure ipWhiteList is array for array merge, even if it is empty 123 $ipWhiteList = ( HCU_array_key_exists(
'ipWhiteList', $mxParms) ? HCU_array_key_value(
'ipWhiteList', $mxParms) :
'' );
124 # strip anything other than digits, hyphens, dots, and the commas 125 $ipWhiteList = preg_replace(
'/[^0-9-\.\/,]/',
'', $ipWhiteList);
127 if (!empty($ipWhiteList)) {
129 $ipArray = explode(
',', $ipWhiteList);
134 # add HomeCU addresses to array 135 $ipallow = array_merge(array(
'199.184.207', # allow DMS/Homecu also
137 '172.16.0.0/12', # 172.16.0.0 – 172.31.255.255
for Docker
138 '174.129.8.163', #(www6)
139 '174.129.23.225', #(www5)
140 '107.21.119.104', #(www3)
141 '107.20.248.233', #(www0 / devel)
142 '34.214.70.112', #(my.homecu.net / alpha.homecu.io)
143 '34.214.139.200', #(my.homecu.net / alpha.homecu.io)
144 '34.214.230.179', #(my.homecu.net / alpha.homecu.io)
145 '209.161.7.186', # (DMS DSL Line)
146 '184.73.202.7' # (monitor)
149 $IPCaller = $_SERVER[
'REMOTE_ADDR'];
150 $IPPass = whitelist($IPCaller, $ipallow);
152 if ($IPPass ==
false) {
153 throw new Exception(
"Unauthorized Access {$_SERVER['REMOTE_ADDR']}", 9403); # IP whitelist fail
156 # default to production mode 157 $testing = ( HCU_array_key_exists(
'testing', $mxParms) ? HCU_array_key_value(
'testing', $mxParms) : 0 );
160 # HCU-issued sharekey used by trusted vendor for hashing CRED2/CRED3 161 # maybe not needed for MDX5? 162 $mdkey = HCU_array_key_value(
'mdTestKey', $mxParms);
163 $mdAPI_key = HCU_array_key_value(
'testAPIKey', $mxParms);
164 $mdData_URL = HCU_array_key_value(
'testServerURL', $mxParms);
165 $mxHMACKey = HCU_array_key_value(
'testHMACKey', $mxParms);
167 # HCU-issued sharekey used by trusted vendor for hashing CRED2/CRED3 168 # maybe not needed for MDX5? 169 $mdkey = HCU_array_key_value(
'mdShareKey', $mxParms);
170 $mdAPI_key = HCU_array_key_value(
'APIKey', $mxParms);
171 $mdData_URL = HCU_array_key_value(
'ServerURL', $mxParms);
172 $mxHMACKey = HCU_array_key_value(
'HMACKey', $mxParms);
176 throw new Exception(
"Client misconfigured for remote authentication", 9500); # missing trusted detail parms (keys)
179 if (empty($mxHMACKey)) {
180 throw new Exception(
"Client misconfigured for remote Mx access", 9500); # missing trusted detail parms (keys)
183 $mxRestOpt = end($mxReqPathArr);
184 if ($mxRestOpt ==
'sessions') {
185 $mxSessionData = file_get_contents(
'php://input');
187 $mxSessionData =
''; #
'accounts' &
'transactions' are GET - no body
189 $headers = apache_request_headers();
190 $validRequest = mxValidRequest($headers, $mxHMACKey, $mxRestOpt, $mxSessionData);
192 if (HCU_array_key_value(
'status', $validRequest)) {
193 $reason = HCU_array_key_value(
'message', $validRequest);
194 throw new Exception(
"Invalid Request", 9412);
197 $mdExtendable = ( HCU_array_key_exists(
'mdExtendable', $mxParms) ? HCU_array_key_value(
'mdExtendable', $mxParms) : 0 );
198 if ($mdExtendable && ( empty($mdAPI_key) || empty($mdData_URL) )) {
199 throw new Exception(
"Client misconfigured for extendable token", 9500); # missing trusted detail parms (URL &/or API key)
202 $SENDAS = ( HCU_array_key_exists(
'resultsAs', $mxParms) ? HCU_array_key_value(
'resultsAs', $mxParms) :
'XML' );
203 $client_key_TTL = ( HCU_array_key_exists(
'mdTokenTTL', $mxParms) ? HCU_array_key_value(
'mdTokenTTL', $mxParms) : 1800 );
205 # start building a stub HB_ENV 207 $HB_ENV[
'platform'] = $client_source_override;
208 $HB_ENV[
'client_key_TTL'] = $client_key_TTL;
209 $UPDAWARE = 0; # Mx cannot update security -
210 # set a 'No Activate' flag so we don't try to add live users w/pin 211 # this could be a trusted detail also, but in this case Mx 212 # cannot 1st-time activate, so force the setting 213 $HB_ENV[
'NoActivation'] = 1;
215 if (!in_array($mxRestOpt, array(
'sessions',
'accounts',
'transactions'))) {
216 throw new Exception(
'Invalid Request', 9404);
219 if ($mxRestOpt ==
'accounts' || $mxRestOpt ==
'transactions') {
220 $mxSessionKey = HCU_array_key_value(
'MDX-Session-Key', $headers);
221 if (empty($mxSessionKey)) {
222 throw new Exception(
"No Session Key", 9401);
224 $mxBundle = openMxKey($CU, $mxSessionKey);
225 if (empty($mxBundle)) {
226 throw new Exception(
"Invalid Session Key (empty)", 9401);
228 if (HCU_array_key_value(
'kt', $mxBundle) !=
'A') {
229 throw new Exception(
"Invalid Session Key (type)", 9401);
231 if (HCU_array_key_value(
'ttl', $mxBundle) < time()) {
232 throw new Exception(
"Expired Session Key", 9401);
235 $login = HCU_array_key_value(
'u', $mxBundle);
237 throw new Exception(
"Invalid Session Key (user)", 9401);
240 Load_HB_ENVc($dbh, $CU, $login, $HB_ENV); # user_name / member number problem USERNAME
241 # make sure we found somebody 242 if (!HCU_array_key_value(
'rowfound', $HB_ENV)) {
243 throw new Exception(
"Invalid User", 9404);
246 $userrec = GetUserbyName($dbh, $CU, $login);
247 if (!HCU_array_key_value(
'rowfound', $userrec)) {
248 throw new Exception(
"Invalid Session Key (user)", 9401);
251 mdesk_setLogging($dbh, $CU, $login, $mxParms);
253 $mfadate = HCU_array_key_value(
'mfadate', $userrec);
254 if (HCU_array_key_value(
'cd', $mxBundle) < $mfadate) {
255 throw new Exception(
"Invalid Session Key (old cred)", 9401);
259 if ($mxRestOpt ==
'sessions') {
260 $mxSessionKey = HCU_array_key_value(
'MDX-Session-Key', $headers);
261 if (!empty($mxSessionKey)) {
262 $mxBundle = openMxKey($CU, $mxSessionKey);
263 if (empty($mxBundle)) {
264 throw new Exception(
"Invalid Session Key (empty)", 9401);
266 if (HCU_array_key_value(
'kt', $mxBundle) !=
'S') {
267 throw new Exception(
"Invalid Session Key (type)", 9401);
269 if (HCU_array_key_value(
'ttl', $mxBundle) < time()) {
270 throw new Exception(
"Expired Session Key", 9401);
273 $login = HCU_array_key_value(
'u', $mxBundle);
275 throw new Exception(
"Invalid Session Key (user)", 9401);
277 Load_HB_ENVc($dbh, $CU, $login, $HB_ENV); # user_name / member number problem USERNAME
278 # make sure we found somebody 279 if (!HCU_array_key_value(
'rowfound', $HB_ENV)) {
280 throw new Exception(
"Invalid User", 9404);
283 $userrec = GetUserbyName($dbh, $CU, $login);
284 if (!HCU_array_key_value(
'rowfound', $userrec)) {
285 throw new Exception(
"Invalid Session Key (user)", 9401);
288 mdesk_setLogging($dbh, $CU, $login, $mxParms);
290 # initial sessions request / no session key 293 # parse body xml in mxSessionData to get parameters to test 294 $sessionXML = simplexml_load_string($mxSessionData,
'SimpleXMLElement', LIBXML_NOCDATA);
295 $sessionJSON = json_encode($sessionXML);
296 $sessionArray = json_decode($sessionJSON,
true);
299 $session = HCU_array_key_value(
'session', $sessionArray);
301 if (HCU_array_key_value(
'userkey', $session)) {
302 # got a userkey, validate it 304 $ckKey = CheckV94key($CU, HCU_array_key_value(
'userkey', $session), $mdtokenkey,
'S');
305 # if userkey check fails 306 if (HCU_array_key_value(
'Code', $ckKey[
'Status']) > 0) {
307 $status = HCU_array_key_value(
'Message', $ckKey[
'Status']);
308 $code = HCU_array_key_value(
'Code', $ckKey[
'Status']);
309 # be careful what you add to status message - 310 # unescaped characters will cause XML parse error on error output 312 throw new Exception(
"Invalid Credentials (userkey) {$status}", 9401);
314 # set $login from userkey content 315 $apptokarr = HCU_array_key_value(
'data', $ckKey);
316 $login = HCU_array_key_value(
'A', $apptokarr);
318 $userrec = GetUserbyName($dbh, $CU, $login);
319 if (!HCU_array_key_value(
'rowfound', $userrec)) {
320 throw new Exception(
"Invalid User", 9404);
323 mdesk_setLogging($dbh, $CU, $login, $mxParms);
326 $cktoken = MakeV94Dkey($CU, $login, $userrec, $client_key_TTL, $mdtokenkey,
'S');
327 parse_str(urldecode($cktoken), $ckarr);
328 if (HCU_array_key_value(
'P', $ckarr) !== HCU_array_key_value(
'P', $apptokarr)) {
329 throw new Exception(
"Invalid Credentials (authentication required)", 9401);
332 # if configured to do so and the userkey is more than 15 min old 333 # extend the userkey stored at Mx 334 $ckExp = HCU_array_key_value(
'E', $ckarr);
335 $tkExp = HCU_array_key_value(
'E', $apptokarr);
336 if ($ckExp - $tkExp > 900) {
337 # if the new token would expire more than 15min later than the current one, set a new one 338 mdesk_setLogging($dbh, $CU, $login, $mxParms);
339 $mxUpdated = mdesk_sync($mdData_URL, $mdAPI_key, $CU, $HB_ENV[
'Uid'], $HB_ENV[
'Cn'], $HB_ENV[
'Ml'], $cktoken, $mxParms);
340 if (HCU_array_key_value(
'code', $mxUpdated) > 0) {
341 $status = HCU_array_key_value(
'status', $mxUpdated);
342 $code = HCU_array_key_value(
'code', $mxUpdated);
343 throw new Exception(
"Error ({$status} {$code}). Unable to connect to MoneyDesktop.", 9500);
348 # no userkey - check login and password 349 $login = HCU_array_key_value(
'login', $session);
350 $userpass = HCU_array_key_value(
'password', $session);
352 if (empty($login) || preg_match(
"/[\\\`,\"\s;]/", $login) || empty($userpass)) { # user_name / member number problem MEMBERACCT
353 throw new Exception(
"Invalid Credentials", 9400); # Member Bad Characters or empty passwd
356 mdesk_setLogging($dbh, $CU, $login, $mxParms);
358 # strip leading zeroes, unless configured not to do so 359 if (!(preg_match(
"/\D/", $login)) && !HCU_array_key_value(
'preserveZeros', $mxParms)) {
360 $login = preg_replace(
"/^0*/",
"", $login);
364 # parse body xml in mxSessionData to get parameters to test 365 $sessionXML = simplexml_load_string($mxSessionData,
'SimpleXMLElement', LIBXML_NOCDATA);
366 $sessionJSON = json_encode($sessionXML);
367 $sessionArray = json_decode($sessionJSON,
true);
370 $session = HCU_array_key_value(
'session', $sessionArray);
372 if (HCU_array_key_value(
'login', $session) || HCU_array_key_value(
'password', $session)) {
373 # if got login and password? 374 $login = HCU_array_key_value(
'login', $session);
375 $userpass = HCU_array_key_value(
'password', $session);
377 if (empty($login) || preg_match(
"/[\\\`,\"\s;]/", $login) || empty($userpass)) { # user_name / member number problem MEMBERACCT
378 throw new Exception(
"Invalid Credentials", 9400); # Member Bad Characters or empty passwd
380 # strip leading zeroes, unless configured not to do so 381 if (!(preg_match(
"/\D/", $login)) && !HCU_array_key_value(
'preserveZeros', $mxParms)) {
382 $login = preg_replace(
"/^0*/",
"", $login);
384 Load_HB_ENVc($dbh, $CU, $login, $HB_ENV); # user_name / member number problem USERNAME
385 # make sure we found somebody 386 if (!HCU_array_key_value(
'rowfound', $HB_ENV)) {
387 throw new Exception(
"Invalid User", 9404);
389 # removed this part of the test - failed because the hash doesn't start with $1$ anymore 390 # and ... ancient news that older crypt only tested first 8 char of passwd and would get 391 # false match. MD5 cured that (hence the $1$ test), and we have long since moved on 392 # && ( preg_match('/^\$1\$/', $HB_ENV['password']) || strlen($userpass) < 9) 394 if (!(trim($userpass) >
'' && password_verify($userpass, $HB_ENV[
'password']) )) {
395 LogFail($dbh, $HB_ENV, array(
'ORG' => $CU), GetUserFlagsValue(
'MEM_LOGIN_FAILED_PWD'));
396 throw new Exception(
"Authentication Failed (password)", 9401); # password
398 $userrec = GetUserByName($dbh, $CU, $login); # user_name / member number problem USERNAME
399 # make sure we found somebody 400 if (!HCU_array_key_value(
'rowfound', $userrec)) {
401 throw new Exception(
"Invalid User", 9404);
404 # if locked, throw error 4011 Locked Account 405 if ($userrec[
'lockedacct']) {
406 throw new Exception(
'Member Account Locked', 9401); # locked
408 if (($userrec[
'forceupdate'] && ($UPDAWARE & $userrec[
'forceupdate']) < $userrec[
'forceupdate'] ) ||
409 empty($userrec[
'email'])) {
410 throw new Exception(
'Member Account Access Blocked', 9401); # Blocked waiting
for required security updates
413 # password ok, if 2-factor, send MFA challenge or fall thru to send data 415 if (HCU_array_key_value(
'cver', $HB_ENV) ==
'F') {
417 # build MFA-pending key 420 'p' => $password, # really?
422 'ttl' => time() + 600, # session key, 10 minutes to complete
423 'cd' => time()); # show MFA change date as now
424 $SessionKey = createMxKey($CU, $mxBundle);
425 if (empty($SessionKey)) {
426 throw new Exception(
"Key Creation Failed", 9500);
430 $data_reply = Mx5_challenge($dbh, $HB_ENV, $MC);
431 if (HCU_array_key_value(
'Status', $data_reply) !==
'Success') {
432 throw new Exception(
"Building Challenge " . $data_reply[
'Status'], 9500); # email
434 $reply_arr = HCU_array_key_value(
'data', $data_reply);
435 $reply_arr[
'session'][
'key'] = $SessionKey;
437 # login/password validated, and not 2-factor 438 # build UserKey with TTL lifespan to store at Mx 440 $userrec = GetUserbyName($dbh, $CU, $HB_ENV[
'Cn']);
441 if (!HCU_array_key_value(
'rowfound', $userrec)) {
442 throw new Exception(
"Invalid User", 9404);
444 $mxtoken = MakeV94Dkey($CU, $HB_ENV[
'Cn'], $userrec, $client_key_TTL, $mdtokenkey,
'S');
455 LogPass($dbh, $HB_ENV);
456 # and build a 10-minute session key 460 'ttl' => time() + 600, # session key, 10 minutes
462 $SessionKey = createMxKey($CU, $mxBundle);
463 if (empty($SessionKey)) {
464 throw new Exception(
"Session Key Creation Failed", 9500);
467 $reply_arr = array(
'session' => array(
'key' => $SessionKey,
'mxToken' => $mxtoken));
469 } elseif (HCU_array_key_exists(
'challenges', $session) && is_array($session[
'challenges'])) {
472 # if invalid, throw error 9401 476 # we have challenge responses - pack them into a list for validation 477 $challresp = array();
478 foreach ($session[
'challenges'][
'challenge'] as $chalkey => $chalinfo) {
479 $challresp[$chalinfo[
'id']] = $chalinfo[
'answer'];
499 if (!HCU_array_key_value(
'MFA_E', $challresp) || !isValidEmail(HCU_array_key_value(
'MFA_E', $challresp), $userrec)) {
500 LogFail($dbh, $HB_ENV, array(
'ORG' => $CU), GetUserFlagsValue(
'MEM_LOGIN_FAILED_EMAIL'));
501 throw new Exception(
"MFA Failed (EML)", 9401); # email
503 # and now check the challenge responses 510 $tmpEnv[
'mfaquest'] = $HB_ENV[
'mfaquest'];
511 $tmpEnv[
'Fset2'] = 0; # turn off
'1 random' 512 list($qfail, $qreason) = MFQ_response($tmpEnv, $challresp);
515 LogFail($dbh, $HB_ENV, array(
'ORG' => $CU), $failreason, GetUserFlagsValue(
'MEM_LOGIN_FAILED_QST'));
516 throw new Exception(
"MFA Failed (Challenge)", 9401); # email
518 # challenge passed, build a UserKey to store at Mx 520 $userrec = GetUserbyName($dbh, $CU, $HB_ENV[
'Cn']);
521 if (!HCU_array_key_value(
'rowfound', $userrec)) {
522 throw new Exception(
"Invalid User", 9404);
524 $mxtoken = MakeV94Dkey($CU, $HB_ENV[
'Cn'], $userrec, $client_key_TTL, $mdtokenkey,
'S');
535 LogPass($dbh, $HB_ENV);
537 # and build a 10-minute authenticated session key 541 'ttl' => time() + 600, # session key, 10 minutes
542 'cd' => $userrec[
'mfadate']);
543 $SessionKey = createMxKey($CU, $mxBundle);
544 if (empty($SessionKey)) {
545 throw new Exception(
"Session Key Creation Failed", 9500);
549 $reply_arr = array(
'session' => array(
'key' => $SessionKey));
550 } elseif (HCU_array_key_exists(
'userkey', $session)) {
551 # elseif got userkey? 552 # if invalid throw error 9401 553 # if extendable && foreground 555 # AuthMode=SSO w/Userkey 557 $userkey = HCU_array_key_value(
'userkey', $session);
558 $apptokarr = array();
559 parse_str(urldecode($userkey), $apptokarr);
562 if (HCU_array_key_exists(
'E', $apptokarr) && HCU_array_key_exists(
'H', $apptokarr)) {
563 $login = $apptokarr[
'A'];
564 $HMethod = (HCU_array_key_exists(
'P', $apptokarr) ?
'S' :
'M');
565 $keycheck = CheckV94key($CU, $userkey, $mdtokenkey, $HMethod);
567 if ($keycheck[
'Status'][
'Message'] !==
'Success') {
568 throw new Exception($keycheck[
'Status'][
'Message'], 9401);
571 throw new Exception(
"Invalid Userkey Credentials Corrupt", 9401);
573 # and build a 10-minute authenticated session key 577 'ttl' => time() + 600, # session key, 10 minutes
578 'cd' => time()); # used $userrec[
'mfadate'] but userrec undefined here?
579 $SessionKey = createMxKey($CU, $mxBundle);
580 if (empty($SessionKey)) {
581 throw new Exception(
"Session Key Creation Failed", 9500);
583 $reply_arr = array(
'session' => array(
'key' => $SessionKey));
584 $mxSessionKey = $SessionKey; # so logging gets the right value
585 $mxParms[
"environment"][
"reply"] =
"$SessionKey";
587 # throw error 4102 no userkey and no credentials 588 throw new Exception(
"Invalid Userkey Credentials Missing ", 9401);
591 if ($mxRestOpt ==
'accounts') {
592 # checking key, loading HB_ENV & userrec occurs at top 593 # return account data 595 $HB_ENV[
'HideCert'] = HCU_array_key_value(
'HideCert', $mxParms);
596 $data_reply = Mx5_accts($dbh, $HB_ENV);
598 if (HCU_array_key_value(
'Status', $data_reply) !==
'Success') {
599 throw new Exception(
"Retrieving Accounts " . $data_reply[
'Status'], 9500); # email
601 $reply_arr = HCU_array_key_value(
'data', $data_reply);
602 if (HCU_array_key_value(
'hashlist', $data_reply) && is_array($data_reply[
'hashlist']) ) {
603 $mxParms[
"environment"][
"reply"] =
'';
604 foreach($data_reply[
'hashlist'] as $hash => $acct) {
605 $mxParms[
"environment"][
"reply"] .=
"$hash : {$acct['acct']} \n";
609 if ($mxRestOpt ==
'transactions') {
610 # checking key, loading HB_ENV & userrec occurs at top 612 # return transaction data 613 #GET /client-id/accounts/:account_id/transactions?start_date=YYYY-MM-DD&page=n 614 # parse to get $account_id and $start date, set $end_date 615 # tie the array index to 'accounts' +1 in case the path changes? 616 # account_id should be encrypted or something to hide it 617 # and then un-hide it here before calling for history 618 $account_id = $mxReqPathArr[3];
619 $HB_ENV[
'HideCert'] = HCU_array_key_value(
'HideCert', $mxParms);
620 $hcuList = Mx5_accts($HB_ENV[
'dbh'], $HB_ENV);
623 if (HCU_array_key_exists(
'hashlist', $hcuList) &&
624 HCU_array_key_exists($account_id, $hcuList[
'hashlist'])) {
625 $KEYACCTID = $hcuList[
'hashlist'][$account_id][
'acct']; # gets C|666665|12
626 $KEYACCTPOS = $hcuList[
'hashlist'][$account_id][
'pos']; # gets numeric idx
627 $mxParms[
"environment"][
"reply"] =
"$account_id : $KEYACCTID ";
630 throw new Exception(
"Requested account not found", 9404); # email
633 "start_date" => array(
'filter' => FILTER_SANITIZE_STRING),
634 "end_date" => array(
'filter' => FILTER_SANITIZE_STRING),
635 "page" => array(
'filter' => FILTER_SANITIZE_NUMBER_INT));
637 HCU_ImportVars($mxPosted,
"", $varOk);
638 $start_date = HCU_array_key_value(
'start_date', $mxPosted);
639 $end_date = HCU_array_key_value(
'end_date', $mxPosted);
640 $dflt_end = date(
"Y-m-d", time() + (4 * 24 * 60 * 60)); # + 4 days
641 $end_date = (empty($end_date) ? $dflt_end : $end_date);
642 $page = HCU_array_key_value(
'page', $mxPosted);
643 $page = (empty($page) ? 1 : $page);
645 $balinfo = $hcuList[
'data'][$KEYACCTPOS][
'account'];
648 switch (substr($KEYACCTID, 0, 1)) {
650 # test for locked / unviewable 652 $data_reply = Mx5_dpTrans($dbh, $HB_ENV, $KEYACCTID, $balinfo, $start_date, $end_date);
655 # test for locked / unviewable 656 # test for info from 3rd party, treat as unviewable 658 $data_reply = Mx5_lnTrans($dbh, $HB_ENV, $KEYACCTID, $balinfo, $start_date, $end_date);
661 # test for locked / unviewable 662 # test for info from 3rd party, treat as unviewable 664 $data_reply = Mx5_ccTrans($dbh, $HB_ENV, $KEYACCTID, $balinfo, $start_date, $end_date);
667 throw new Exception(
"Invalid account requested", 9404); # email
671 if (HCU_array_key_value(
'Status', $data_reply) !==
'Success') {
672 throw new Exception(
"Retrieving Transactions " . $data_reply[
'Status'], 9500); # email
675 $reply_arr = HCU_array_key_value(
'data', $data_reply);
676 if (HCU_array_key_exists(
'account',$reply_arr) && HCU_array_key_exists(
'transactions', $reply_arr[
'account'])) {
677 # transactions array has 1 entry even if there are no actual transactions 678 $trancount = (integer) count($reply_arr[
'account'][
'transactions']) -1;
679 $mxParms[
"environment"][
"reply"] .=
"\n[$start_date - $end_date $trancount transactions]";
686 if ($mxParms[
"logging"] ==
"enabled") {
687 $logParms = $mxParms[
"environment"];
689 $logSessionData = file_get_contents(
'php://input');
690 $logheaders = apache_request_headers();
691 $logReqPathArr = explode(
"/", $_SERVER[
'PATH_INFO']);
692 $logEnd = end($logReqPathArr);
694 $logParms[
"token"] = $mxSessionKey;
695 $logParms[
"txnId"] = time();
696 $logParms[
"logPoint"] =
"MDX $logEnd";
697 $logParms[
"request"] =
"HEADERS: " . json_encode($logheaders) .
"\n\n";
698 $logParms[
"request"] .=
"PATH: {$_SERVER['PATH_INFO']}\n";
699 $logParms[
"request"] .=
"INPUT: $logSessionData";
701 LogSSOActivity($logParms);
707 send_response($reply_arr, 200, $SENDAS);
708 #========== END of PROCESSING ============ 714 apache_note(
'user_name',
"{$CU}:{$login}"); # user_name / member number problem Which one does Odyssey
get?
715 }
catch (Exception $ex) {
716 $httpcode = $ex->getCode();
718 $reply_arr = array(
'error' =>
719 array(
'code' => $ex->getCode(),
'message' => $ex->getMessage() .
" (" . $ex->getLine() .
")"));
720 send_response($reply_arr, $httpcode, $SENDAS);
723 function send_response($reply_arr, $httpcode, $sendas =
'XML') {
725 $httpcode = 200; #
if code not
set,
default to 200 OK
727 http_response_code($httpcode);
731 $xmlResp = HCU_JsonEncode($reply_arr);
732 header(
"Content-Type: application/json");
737 $attrs = array(
'version' =>
'5.0');
738 $xmlResp = Format_AppFeed(assocArrayToXML($reply_arr,
'mdx', $attrs));
742 header(
"Content-Type: application/vnd.moneydesktop.mdx.v5+xml");
743 header(
"Content-length: " . strlen($xmlResp));
780 function sqlmdy($date) {
782 if (strtolower($date) ==
"now" || strtolower($date) ==
"today") {
783 $date = date(
"Y-m-d");
785 # only allow 0-9 and dash(-) or slash (/) 786 # also allow dot (.) for milliseconds 787 if (preg_match(
"/[^0-9\-\/\.]/", $date)) {
790 if (preg_match(
"/[-\/]/", $date)) {
791 list ($yy, $mm, $dd) = preg_split(
"/[-\/\.]/", $date);
793 $yy = substr($date, 0, 4);
794 $mm = substr($date, 4, 2);
795 $dd = substr($date, 6, 2);
797 $mm = sprintf(
"%02d", intval($mm));
798 $dd = sprintf(
"%02d", intval($dd));
799 if (strlen($yy) > 0 && strlen($yy) < 4) {
800 $yy = ($yy < 70 ? 2000 + $yy : 1900 + $yy);
802 $yy = sprintf(
"%04d", intval($yy));
803 if (checkdate($mm, $dd, $yy)) {
804 return "${yy}${mm}${dd}";
816 function whitelist($IPCaller, $ipWhiteList) {
819 foreach ($ipWhiteList as $value) {
820 if (strpos($value,
'/')) {
821 if (cidr_match($IPCaller, $value)) {
825 } elseif (substr($IPCaller, 0, strlen(trim($value))) == trim($value)) {
839 function cidr_match($ip, $range) {
840 list ($subnet, $bits) = explode(
'/', $range);
842 $subnet = ip2long($subnet);
843 $mask = -1 << (32 - $bits);
844 $subnet &= $mask; # nb: in
case the supplied subnet wasn
't correctly aligned 845 $lownetwork = $subnet & $mask; 846 $highbroadcast = $subnet | (~$mask & 0xFFFFFFFF); 848 $pass = ($ip > $lownetwork && $ip < $highbroadcast ? 1 : 0); 849 #return ($ip & $mask) == $subnet; 859 function Format_AppFeed($data) {
861 $dom = new DOMDocument();
863 $dom->preserveWhiteSpace = false;
864 $dom->formatOutput = true;
866 $dom->loadXML($data);
867 $out = $dom->saveXML();
869 $out = str_replace('<?xml version=
"1.0"?>
', '', $out); 884 function Mx5_accts($dbh, $HB_ENV) {
887 $balances = Get_Balances($dbh, $HB_ENV);
889 $reply_arr = array();
892 if (HCU_array_key_exists('dp
', $balances) && count($balances['dp
'])) { 894 # for each $balances['dp
'], print list 895 foreach ($balances['dp
'] as $balkey => $balinfo) { 896 if (HCU_array_key_value('trust
', $balinfo) == 'transfer
') { 897 # cross-account transfer only, don't send to Mx
900 $desc = htmlspecialchars($balinfo[
'description'], ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE);
901 $displaydesc = htmlspecialchars($balinfo[
'displaydesc'], ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE);
902 $atype = ($balinfo[
'certnumber'] ==
"0" || $HB_ENV[
'HideCert'] ? $balinfo[
'accounttype'] :
"{$balinfo['accounttype']}_{$balinfo['certnumber']}");
903 $atype = ($balinfo[
'accountnumber'] .
'-' . $atype);
904 $atype = htmlspecialchars(trim($atype), ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE);
906 switch ($balinfo[
'deposittype']) {
908 $acttype =
"CHECKING";
913 $acttype =
"SAVINGS";
916 $hash = sha1(
"{$HB_ENV['Cu']}{$balkey}");
922 'name' => $balinfo[
'displaydesc'],
923 'balance' => $balinfo[
'currentbal'],
924 'account_number' => $atype,
925 'currency_code' =>
'USD');
926 if (HCU_array_key_value(
'Fset', $HB_ENV) & GetFlagsetValue(
'CU_SHOWAVAILABLE')) {
927 $itm_arr[
'available_balance'] = $balinfo[
'availablebal'];
929 # available_balance is required @ Mx. Populate w/currentbal if we aren't showing it 930 $itm_arr[
'available_balance'] = $balinfo[
'currentbal'];
947 $reply_arr[
'accounts'][][
'account'] = $itm_arr;
948 $reply_arr[
'hashlist'][$hash] = array(
'acct' => $balkey,
'pos' => $reply_idx);
955 # If CU2_SPEC18, try to get credit card loans 956 if (HCU_array_key_value(
'Fset2', $HB_ENV) & GetFlagsetValue(
'CU2_SPEC18')) {
958 if (HCU_array_key_exists(
'cc', $balances) && count($balances[
'cc'])) {
960 foreach ($balances[
'cc'] as $balkey => $balinfo) {
961 if (HCU_array_key_value(
'trust', $balinfo) ==
'transfer') {
962 # cross-account transfer only, don't send to Mx 966 $nextduedate = $balinfo[
'nextduedate'];
967 $creditlimit = $balinfo[
'creditlimit'];
969 $desc = $balinfo[
'description'];
970 $desc = htmlspecialchars(
"$desc", ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE);
971 $displaydesc = $balinfo[
'displaydesc'];
972 $displaydesc = htmlspecialchars(
"$displaydesc", ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE);
973 $loan = $balinfo[
'loan'];
974 $atype = ($balinfo[
'accountnumber'] .
'-' . $loan);
975 $atype = htmlspecialchars(trim($atype), ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE);
976 $balance = $balinfo[
'currentbal'];
977 $creditlimit = $balinfo[
'creditlimit'];
978 $available = $creditlimit - $balance;
979 $balance = sprintf(
"%.2f", $balance * -1);
980 $available = ($available < 0 ?
"" : $available);
981 $cur_avail = (HCU_array_key_value(
'Fset2', $HB_ENV) & GetFlagsetValue(
'CU2_CALL_CCAVAIL') ?
"Call" : $available);
983 $hash = sha1(
"{$HB_ENV['Cu']}{$balkey}");
988 'type' =>
'CREDIT_CARD',
989 'name' => $balinfo[
'displaydesc'],
990 'balance' => $balinfo[
'currentbal'],
991 'account_number' => $atype,
992 'currency_code' =>
'USD');
994 if ($cur_avail > 0) {
995 $itm_arr[
'available_balance'] = $cur_avail;
997 $reply_arr[
'accounts'][][
'account'] = $itm_arr;
998 $reply_arr[
'hashlist'][$hash] = array(
'acct' => $balkey,
'pos' => $reply_idx);
1005 if (count($balances[
'ln'])) {
1007 foreach ($balances[
'ln'] as $balkey => $balinfo) {
1008 if (HCU_array_key_value(
'trust', $balinfo) ==
'transfer') {
1009 # cross-account transfer only, don't send to Mx 1014 $balance = $balinfo[
'currentbal'];
1015 $loan = $balinfo[
'loan'];
1016 $atype = ($balinfo[
'accountnumber'] .
'-' . $loan);
1017 $atype = htmlspecialchars(trim($atype), ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE);
1018 $desc = htmlspecialchars($balinfo[
'description'], ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE);
1019 $displaydesc = htmlspecialchars($balinfo[
'displaydesc'], ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE);
1020 $payoff = HCU_array_key_value(
'payoff', $balinfo);
1021 $nextduedate = $balinfo[
'nextduedate'];
1022 $creditlimit = $balinfo[
'creditlimit'];
1024 $hash = sha1(
"{$HB_ENV['Cu']}{$balkey}");
1030 'name' => $balinfo[
'displaydesc'],
1031 'balance' => $balinfo[
'currentbal'],
1032 'account_number' => $atype,
1033 'currency_code' =>
'USD');
1035 $reply_arr[
'accounts'][][
'account'] = $itm_arr;
1036 $reply_arr[
'hashlist'][$hash] = array(
'acct' => $balkey,
'pos' => $reply_idx);
1040 # returning hashlist under data returned it to caller script as data - ick! 1041 # also caused the Format_AppFeed function to throw an error due to invalid XML content 1042 # so bump it up as a peer to data - can still use to validate w/o exposing list 1043 $result = array(
'Status' =>
'Success',
'data' => $reply_arr[
'accounts'],
'hashlist' => $reply_arr[
'hashlist']);
1044 }
catch (Exception $e) {
1045 $result = array(
'Status' =>
'Failed ' . htmlspecialchars($e->getMessage(), ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE),
'data' => $reply_arr);
1052 function Mx5_dpTrans($dbh, $HB_ENV, $balkey, $balinfo, $sqlstart, $sqlend) {
1055 $reply_arr = array(
'account' => array(
1056 'id' => $balinfo[
'id'],
1057 'transactions' => array(
'@attributes' => array(
'start_date' => $sqlstart,
'3' =>
'three',
'pages' => 1,
'page' => 1))));
1059 $history = Get_History($dbh, $HB_ENV, $balkey, $sqlstart, $sqlend);
1061 if (HCU_array_key_exists($balkey, $history) && is_array($history[$balkey])) {
1063 $atype = $balinfo[
'account_number'];
1065 foreach ($history[$balkey] as $tnum => $detl) {
1069 $tranamount = $detl[
'amount'];
1070 $tranamount = str_replace(
",",
"", str_replace(
"$",
"", $tranamount));
1071 $tranamount = sprintf(
"%.2f", $tranamount);
1072 $check = (HCU_array_key_exists(
'checkno', $detl) ? $detl[
'checkno'] : 0);
1073 $trandesc = $detl[
'description'];
1074 if ($trandesc <
" " && $check != 0) {
1077 $trandesc = (preg_replace(
"/<BR>/",
" ", $trandesc));
1078 $trandesc = (preg_replace(
"/ /",
" ", $trandesc));
1080 $trandesc = (trim($trandesc) ==
'' ?
'.' : $trandesc);
1082 if ($tranamount < 0) {
1085 $trntype =
'CREDIT';
1087 $itm_arr[
'id'] =
"{$atype}-{$detl['traceno']}";
1088 $itm_arr[
'type'] = $trntype;
1089 $itm_arr[
'amount'] = $tranamount;
1090 $itm_arr[
'description'] = array(
"@cdata" => $trandesc);
1091 $itm_arr[
'status'] =
'POSTED';
1092 $itm_arr[
'transacted_on'] = date(
'Y-m-d', strtotime($detl[
'date']));
1093 $itm_arr[
'posted_on'] = date(
'Y-m-d', strtotime($detl[
'date']));
1094 if ($balinfo[
'type'] ==
'CHECKING' and $check != 0) {
1095 $itm_arr[
'check_number'] = $check;
1097 $reply_arr[
'account'][
'transactions'][][
'transaction'] = $itm_arr;
1101 $result = array(
'Status' =>
'Success',
'data' => $reply_arr);
1102 }
catch (Exception $e) {
1103 $result = array(
'Status' =>
'Failed ' . htmlspecialchars($e->getMessage(), ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE),
'data' => $reply_arr);
1108 function Mx5_ccTrans($dbh, $HB_ENV, $balkey, $balinfo, $sqlstart, $sqlend) {
1111 $hisinfo = HCU_array_key_value(
'hisinfo', $balinfo);
1112 $incchist = (empty($hisinfo) || trim(strtoupper($hisinfo)) ==
'HOMECU' ? 1 : 0);
1114 $reply_arr = array(
'account' => array(
1115 'id' => $balinfo[
'id'],
1116 #<transactions start_date=
"2013-06-07" pages=
"5" page=
"1">
1117 #
'transactions' =>
'start_date="' . $sqlstart .
' pages="1" page="1"'));
1118 'transactions' => array(
'@attributes' => array(
'start_date' => $sqlstart,
'pages' => 1,
'page' => 1))));
1121 $history = Get_History($dbh, $HB_ENV, $balkey, $sqlstart, $sqlend);
1122 if (HCU_array_key_exists($balkey, $history) && is_array($history[$balkey])) {
1124 $atype = $balinfo[
'account_number'];
1126 foreach ($history[$balkey] as $tnum => $detl) {
1130 $principle = (HCU_array_key_exists(
'principal', $detl) ? $detl[
'principal'] : 0);
1148 $principle = $principle * -1;
1149 if ($principle < 0) {
1152 $trntype =
"CREDIT";
1155 $totalpay = $detl[
'totalpay'];
1156 $trdesc = $detl[
'description'];
1158 $trdesc = (trim($trdesc) ==
'' ?
'.' : $trdesc);
1160 $itm_arr[
'id'] =
"{$atype}-{$detl['traceno']}";
1161 $itm_arr[
'type'] = $trntype;
1162 $itm_arr[
'amount'] = $totalpay;
1163 $itm_arr[
'description'] = array(
"@cdata" => $trdesc);
1164 $itm_arr[
'status'] =
'POSTED';
1165 $itm_arr[
'transacted_on'] = date(
'Y-m-d', strtotime($detl[
'date']));
1166 $itm_arr[
'posted_on'] = date(
'Y-m-d', strtotime($detl[
'date']));
1168 $reply_arr[
'account'][
'transactions'][][
'transaction'] = $itm_arr;
1172 $result = array(
'Status' =>
'Success',
'data' => $reply_arr);
1173 }
catch (Exception $e) {
1174 $result = array(
'Status' =>
'Failed ' . htmlspecialchars($e->getMessage(), ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE),
'data' => $reply_arr);
1179 function Mx5_lnTrans($dbh, $HB_ENV, $balkey, $balinfo, $sqlstart, $sqlend) {
1181 $reply_arr = array(
'account' => array(
1182 'id' => $balinfo[
'id'],
1183 #<transactions start_date=
"2013-06-07" pages=
"5" page=
"1">
1184 #
'transactions' =>
'start_date="' . $sqlstart .
' pages="1" page="1"'));
1185 'transactions' => array(
'@attributes' => array(
'start_date' => $sqlstart,
'pages' => 1,
'page' => 1))));
1187 $hisinfo = HCU_array_key_value(
'hisinfo', $balinfo);
1188 $incchist = (empty($hisinfo) || trim(strtoupper($hisinfo)) ==
'HOMECU' ? 1 : 0);
1191 $history = Get_History($dbh, $HB_ENV, $balkey, $sqlstart, $sqlend);
1192 if (HCU_array_key_exists($balkey, $history) && is_array($history[$balkey])) {
1194 $atype = $balinfo[
'account_number'];
1196 foreach ($history[$balkey] as $tnum => $detl) {
1199 $principle = (HCU_array_key_exists(
'principal', $detl) ? $detl[
'principal'] : 0);
1200 $interest = (HCU_array_key_exists(
'interest', $detl) ? $detl[
'interest'] : 0);
1201 $totalpay = (HCU_array_key_exists(
'totalpay', $detl) ? $detl[
'totalpay'] : 0);
1202 $traceno = (HCU_array_key_exists(
'traceno', $detl) ? $detl[
'traceno'] : 0);
1203 $trdesc = (HCU_array_key_exists(
'description', $detl) ? $detl[
'description'] :
'');
1204 $trdesc = (trim($trdesc) ==
'' ?
'.' : $trdesc);
1206 # OFX set them this way - 1207 if ($principle < 0) {
1208 $trntype =
"PAYMENT";
1210 $trntype =
"ADVANCE";
1212 # for MDX - CREDIT increases member net worth 1213 if ($principle < 0) {
1214 $trntype =
"CREDIT";
1220 # transaction data row 1228 $itm_arr[
'id'] =
"{$atype}-{$detl['traceno']}";
1229 $itm_arr[
'type'] = $trntype;
1230 $itm_arr[
'amount'] = $totalpay;
1231 $itm_arr[
'description'] = array(
"@cdata" => $trdesc);
1232 $itm_arr[
'status'] =
'POSTED';
1233 $itm_arr[
'transacted_on'] = date(
'Y-m-d', strtotime($detl[
'date']));
1234 $itm_arr[
'posted_on'] = date(
'Y-m-d', strtotime($detl[
'date']));
1236 $reply_arr[
'account'][
'transactions'][][
'transaction'] = $itm_arr;
1240 $result = array(
'Status' =>
'Success',
'data' => $reply_arr);
1241 }
catch (Exception $e) {
1242 $result = array(
'Status' =>
'Failed ' . htmlspecialchars($e->getMessage(), ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE),
'data' => $reply_arr);
1247 function Mx5_challenge($dbh, $HB_ENV, $MC) {
1268 # for backward compatibility at Mx 1269 # this MFA_send_chall sends MFA_E and all MFA_Q settings 1270 # regardless of '1 random' settings 1275 $HB_ENV[
'flagset2'] = $HB_ENV[
'flagset2'] & (~ GetFlagsetValue(
'CU2_RANDOM_CHAL')); # turn off
'1 random' 1277 $MemberChallengeQuestions_ary = GetChallengeQuestions(
"CHALLENGE", $dbh, $HB_ENV, $MC, $HB_ENV[
'Cn']);
1279 $reply_arr = array(
'session' => array(
'challenges' => array()));
1281 # force 'What email' as first challenge question 1282 $itm_arr = array(
'challenge' => array(
1284 'question' => array(
"@cdata" =>
'What email address is saved with this account?')));
1286 $reply_arr[
'session'][
'challenges'][] = $itm_arr;
1288 # and now add mfa questions, if any were found 1289 if (count($MemberChallengeQuestions_ary)) {
1290 foreach ((array) $MemberChallengeQuestions_ary as $chakey => $mfaitem) {
1291 $itm_arr = array(
'challenge' => array(
1292 'id' =>
"MFA_{$mfaitem['cqid']}",
1293 'question' => array(
"@cdata" =>
"{$mfaitem['display']}")));
1295 $reply_arr[
'session'][
'challenges'][] = $itm_arr;
1299 $result = array(
'Status' =>
'Success',
'data' => $reply_arr);
1300 }
catch (Exception $e) {
1301 $result = array(
'Status' =>
'Failed ' . htmlspecialchars($e->getMessage(), ENT_NOQUOTES | ENT_XML1,
'UTF-8', FALSE),
'data' => $reply_arr);