3 require_once 'Pman.php';
9 * User class must provide the following features.
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'])) {
19 * lang(val) - to set the language..
24 class Pman_Login extends Pman
26 var $masterTemplate = 'login.html';
28 var $ip_management = false;
30 var $event_suffix = '';
32 // for forgot password email
40 function getAuth() // everyone allowed in here..
42 parent::getAuth(); // load company..
44 $ff = HTML_FlexyFramework::get();
46 $this->ip_management = (empty($ff->Pman['ip_management'])) ? false : true;
56 function get($v, $opts=array())
58 $this->initErrorHandling();
60 // DB_DataObject::DebugLevel(5);
61 if (!empty($_REQUEST['logout'])) {
62 return $this->logout();
66 if (!empty($_REQUEST['getAuthUser'])) {
67 //DB_Dataobject::debugLevel(5);
68 $this->sendAuthUserDetails();
72 if(!empty($_REQUEST['check_owner_company'])) {
73 $core_company = DB_DataObject::factory('core_company');
74 $core_company->comptype = 'OWNER';
75 $this->jok($core_company->count());
78 // might be an idea to disable this?!?
79 if (!empty($_REQUEST['username'])) {
84 if (!empty($_REQUEST['switch'])) {
85 $this->switchUser($_REQUEST['switch']);
88 if (!empty($_REQUEST['loginPublic'])) {
89 $this->switchPublicUser($_REQUEST['loginPublic']);
91 if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('/^check_http/', $_SERVER['HTTP_USER_AGENT'])) {
92 die("server is alive = authFailure"); // should really use heartbeat now..
94 $this->jerror("NOTICE-INVALID", "INVALID REQUEST");
101 $ff = class_exists('HTML_FlexyFramework2') ? HTML_FlexyFramework2::get() : HTML_FlexyFramework::get();
103 //DB_DAtaObject::debugLevel(1);
104 $u = $this->getAuthUser();
108 $this->addEvent('LOGOUT'. $this->event_suffix);
109 $e = DB_DataObject::factory('Events');
113 session_regenerate_id(true);
116 if(!empty($ff->Pman['local_autoauth']) && !empty($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST'] == 'localhost') {
117 $this->jerr("you are using local autoauth!?");
119 //echo '<PRE>';print_R($_SESSION);
120 $this->jok("Logged out - user ");
124 //$_SESSION['Pman_I18N'] = array(); <<
125 session_regenerate_id(true);
128 $this->jok("Logged out - no user");
132 function sendAuthUserDetails()
134 // remove for normal use - it's a secuirty hole!
135 //DB_DataObject::debugLevel(1);
136 if (!empty($_REQUEST['_debug'])) {
137 // DB_DataObject::debugLevel(1);
140 $ff = HTML_FlexyFramework::get();
141 $tbl = empty($ff->Pman['authTable']) ? 'core_person' : $ff->Pman['authTable'];
143 $u = DB_DataObject::factory($tbl);
144 $s = DB_DataObject::factory('core_setting');
145 $require_oath_val = 1;
146 $require_oath = $s->lookup('core', 'two_factor_auth_required');
147 if(!empty($require_oath)) {
148 if($require_oath->val == 0) {
149 $require_oath_val = 0;
161 $au = $u->getAuthUser();
163 // might occur on shared systems.
164 $ff= HTML_FlexyFramework::get();
166 if (!empty($ff->Pman['auth_comptype']) && $au->id > 0 &&
167 ($ff->Pman['auth_comptype'] != $au->company()->comptype)) {
169 $this->jerr("Login not permited to outside companies - please reload");
172 //$au = $u->getAuthUser();
174 $aur = $au ? $au->authUserArray() : array();
176 /** -- these need modulizing somehow! **/
180 // basically calls Pman_MODULE_Login::sendAuthUserDetails($aur) on all the modules
181 //echo '<PRE>'; print_r($this->modules());
182 // technically each module should only add properties to an array named after that module..
184 foreach($this->modules() as $m) {
188 if (!file_exists($this->rootDir.'/Pman/'.$m.'/Login.php')) {
191 $cls = 'Pman_'.$m.'_Login';
192 require_once 'Pman/'.$m.'/Login.php';
195 $aur = $x->sendAuthUserDetails($aur);
200 // echo '<PRE>';print_r($aur);
208 function switchUser($id)
210 $tbl = empty($ff->Pman['authTable']) ? 'core_person' : $ff->Pman['authTable'];
211 $u = DB_DataObject::factory($tbl);
213 $this->err("not logged in");
216 $au = $u->getAuthUser();
218 // first check they have perms to do this..
219 if (!$au|| ($au->company()->comptype != 'OWNER') || !$this->hasPerm('Core.Person', 'E')) {
220 $this->jerr("User switching not permitted");
223 $u = DB_DataObject::factory($tbl);
226 $this->jerr('Account disabled');
229 // we might need this later..
230 $this->addEvent("LOGIN-SWITCH-USER". $this->event_suffix, false, $au->name . ' TO ' . $u->name);
231 $this->jok("SWITCH");
235 function switchPublicUser($id)
237 $tbl = empty($ff->Pman['authTable']) ? 'core_person' : $ff->Pman['authTable'];
239 $u = DB_DataObject::factory($tbl);
243 $this->jerr('Account disabled');
246 if(!$u->loginPublic()){
247 $this->jerr('Switch fail');
257 //DB_DataObject::debugLevel(1);
259 if (!empty($_REQUEST['getAuthUser'])) {
260 $this->sendAuthUserDetails();
264 if (!empty($_REQUEST['logout'])) {
265 return $this->logout();
268 if(!empty($_REQUEST['check_owner_company'])) {
269 $core_company = DB_DataObject::factory('core_company');
270 $core_company->comptype = 'OWNER';
271 $this->jok($core_company->count());
274 if (!empty($_REQUEST['passwordRequest'])) { //|| (strpos($_REQUEST['username'], '@') < 1)) {
275 return $this->passwordRequest($_REQUEST['passwordRequest']);
278 if (!empty($_REQUEST['ResetPassword'])) {
279 if (empty($_REQUEST['id']) ||
280 empty($_REQUEST['ts']) ||
281 empty($_REQUEST['key']) ||
282 empty($_REQUEST['password1']) ||
283 empty($_REQUEST['password2']) ||
284 ($_REQUEST['password1'] != $_REQUEST['password2'])
286 $this->jerr("Invalid request to reset password");
289 $this->resetPassword($_REQUEST['id'], $_REQUEST['ts'], $_REQUEST['key'], $_REQUEST['password1'] );
293 if (!empty($_REQUEST['_verifyCheckSum'])) {
294 if (empty($_REQUEST['id']) ||
295 empty($_REQUEST['ts']) ||
296 empty($_REQUEST['key'])
299 $this->jerr("Invalid request to reset password");
302 $this->verifyResetPassword($_REQUEST['id'], $_REQUEST['ts'], $_REQUEST['key']);
303 $this->jok("Checksum is ok");
306 // this is 'classic' change password...
307 if (!empty($_REQUEST['changePassword'])) {
308 return $this->changePassword($_REQUEST);
313 $ff = HTML_FlexyFramework::get();
314 $tbl = empty($ff->Pman['authTable']) ? 'core_person' : $ff->Pman['authTable'];
317 $u = DB_DataObject::factory($tbl);
319 $ip = $this->ip_lookup();
322 //DB_DataObject::DebugLevel(1);
323 $e = DB_DataObject::Factory('Events');
324 $e->action = 'LOGIN-BAD'. $this->event_suffix;
326 $e->whereAdd('event_when > NOW() - INTERVAL 10 MINUTE');
327 if ($e->count() > 5) {
328 $this->jerror('LOGIN-RATE'. $this->event_suffix, "Login failures are rate limited - please try later");
332 // this was removed before - not quite sure why.
333 // when a duplicate login account is created, this stops the old one from interfering..
336 // empty username = not really a hacking attempt.
338 if (empty($_REQUEST['username'])) { //|| (strpos($_REQUEST['username'], '@') < 1)) {
339 $this->jerror('LOGIN-EMPTY'. $this->event_suffix, 'You typed the wrong Username or Password (0)');
343 $u->authUserName($_REQUEST['username']);
345 if ($u->count() > 1 || !$u->find(true)) {
346 $this->jerror('LOGIN-BAD'. $this->event_suffix,'You typed the wrong Username or Password (1)');
351 $this->jerror('LOGIN-BAD'. $this->event_suffix,'Account disabled');
354 if(!empty($u->oath_key) && empty($_REQUEST['oath_password'])){
355 $this->jerror('LOGIN-2FA'. $this->event_suffix,'Your account requires Two-Factor Authentication');
358 // check if config allows non-owner passwords.
359 // auth_company = "OWNER" // auth_company = "CLIENT" or blank for all?
360 // perhaps it should support arrays..
361 $ff= HTML_FlexyFramework::get();
362 if (!empty($ff->Pman['auth_comptype']) && $ff->Pman['auth_comptype'] != $u->company()->comptype) {
363 //print_r($u->company());
364 $this->jerror('LOGIN-BADUSER'. $this->event_suffix, "Login not permited to outside companies"); // serious failure
368 // note we trim \x10 -- line break - as it was injected the front end
369 // may have an old bug on safari/chrome that added that character in certian wierd scenarios..
370 if (!$u->checkPassword(trim($_REQUEST['password'],"\x10"))) {
371 $this->jerror('LOGIN-BAD'. $this->event_suffix, 'You typed the wrong Username or Password (2)'); // - " . htmlspecialchars(print_r($_POST,true))."'");
376 !empty($u->oath_key) &&
378 empty($_REQUEST['oath_password']) ||
379 !$u->checkTwoFactorAuthentication($_REQUEST['oath_password'])
382 $this->jerror('LOGIN-BAD'. $this->event_suffix, 'You typed the wrong Username or Password (3)');
386 $this->ip_checking();
389 // we might need this later..
390 $this->addEvent("LOGIN". $this->event_suffix, false, session_id());
394 if (!empty($_REQUEST['lang'])) {
396 if (!empty($ff->languages['avail']) && !in_array($_REQUEST['lang'],$ff->languages['avail'])) {
400 $u->lang($_REQUEST['lang']);
405 $this->sendAuthUserDetails();
411 function passwordRequest($n)
413 $u = DB_DataObject::factory('core_person');
414 //$u->company_id = $this->company->id;
416 $u->whereAdd('LENGTH(passwd) > 1');
419 if ($u->count() > 1 || !$u->find(true)) {
420 $this->jerr('invalid User (1)');
422 // got a avlid user..
423 if (!strlen($u->passwd)) {
424 $this->jerr('invalid User (2)');
426 // check to see if we have sent a request before..
428 if ($u->no_reset_sent > 3) {
429 $this->jerr('We have issued to many resets - please contact the Administrator');
436 $cm = DB_DataObject::factory('core_email');
437 if (!$cm->get('name', 'ADMIN_PASSWORD_RESET')) {
438 $this->jerr("no template Admin password reset (ADMIN_PASSWORD_RESET) exists - please run importer ");
441 $this->jerr("template for Admin password reset has been disabled");
445 $g = DB_DAtaObject::factory('Groups');
446 if (!$g->get('name', 'system-email-from')) {
447 $this->jerr("no group 'system-email-from' exists in the system");
449 $from_ar = $g->members();
450 if (count($from_ar) != 1) {
451 $this->jerr(count($from_ar) ? "To many members in the 'system-email-from' group " :
452 "'system-email-from' group does not have any members");
459 $g = DB_DAtaObject::factory('core_group');
460 if (!$cm->bcc_group_id || !$g->get($cm->bcc_group_id)) {
461 $this->jerr("BCC for ADMIN_PASSWORD_RESET email has not been set");
463 $bcc = $g->members('email');
465 $this->jerr( "'BCC group for ADMIN_PASSWORD_RESET does not have any members");
470 $this->authFrom = time();
471 $this->authKey = $u->genPassKey($this->authFrom);
472 //$this->authKey = md5($u->email . $this->authFrom . $u->passwd);
475 $this->rcpts = $u->getEmailFrom();
478 $mailer = $cm->toMailer($this, false);
479 if (is_a($mailer,'PEAR_Error') ) {
480 $this->addEvent('SYSERR',false, $mailer->getMessage());
481 $this->jerr($mailer->getMessage());
483 $sent = $mailer->send();
484 if (is_a($sent,'PEAR_Error') ) {
485 $this->addEvent('SYSERR',false, $sent->getMessage());
486 $this->jerr($sent->getMessage());
489 $this->addEvent('LOGIN-PASSREQ'. $this->event_suffix,$u, $u->email);
491 $uu->no_reset_sent++;
497 function verifyResetPassword($id,$t, $key)
499 $au = $this->getAuthUser();
502 $this->jerr( "Already Logged in - no need to use Password Reset");
505 $u = DB_DataObject::factory('core_person');
506 //$u->company_id = $this->company->id;
508 if (!$u->get($id) || !strlen($u->passwd)) {
509 $this->jerr("Password reset link is not valid (id)");
513 if ($key != $u->genPassKey($t)) {
514 $this->jerr("Password reset link is not valid (key)");
517 if ($t < strtotime("NOW - 1 DAY")) {
518 $this->jerr("Password reset link has expired");
527 function resetPassword($id,$t, $key, $newpass )
530 $u = $this->verifyResetPassword($id,$t,$key);
534 $u->no_reset_sent = 0;
535 if ($newpass != false) {
536 $u->setPassword($newpass);
539 $this->addEvent("LOGIN-CHANGEPASS". $this->event_suffix, $u);
541 $this->jok("Password has been Updated");
545 function changePassword($r)
547 $au = $this->getAuthUser();
549 $this->jerr("Password change attempted when not logged in");
552 $au->setPassword($r['passwd1']);
554 $this->addEvent("LOGIN-CHANGEPASS". $this->event_suffix, $au);
559 function ip_checking()
561 if(empty($this->ip_management)){
565 $ip = $this->ip_lookup();
568 $this->jerr('BAD-IP-ADDRESS', array('ip' => $ip));
571 $core_ip_access = DB_DataObject::factory('core_ip_access');
573 if(!DB_DataObject::factory('core_ip_access')->count()){ // first ip we always mark it as approved..
575 $core_ip_access = DB_DataObject::factory('core_ip_access');
577 $core_ip_access->setFrom(array(
579 'created_dt' => $core_ip_access->sqlValue("NOW()"),
580 'authorized_key' => md5(openssl_random_pseudo_bytes(16)),
582 'email' => (empty($_REQUEST['username'])) ? '' : $_REQUEST['username'],
583 'user_agent' => (empty($_SERVER['HTTP_USER_AGENT'])) ? '' : $_SERVER['HTTP_USER_AGENT']
586 $core_ip_access->insert();
591 $core_ip_access = DB_DataObject::factory('core_ip_access');
593 if(!$core_ip_access->get('ip', $ip)){ // new ip
595 $core_ip_access->setFrom(array(
597 'created_dt' => $core_ip_access->sqlValue("NOW()"),
598 'authorized_key' => md5(openssl_random_pseudo_bytes(16)),
600 'email' => (empty($_REQUEST['username'])) ? '' : $_REQUEST['username'],
601 'user_agent' => (empty($_SERVER['HTTP_USER_AGENT'])) ? '' : $_SERVER['HTTP_USER_AGENT']
604 $core_ip_access->insert();
606 $core_ip_access->sendXMPP();
608 $this->jerror('NEW-IP-ADDRESS', "New IP Address = needs approving", array('ip' => $ip));
613 if(empty($core_ip_access->status)){
614 $this->jerror('PENDING-IP-ADDRESS', "IP is still pending approval", array('ip' => $ip));
617 if($core_ip_access->status == -1){
618 $this->jerror('BLOCKED-IP-ADDRESS', "Your IP is blocked", array('ip' => $ip));
622 if($core_ip_access->status == -2 && strtotime($core_ip_access->expire_dt) < strtotime('NOW')){
623 $this->jerror('BLOCKED-IP-ADDRESS', "Your IP is blocked", array('ip' => $ip));
633 if (!empty($_SERVER['HTTP_CLIENT_IP'])){
634 return $_SERVER['HTTP_CLIENT_IP'];
637 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
638 return $_SERVER['HTTP_X_FORWARDED_FOR'];
641 return $_SERVER['REMOTE_ADDR'];