Commit 3a2f32965d8ceb92b654eabb1e16adc4c60bbea0

Authored by Mark Holtzhausen
1 parent 11bf483c

PT: 1638188 - Implement HMAC(custom) security in comms

webservice/clienttools/ajaxhandler.php
1 1 <?php
2 2 class ajaxHandler{
  3 + protected $rawRequestObject=NULL;
  4 + protected $digestToken=NULL;
  5 + protected $remoteIp=NULL;
  6 +
3 7 public $ret=NULL;
4 8 public $req=NULL;
5 9 public $version=NULL;
... ... @@ -9,60 +13,310 @@ class ajaxHandler{
9 13 public $authenticator=NULL;
10 14 public $noAuthRequireList=array();
11 15 public $standardServices=array('system');
  16 +
  17 + protected $errors=array();
  18 +
  19 + /**
  20 + * 1.Parse JSON
  21 + * 2.Check Request Validity (hash/ip/expiration token)
  22 + * 3.Preliminary Session Check
  23 + * if no session or invalid session
  24 + * 3.1 Use credentials to create a new session.
  25 + * 3.3 Update Authentication segment with new sessionid
  26 + * 4.Authentication Check
  27 + * 5.Service Dispatch
  28 + */
  29 +
  30 + public function __construct(&$response=NULL,&$kt,$noAuthRequests=''){
  31 +
  32 + //========================= Preparations
  33 + // set the response object
  34 + if(get_class($response)=='jsonResponseObject'){
  35 + $this->ret=&$response;
  36 + }else{
  37 + $this->ret=new jsonResponseObject();
  38 + }
  39 + $this->log("[__construct]ENTERING PREPARATIONS");
  40 +
  41 + $this->remoteIp = (getenv(HTTP_X_FORWARDED_FOR)) ? getenv(HTTP_X_FORWARDED_FOR) : getenv(REMOTE_ADDR);
  42 + $this->log("[__construct]Remote IP determined as: {$this->remoteIp}");
12 43  
13   - public function __construct(&$ret=NULL,&$kt,$noAuthRequests=''){
14   - // set a local copy of the json request wrapper
15 44 $noAuthRequests=is_array($noAuthRequests)?$noAuthRequests:split(',',(string)$noAuthRequests);
16 45 $this->registerNoAuthRequest($noAuthRequests);
17   - $this->req=new jsonWrapper(isset($_GET['request'])?$_GET['request']:(isset($_POST['request'])?$_POST['request']:''));
  46 +
  47 + $this->rawRequestObject=isset($_GET['request'])?$_GET['request']:(isset($_POST['request'])?$_POST['request']:'');
  48 + $this->digestToken=isset($_GET['msgAuth'])?$_GET['msgAuth']:(isset($_POST['msgAuth'])?$_POST['msgAuth']:'');
  49 + $this->log("[__construct]DigestToken Found: {$this->digestToken}");
  50 +
  51 + $this->ret->addDebug('Raw Request',$this->rawRequestObject);
  52 + $this->ret->addDebug('DigestToken Received',$this->digestToken);
  53 + $this->ret->addDebug('Remote IP',$this->remoteIp);
  54 +
  55 +
  56 + if($this->auth['session'])session_id($this->auth['session']);
  57 + $this->session=session_id();
  58 + $this->log("[__construct]Session Restarted as: {$this->session}");
  59 + // session_id('BLANK_SESSION');
  60 +
  61 +
  62 +
  63 + //========================= 1. Parse Json
  64 + $this->log("[__construct]ENTERING Parse Json");
  65 + $this->req=new jsonWrapper($this->rawRequestObject);
18 66 $this->auth=$this->structArray('user,pass,passhash,appType,session,token,version',$this->req->jsonArray['auth']);
19 67 $this->request=$this->structArray('service,function,parameters',$this->req->jsonArray['request']);
20 68  
  69 + //Add additional parameters
21 70 $add_params=array_merge($_GET,$_POST);
22 71 unset($add_params['request'],$add_params['datasource']);
23 72 $this->request['parameters']=array_merge($this->request['parameters'],$add_params);
24   -
25   -
26   - // set the response object
27   - if(get_class($ret)=='jsonResponseObject'){
28   - $this->ret=&$ret;
29   - }else{
30   - $this->ret=new jsonResponseObject();
31   - }
  73 +
  74 + if(!$this->auth['debug'])$this->ret->includeDebug=false;
  75 +
32 76 $this->ret->setRequest($this->req->jsonArray);
33 77 $this->ret->setTitle($this->request['service'].'::'.$this->request['function']);
34 78 $this->ret->setDebug('Server Versions',$this->getServerVersions());
35   -
  79 +
  80 +
  81 +
  82 +
  83 +
  84 + //========================= 2. Test System Requirements
  85 + $this->log("[__construct]ENTERING Test System Requirements");
36 86 if(get_class($kt)=='KTAPI'){
37 87 $this->kt=&$kt;
38 88 }else{
39 89 $this->ret->addError('KnowledgeTree Object not Received in '.__CLASS__.' constructor. Quitting.');
40 90 return $this->render();
41 91 }
  92 +
  93 +
  94 + //TODO: Get rid of this service
  95 + $this->loadService('auth');
  96 + $this->authenticator=new auth($this,$this->ret,$this->kt,$this->request,$this->auth);
  97 +
  98 +
  99 +
  100 + //========================= 3. Check Request Validity
  101 + $this->log("[__construct]ENTERING Check Request Validity");
  102 + if(!$this->checkRequestValidity())return $this->render();
  103 + if(!$this->checkTokenValidity())return $this->render();
  104 +
  105 +
  106 +
  107 +
  108 + //========================= 4. Preliminary Session Check
  109 + $this->log("[__construct]ENTERING Preliminary Session Check");
  110 + if(!$this->checkSessionValidity()){
  111 + $this->creatNewSession(); //(login) This may fail, be the user is still allowed to dispatch to the
  112 + }
  113 +
  114 +
  115 +
  116 +
  117 +
  118 + //========================= 5. Authentication Check
  119 + $this->log("[__construct]ENTERING Authentication Check");
  120 + if(!$this->isStandardService() && !$this->isNoAuthRequiredRequest()){
  121 + //Authentication is Required
  122 + $this->log("[__construct]Determined Authentication is required");
  123 + if(!$this->checkCredentials()){
  124 + throw new Exception('User Credentials Necessary for Requested Service');
  125 + return $this->render();
  126 + }
  127 + }
  128 +
  129 +
  130 +
  131 +
  132 +
  133 +
  134 + //========================= 6. Service Dispatch
  135 + $this->log("[__construct]ENTERING Service Dispatch");
  136 + $this->dispatch();
  137 + return $this->render();
  138 + }
  139 +
  140 +
  141 +
  142 +
  143 +
  144 +
  145 +
  146 +
  147 +
  148 +
  149 +
  150 +
  151 + protected function checkRequestValidity(){
  152 + $this->log("[checkRequestvalidity]Entering...");
  153 + $securityHash=md5(md5($this->rawRequestObject).'_'.$this->auth['token'].'_'.$this->getUserPass());
  154 + $digestToken=$this->digestToken;
  155 + $this->log("[checkRequestvalidity]comparing {$securityHash} with {$digestToken} as received");
42 156  
43   - // Prepare
44   - if(!$this->isStandardService()){
45   - $this->loadService('auth');
46   - $this->authenticator=new auth($this,$this->ret,$this->kt,$this->request,$this->auth);
47   -
  157 + $passed=$securityHash==$digestToken;
  158 +
  159 + $data=array(
  160 + 'Received Token' =>$digestToken,
  161 + 'Expected Token' =>$securityHash,
  162 + 'Passed' =>$passed,
  163 + ''
  164 + );
  165 + $this->ret->addDebug('Message Digest Security',$data);
  166 +
  167 + if(!$passed){
  168 + $this->log("[checkRequestvalidity]Failed Validity Test");
  169 + throw new Exception('Message Integrity Was Compromised.');
  170 + }
  171 + return $passed;
  172 + }
48 173  
49   - //Make sure a token exists before continuing
50   - if(!$this->verifyToken())return $this->render();
51 174  
  175 + protected function checkSessionValidity(){
  176 + $valid=$this->start_session();
  177 + $this->auth['session']=session_id();
  178 + $this->ret->setStatus('session_id',session_id());
  179 + $this->ret->addDebug('Auth',array('Session Check'=>$valid));
  180 +// echo $valid?'true':'false'.'<br /><br /><br /><br /><br /><br /><br />';
  181 + return $valid;
  182 + }
52 183  
53   - if(!$this->verifySession()){
54   - $this->doLogin();
55   - $isAuthRequired=$this->isNoAuthRequiredRequest();
56   - $isAuthenticated=$this->isAuthenticated();
57   - if(!$isAuthRequired && !$isAuthenticated)return $this->render();
58   - }
  184 + //TODO: Alter this to verify whether token was used before or whether it is new
  185 + protected function checkTokenValidity(){
  186 + $token=$this->auth['token'];
  187 + $tokenList=$_SESSION['JAPI_TOKEN_STORE']?$_SESSION['JAPI_TOKEN_STORE']:array();
  188 + $valid=!in_array($token,$tokenList);
  189 + if($valid){
  190 + $tokenList[$token]=$token;
  191 + $_SESSION['JAPI_TOKEN_STORE']=$tokenList;
  192 + }else{
  193 + $this->error('Invalid Token - Already Used');
  194 + $this->log('Invalid Token - Already Used');
59 195 }
60 196  
61   - $this->dispatch();
62   -
63   - return $this->render();
  197 + return $valid;
  198 + }
  199 +
  200 +
  201 + protected function creatNewSession(){
  202 + $this->ret->addDebug('Auth',array('Attempting to Create a New Session'));
  203 + if($this->checkCredentials()){
  204 + $ssession=KTAPI_UserSession::_check_session($this->getUserObject(),$this->remoteIp,$this->auth['appType']);
  205 + $session=$ssession[0];
  206 + $this->ret->addDebug('####################################Session Created : '.$session);
  207 + $this->auth['session']=session_id();
  208 + $this->ret->setStatus('session_id',session_id());
  209 + return true;
  210 + }else{
  211 + return false;
  212 + }
  213 + }
  214 +
  215 + protected function start_session(){
  216 + $app_type=$this->auth['appType'];
  217 + $session_id=$this->auth['session'];
  218 + $ip=$this->remoteIp;
  219 +
  220 + $session=$this->kt->get_session();
  221 +
  222 + if(get_class($session)=='KTAPI_UserSession'){
  223 + return true;
  224 + }else{
  225 + $session = $this->kt->get_active_session($session_id, $ip, $app_type);
  226 +
  227 + if (PEAR::isError($session)){
  228 + return false;
  229 + }
  230 + $this->auth['session']=session_id();
  231 + $this->ret->setStatus('session_id',session_id());
  232 + return true;
  233 + }
  234 +
  235 +
  236 + }
  237 +
  238 +
  239 + protected function getUserPass(){
  240 + $l_pass=md5('@NO_AUTH_NEEDED@');
  241 + $u=$this->getUserObject();
  242 + if($u){
  243 + $l_pass=$this->getUserObject()->getPassword();
  244 + }
  245 + return $l_pass;
  246 + }
  247 +
  248 + protected function getUserObject(){
  249 + $kt=$this->kt;
  250 + $user=$this->auth['user'];
  251 + $o_user=$kt->get_user_object_by_username($user);
  252 +
  253 + if(PEAR::isError($o_user)){
  254 + if(!isset($this->errors['usernotfound']))$this->ret->addError('User '.$user.' not found');
  255 + $this->errors['usernotfound']=true;
  256 + return false;
  257 + }else{
  258 + $this->log("[getUserObject] Found User: ".$o_user->getName());
  259 + }
  260 + return $o_user;
  261 + }
  262 +
  263 + protected function checkCredentials(){
  264 + $user=$this->auth['user'];
  265 + $passHash=$this->auth['passhash'];
  266 +
  267 + $kt=$this->kt;
  268 +
  269 + $o_user=$kt->get_user_object_by_username($user);
  270 +
  271 + if(PEAR::isError($o_user)){
  272 + if(!isset($this->errors['usernotfound']))$this->ret->addError('User '.$user.' not found');
  273 + $this->errors['usernotfound']=true;
  274 + return false;
  275 + }
  276 +
  277 + try{
  278 + $l_pass=$o_user->getPassword();
  279 + $l_passHash=md5($l_pass.$this->auth['token']);
  280 +
  281 + $passed=$passHash==$l_passHash;
  282 +
  283 + $this->ret->setDebug('Auth',array(
  284 + 'User Real Password'=>$l_pass,
  285 + 'User Real Password Hash'=>$l_passHash,
  286 + 'Received Password Hash'=>$passHash,
  287 + 'passed'=>$passed
  288 + ));
  289 +
  290 + return $passed;
  291 +
  292 + }catch(Exception $e){
  293 + throw new Exception('Unknown credentialCheck error encountered');
  294 + return false;
  295 + }
  296 +
  297 + return ture;
  298 + }
  299 +
  300 +
  301 +
  302 + protected function log($str=''){
  303 + $this->ret->log($str);
  304 + }
  305 +
  306 +
  307 + protected function error($errMsg=NULL){
  308 + $this->ret->addError($errMsg);
64 309 }
65 310  
  311 +
  312 +
  313 +
  314 +
  315 +
  316 +
  317 +
  318 +
  319 +
66 320 private function structArray($structString=NULL,$arr=NULL){
67 321 $struct=array_flip(split(',',(string)$structString));
68 322 return array_merge($struct,is_array($arr)?$arr:array());
... ... @@ -91,7 +345,17 @@ class ajaxHandler{
91 345 }
92 346  
93 347 public function isStandardService(){
94   - return in_array($this->request['service'],$this->standardServices);
  348 + $isStandardService=in_array($this->request['service'],$this->standardServices);
  349 +
  350 + $debug=array(
  351 + 'requested service' =>$this->request['service'],
  352 + 'standard services' =>$this->standardServices,
  353 + 'isStandardService' =>$isStandardService
  354 + );
  355 +
  356 + $this->ret->addDebug('ajaxhandler::isStandardService',$debug);
  357 +
  358 + return $isStandardService;
95 359 }
96 360  
97 361  
... ... @@ -188,7 +452,17 @@ class ajaxHandler{
188 452 public function isNoAuthRequiredRequest(){
189 453 $req=$this->request;
190 454 $reqString=strtolower("{$req['service']}.{$req['function']}");
191   - return in_array($reqString,$this->noAuthRequireList);
  455 + $requiresAuth=in_array($reqString,$this->noAuthRequireList);
  456 +
  457 + $debug=array(
  458 + 'requested service method' =>$reqString,
  459 + 'no auth required list' =>$this->noAuthRequireList,
  460 + 'requires auth' =>$requiresAuth
  461 + );
  462 +
  463 + $this->ret->addDebug('ajaxhandler::isNoAuthRequiredRequest',$debug);
  464 +
  465 + return $requiresAuth;
192 466 }
193 467  
194 468 }
... ...
webservice/clienttools/comms.php
... ... @@ -50,7 +50,7 @@ if($_GET[&#39;datasource&#39;])$RET-&gt;isDataSource=true;
50 50  
51 51 $noAuthRequests=array(
52 52 'auth.ping',
53   - 'auth.japiLogin',
  53 + //'auth.japiLogin',
54 54 'kt.get_all_client_policies',
55 55 'kt.get_languages',
56 56 'kt.switchlang'
... ...
webservice/clienttools/jsonWrapper.php
... ... @@ -9,6 +9,7 @@ class jsonResponseObject{
9 9 protected $errors=array();
10 10 protected $status=array('session_id'=>'','random_token'=>'');
11 11 protected $data=array();
  12 + protected $log=array();
12 13 protected $request=array();
13 14 protected $debug=array();
14 15 public $additional=array();
... ... @@ -28,7 +29,8 @@ class jsonResponseObject{
28 29 ),
29 30 'data' =>array(),
30 31 'request' =>array(),
31   - 'debug' =>array()
  32 + 'debug' =>array(),
  33 + 'log' =>array()
32 34 );
33 35  
34 36  
... ... @@ -49,9 +51,12 @@ class jsonResponseObject{
49 51 }
50 52  
51 53 public function setDebug($varName=NULL,$value=NULL){
  54 + if(is_array($this->debug[$varName]) && is_array($value))$value=array_merge($this->debug[$varName],$value);
52 55 $this->debug[$varName]=$value;
53 56 }
54 57  
  58 + public function addDebug($varName=NULL,$value=NULL){$this->setDebug($varName,$value);}
  59 +
55 60 public function setRequest($request=NULL){
56 61 $this->request=$request;
57 62 }
... ... @@ -61,6 +66,10 @@ class jsonResponseObject{
61 66 $this->title=$title;
62 67 }
63 68  
  69 + public function log($str){
  70 + $this->log[]='['.date('h:i:s').'] '.$str;
  71 + }
  72 +
64 73 public function getJson(){
65 74 $response=array_merge(array(
66 75 'requestName' =>$this->title,
... ... @@ -72,6 +81,7 @@ class jsonResponseObject{
72 81 'data' =>$this->data,
73 82 'request' =>$this->request,
74 83 'debug' =>$this->debug,
  84 + 'log' =>$this->log
75 85 ),$this->additional);
76 86 if(!$this->includeDebug) unset($response['debug']);
77 87  
... ... @@ -91,7 +101,6 @@ class jsonWrapper{
91 101 public $jsonArray=array();
92 102  
93 103 public function __construct($content=NULL){
94   -// $content=stripslashes($content);
95 104 $this->raw=$content;
96 105 $content=@json_decode($content,true);
97 106 if(!is_array($content))throw new jsonContentException('Invalid JSON input',jsonContentException::INPUT_ERROR);
... ...