fixed locked
[Pman.Base] / Pman / Login.php
1 <?php
2
3 require_once 'Pman.php';
4
5 /***
6
7 * Auth wrapper..
8
9 * User class must provide the following features.
10
11 * logout()
12 * isAuth() 
13 * getAuthUser();
14 * authUserArray() 
15 * active()  -- is user active. // or set prior to checking..
16 * authUserName(n) - sets the value prior to a find(true)
17 * checkPassword($_REQUEST['password'])) {
18 * login();
19 * lang(val) - to set the language..
20 */
21
22
23
24 class Pman_Login extends Pman
25
26     var $masterTemplate = 'login.html';
27     
28     var $ip_management = false;
29         
30         var $event_prefix = '';
31         
32     
33     function getAuth() // everyone allowed in here..
34     {
35         parent::getAuth(); // load company..
36         
37         $ff = HTML_FlexyFramework::get();
38         
39         $this->ip_management = (empty($ff->Pman['ip_management'])) ? false : true;
40         
41         return true;
42     }
43     /**
44      * Accepts:
45      * logout =
46      * 
47      * 
48      */
49     function get($v, $opts=array()) 
50     {
51         $this->initErrorHandling();
52         
53          // DB_DataObject::DebugLevel(5);
54         if (!empty($_REQUEST['logout'])) {
55            return $this->logout();
56         }
57         
58         // general query...
59         if (!empty($_REQUEST['getAuthUser'])) {
60             //DB_Dataobject::debugLevel(5);
61             $this->sendAuthUserDetails();
62             exit;
63         }
64         
65         if(!empty($_REQUEST['check_owner_company'])) {
66             $core_company = DB_DataObject::factory('core_company');
67             $core_company->comptype = 'OWNER';
68             $this->jok($core_company->count());
69         }
70         
71         // might be an idea to disable this?!?
72         if (!empty($_REQUEST['username'])) {
73             $this->post();
74         }
75         
76         
77         if (!empty($_REQUEST['switch'])) {
78             $this->switchUser($_REQUEST['switch']);
79         }
80         
81         if (!empty($_REQUEST['loginPublic'])) {
82             $this->switchPublicUser($_REQUEST['loginPublic']);
83         }
84         if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('/^check_http/', $_SERVER['HTTP_USER_AGENT'])) {
85                         die("server is alive = authFailure");
86                 }
87         $this->jerr("INVALID REQUEST");
88         exit;
89     }
90     
91     
92     function logout()
93     {
94         $ff = class_exists('HTML_FlexyFramework2') ?  HTML_FlexyFramework2::get()  :  HTML_FlexyFramework::get();
95         
96                 //DB_DAtaObject::debugLevel(1);
97         $u = $this->getAuthUser();
98         //print_r($u);
99         if ($u) {
100             
101             $this->addEvent($this->event_prefix . 'LOGOUT');
102             $e = DB_DataObject::factory('Events');
103           
104             
105             $u->logout();
106             session_regenerate_id(true);
107             session_commit(); 
108
109             if(!empty($ff->Pman['local_autoauth']) && !empty($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST'] == 'localhost') {
110                 $this->jerr("you are using local autoauth!?");                
111             }
112             //echo '<PRE>';print_R($_SESSION);
113             $this->jok("Logged out - user ");
114         }
115         // log it..
116         
117         //$_SESSION['Pman_I18N'] = array(); << 
118         session_regenerate_id(true);
119         session_commit();
120         
121         $this->jok("Logged out - no user");
122         
123     }
124     
125     function sendAuthUserDetails()
126     {
127         // remove for normal use - it's a secuirty hole!
128         //DB_DataObject::debugLevel(1);
129         if (!empty($_REQUEST['_debug'])) {
130            // DB_DataObject::debugLevel(1);
131         }
132         // 
133         $ff = HTML_FlexyFramework::get();
134         $tbl = empty($ff->Pman['authTable']) ? 'core_person' : $ff->Pman['authTable'];
135         
136         $u = DB_DataObject::factory($tbl);
137         $s = DB_DataObject::factory('core_setting');
138         $require_oath_val = 1;
139         $require_oath = $s->lookup('core', 'two_factor_auth_required');
140         if(!empty($require_oath)) {
141             if($require_oath->val == 0) {
142                 $require_oath_val = 0;
143             }
144         } 
145         
146         if (!$u->isAuth()) {
147             $this->jok(array(
148                 'id' => 0
149             ));
150             exit;
151         }
152         
153         //die("got here?");
154         $au = $u->getAuthUser();
155         
156          // might occur on shared systems.
157         $ff= HTML_FlexyFramework::get();
158         
159         if (!empty($ff->Pman['auth_comptype'])  && $au->id > 0 &&
160                 ($ff->Pman['auth_comptype'] != $au->company()->comptype)) {
161             $au->logout();
162             $this->jerr("Login not permited to outside companies - please reload");
163         }
164         
165         //$au = $u->getAuthUser();
166         
167         $aur = $au ?  $au->authUserArray() : array();
168         
169         /** -- these need modulizing somehow! **/
170         
171         
172         
173         // basically calls Pman_MODULE_Login::sendAuthUserDetails($aur) on all the modules
174         //echo '<PRE>'; print_r($this->modules());
175         // technically each module should only add properties to an array named after that module..
176         
177         foreach($this->modules() as $m) {
178             if (empty($m)) {
179                 continue;
180             }
181             if (!file_exists($this->rootDir.'/Pman/'.$m.'/Login.php')) {
182                 continue;
183             }
184             $cls = 'Pman_'.$m.'_Login';
185             require_once 'Pman/'.$m.'/Login.php';
186             $x = new $cls;
187             $x->authUser = $au;
188             $aur = $x->sendAuthUserDetails($aur);
189         }
190         
191                  
192 //        
193 //        echo '<PRE>';print_r($aur);
194 //        exit;
195         $this->jok($aur);
196         exit;
197         
198             
199     }
200
201     function switchUser($id)
202     {
203         $tbl = empty($ff->Pman['authTable']) ? 'core_person' : $ff->Pman['authTable'];
204         $u = DB_DataObject::factory($tbl);
205         if (!$u->isAuth()) {
206             $this->err("not logged in");
207         }
208         
209         $au = $u->getAuthUser();
210         
211         // first check they have perms to do this..
212         if (!$au|| ($au->company()->comptype != 'OWNER') || !$this->hasPerm('Core.Person', 'E')) {
213             $this->jerr("User switching not permitted");
214         }
215                 
216         $u = DB_DataObject::factory($tbl);
217         $u->get($id);
218         if (!$u->active()) {
219             $this->jerr('Account disabled');
220         }
221         $u->login();
222             // we might need this later..
223         $this->addEvent($this->event_prefix . "SWITCH-USER", false, $au->name . ' TO ' . $u->name);
224         $this->jok("SWITCH");
225         
226     }
227     
228     function switchPublicUser($id)
229     {
230         $tbl = empty($ff->Pman['authTable']) ? 'core_person' : $ff->Pman['authTable'];
231         
232         $u = DB_DataObject::factory($tbl);
233         $u->get($id);
234         
235         if (!$u->active()) {
236             $this->jerr('Account disabled');
237         }
238         
239         if(!$u->loginPublic()){
240             $this->jerr('Switch fail');
241         }
242          
243         $this->jok('OK');
244     }
245     
246     var $domObj = false;
247     
248     function post($v)
249     {
250         //DB_DataObject::debugLevel(1);
251         
252         if (!empty($_REQUEST['getAuthUser'])) {
253             $this->sendAuthUserDetails();
254             exit;
255         }
256         
257         if (!empty($_REQUEST['logout'])) {
258            return $this->logout();
259         }
260          
261         if(!empty($_REQUEST['check_owner_company'])) {
262             $core_company = DB_DataObject::factory('core_company');
263             $core_company->comptype = 'OWNER';
264             $this->jok($core_company->count());
265         }
266         
267         if (!empty($_REQUEST['passwordRequest'])) { //|| (strpos($_REQUEST['username'], '@') < 1)) {
268             return $this->passwordRequest($_REQUEST['passwordRequest']);   
269         }
270                         
271                 if (!empty($_REQUEST['ResetPassword'])) {
272                         if (empty($_REQUEST['id']) || 
273                         empty($_REQUEST['ts']) ||
274                         empty($_REQUEST['key']) ||
275                         empty($_REQUEST['password1']) ||
276                         empty($_REQUEST['password2']) ||
277                         ($_REQUEST['password1'] != $_REQUEST['password2'])
278                         ) {
279                         $this->jerr("Invalid request to reset password");
280                         }
281                         
282                         $this->resetPassword($_REQUEST['id'], $_REQUEST['ts'], $_REQUEST['key'], $_REQUEST['password1'] );
283                 }
284                 
285                 
286                 if (!empty($_REQUEST['_verifyCheckSum'])) {
287                         if (empty($_REQUEST['id']) || 
288                         empty($_REQUEST['ts']) ||
289                         empty($_REQUEST['key'])
290                          
291                         ) {
292                         $this->jerr("Invalid request to reset password");
293                         }
294                         
295                         $this->verifyResetPassword($_REQUEST['id'], $_REQUEST['ts'], $_REQUEST['key']);
296                         $this->jok("Checksum is ok");
297                 }
298         
299         // this is 'classic' change password...
300         if (!empty($_REQUEST['changePassword'])) {
301             return $this->changePassword($_REQUEST);
302         }
303         
304         // login attempt..
305         
306         $ff = HTML_FlexyFramework::get();
307         $tbl = empty($ff->Pman['authTable']) ? 'core_person' : $ff->Pman['authTable'];
308         
309        
310         $u = DB_DataObject::factory($tbl);
311         
312         $ip = $this->ip_lookup();
313         // ratelimit
314         if (!empty($ip)) {
315             //DB_DataObject::DebugLevel(1);
316             $e = DB_DataObject::Factory('Events');
317             $e->action = $this->event_prefix . 'LOGIN-BAD';
318             $e->ipaddr = $ip;
319             $e->whereAdd('event_when > NOW() - INTERVAL 10 MINUTE');
320             if ($e->count() > 5) {
321                 $this->jerror($this->event_prefix . 'LOGIN-RATE', "Login failures are rate limited - please try later");
322             }
323         }
324         
325         // this was removed before - not quite sure why.
326         // when a duplicate login account is created, this stops the old one from interfering..
327         $u->active = 1;
328         
329         // empty username = not really a hacking attempt.
330         
331         if (empty($_REQUEST['username'])) { //|| (strpos($_REQUEST['username'], '@') < 1)) {
332             $this->jerror($this->event_prefix . 'LOGIN-EMPTY', 'You typed the wrong Username or Password (0)');
333             exit;
334         }
335         
336         $u->authUserName($_REQUEST['username']);
337         
338         if ($u->count() > 1 || !$u->find(true)) {
339             $this->jerror($this->event_prefix . 'LOGIN-BAD','You typed the wrong Username or Password  (1)');
340             exit;
341         }
342         
343         if (!$u->active()) { 
344             $this->jerror($this->event_prefix . 'LOGIN-BAD','Account disabled');
345         }
346         
347         if(!empty($u->oath_key) && empty($_REQUEST['oath_password'])){
348             $this->jerror($this->event_prefix . 'LOGIN-2FA','Your account requires Two-Factor Authentication');
349         }
350         
351         // check if config allows non-owner passwords.
352         // auth_company = "OWNER" // auth_company = "CLIENT" or blank for all?
353         // perhaps it should support arrays..
354         $ff= HTML_FlexyFramework::get();
355         if (!empty($ff->Pman['auth_comptype']) && $ff->Pman['auth_comptype'] != $u->company()->comptype) {
356             //print_r($u->company());
357             $this->jerror($this->event_prefix . 'LOGIN-BADUSER', "Login not permited to outside companies"); // serious failure
358         }
359         
360         
361         // note we trim \x10 -- line break - as it was injected the front end
362         // may have an old bug on safari/chrome that added that character in certian wierd scenarios..
363         if (!$u->checkPassword(trim($_REQUEST['password'],"\x10"))) {
364             $this->jerror($this->event_prefix . 'LOGIN-BAD', 'You typed the wrong Username or Password  (2)'); // - " . htmlspecialchars(print_r($_POST,true))."'");
365             exit;
366         }
367         
368         if(
369             !empty($u->oath_key) &&
370             (
371                 empty($_REQUEST['oath_password']) ||
372                 !$u->checkTwoFactorAuthentication($_REQUEST['oath_password'])
373             )
374         ) {
375             $this->jerror($this->event_prefix . 'LOGIN-BAD', 'You typed the wrong Username or Password  (3)');
376             exit;
377         }
378         
379         $this->ip_checking();
380         
381         $u->login();
382         // we might need this later..
383         $this->addEvent($this->event_prefix . "LOGIN", false, session_id());
384                 
385                 
386                 
387         if (!empty($_REQUEST['lang'])) {
388                         
389                         if (!empty($ff->languages['avail']) && !in_array($_REQUEST['lang'],$ff->languages['avail'])) {
390                                 // ignore.      
391                         } else {
392                         
393                                 $u->lang($_REQUEST['lang']);
394                         }
395         }
396          // log it..
397
398         $this->sendAuthUserDetails();
399         exit;
400          
401         
402     }
403     
404     function passwordRequest($n) 
405     {
406         $u = DB_DataObject::factory('core_person');
407         //$u->company_id = $this->company->id;
408         
409         $u->whereAdd('LENGTH(passwd) > 1');
410         $u->email = $n;
411         $u->active = 1;
412         if ($u->count() > 1 || !$u->find(true)) {
413             $this->jerr('invalid User (1)');
414         }
415         // got a avlid user..
416         if (!strlen($u->passwd)) {
417             $this->jerr('invalid User (2)');
418         }
419         // check to see if we have sent a request before..
420         
421         if ($u->no_reset_sent > 3) {
422             $this->jerr('We have issued to many resets - please contact the Administrator');
423         }
424         
425         
426         
427         
428         // sort out sender.
429         $cm = DB_DataObject::factory('core_email');
430         if (!$cm->get('name', 'ADMIN_PASSWORD_RESET')) {
431             $this->jerr("no template  Admin password reset (ADMIN_PASSWORD_RESET) exists - please run importer ");
432         }
433                 if (!$cm->active) {
434                         $this->jerr("template for Admin password reset has been disabled");
435                 }
436         /*
437         
438         $g = DB_DAtaObject::factory('Groups');
439         if (!$g->get('name', 'system-email-from')) {
440             $this->jerr("no group 'system-email-from' exists in the system");
441         }
442         $from_ar = $g->members();
443         if (count($from_ar) != 1) {
444             $this->jerr(count($from_ar) ? "To many members in the 'system-email-from' group " :
445                        "'system-email-from' group  does not have any members");
446         }
447         */
448         
449         
450         
451         // bcc..
452         $g = DB_DAtaObject::factory('core_group');
453         if (!$g->get('name', 'bcc-email')) {
454             $this->jerr("no group 'bcc-email' exists in the system");
455         }
456         $bcc = $g->members('email');
457         if (!count($bcc)) {
458             $this->jerr( "'bcc-email' group  does not have any members");
459         }
460         
461         
462         
463         $this->authFrom = time();
464         $this->authKey = $u->genPassKey($this->authFrom);
465         //$this->authKey = md5($u->email . $this->authFrom . $u->passwd);
466         $this->person = $u;
467         $this->bcc = $bcc;
468         $this->rcpts = $u->getEmailFrom();
469         
470         
471                 $mailer = $cm->toMailer($this, false);
472                 if (is_a($mailer,'PEAR_Error') ) {
473                         $this->addEvent('SYSERR',false, $mailer->getMessage());
474                         $this->jerr($mailer->getMessage());
475                 }
476         $sent = $mailer->send();
477                 if (is_a($sent,'PEAR_Error') ) {
478                         $this->addEvent('SYSERR',false, $sent->getMessage());
479                         $this->jerr($sent->getMessage());
480         }
481         
482         $this->addEvent($this->event_prefix . 'PASSREQ',$u, $u->email);
483         $uu = clone($u);
484         $uu->no_reset_sent++;
485         $uu->update($u);
486         $this->jok("done");
487         
488     }
489     
490     function verifyResetPassword($id,$t, $key)
491     {
492                 $au = $this->getAuthUser();
493                 //print_R($au);
494         if ($au) {
495             $this->jerr( "Already Logged in - no need to use Password Reset");
496         }
497         
498         $u = DB_DataObject::factory('core_person');
499         //$u->company_id = $this->company->id;
500         $u->active = 1;
501         if (!$u->get($id) || !strlen($u->passwd)) {
502             $this->jerr("Password reset link is not valid (id)");
503         }
504         
505         // validate key.. 
506         if ($key != $u->genPassKey($t)) {
507             $this->jerr("Password reset link is not valid (key)");
508         }
509         
510                 if ($t < strtotime("NOW - 1 DAY")) {
511             $this->jerr("Password reset link has expired");
512         }
513         return $u;
514         
515         
516         
517     }
518     
519     
520     function resetPassword($id,$t, $key, $newpass )
521     {
522         
523         $u = $this->verifyResetPassword($id,$t,$key);
524         
525         
526         $uu = clone($u);
527         $u->no_reset_sent = 0;
528                 if ($newpass != false) {
529                         $u->setPassword($newpass);
530                 }
531         $u->update($uu);
532                 $this->addEvent($this->event_prefix . "CHANGEPASS", $u);
533
534         $this->jok("Password has been Updated");
535     }
536     
537     
538     function changePassword($r)
539     {   
540         $au = $this->getAuthUser();
541         if (!$au) {
542                         $this->jerr("Password change attempted when not logged in");
543                 }
544                 $uu = clone($au);
545                 $au->setPassword($r['passwd1']);
546                 $au->update($uu);
547                 $this->addEvent($this->event_prefix . "CHANGEPASS", $au);
548                 $this->jok($au);
549                          
550     }
551     
552     function ip_checking()
553     {
554         if(empty($this->ip_management)){
555             return;
556         }
557         
558         $ip = $this->ip_lookup();
559         
560         if(empty($ip)){
561             $this->jerr('BAD-IP-ADDRESS', array('ip' => $ip));
562         }
563         
564         $core_ip_access = DB_DataObject::factory('core_ip_access');
565         
566         if(!DB_DataObject::factory('core_ip_access')->count()){ // first ip we always mark it as approved..
567             
568             $core_ip_access = DB_DataObject::factory('core_ip_access');
569             
570             $core_ip_access->setFrom(array(
571                 'ip' => $ip,
572                 'created_dt' => $core_ip_access->sqlValue("NOW()"),
573                 'authorized_key' => md5(openssl_random_pseudo_bytes(16)),
574                 'status' => 1,
575                 'email' => (empty($_REQUEST['username'])) ? '' : $_REQUEST['username'],
576                 'user_agent' => (empty($_SERVER['HTTP_USER_AGENT'])) ? '' : $_SERVER['HTTP_USER_AGENT']
577             ));
578             
579             $core_ip_access->insert();
580             
581             return;
582         }
583         
584         $core_ip_access = DB_DataObject::factory('core_ip_access');
585         
586         if(!$core_ip_access->get('ip', $ip)){ // new ip
587             
588             $core_ip_access->setFrom(array(
589                 'ip' => $ip,
590                 'created_dt' => $core_ip_access->sqlValue("NOW()"),
591                 'authorized_key' => md5(openssl_random_pseudo_bytes(16)),
592                 'status' => 0,
593                 'email' => (empty($_REQUEST['username'])) ? '' : $_REQUEST['username'],
594                 'user_agent' => (empty($_SERVER['HTTP_USER_AGENT'])) ? '' : $_SERVER['HTTP_USER_AGENT']
595             ));
596             
597             $core_ip_access->insert();
598             
599             $core_ip_access->sendXMPP();
600             
601             $this->jerror('NEW-IP-ADDRESS', "New IP Address = needs approving", array('ip' => $ip));
602             
603             return;
604         }
605         
606         if(empty($core_ip_access->status)){
607             $this->jerror('PENDING-IP-ADDRESS', "IP is still pending approval", array('ip' => $ip));
608         }
609         
610         if($core_ip_access->status == -1){
611             $this->jerror('BLOCKED-IP-ADDRESS', "Your IP is blocked", array('ip' => $ip));
612             return;
613         }
614         
615         if($core_ip_access->status == -2 && strtotime($core_ip_access->expire_dt) < strtotime('NOW')){
616             $this->jerrpr('BLOCKED-IP-ADDRESS', "Your IP is blocked", array('ip' => $ip));
617             return;
618         }
619         
620         return;
621     }
622     
623     function ip_lookup()
624     {
625
626         if (!empty($_SERVER['HTTP_CLIENT_IP'])){
627             return $_SERVER['HTTP_CLIENT_IP'];
628         }
629         
630         if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
631             return $_SERVER['HTTP_X_FORWARDED_FOR'];
632         }
633         
634         return $_SERVER['REMOTE_ADDR'];
635     }
636 }
637