Overview

Packages

  • awl
    • AuthPlugin
    • AwlDatabase
    • Browser
    • classEditor
    • DataEntry
    • DataUpdate
    • EMail
    • iCalendar
    • MenuSet
    • PgQuery
    • Session
    • Translation
    • User
    • Utilities
    • Validation
    • vCalendar
    • vComponent
    • XMLDocument
    • XMLElement
  • None
  • PHP

Classes

  • Session

Functions

  • check_temporary_passwords
  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  • Todo
  1: <?php
  2: /**
  3: * Session handling class and associated functions
  4: *
  5: * This subpackage provides some functions that are useful around web
  6: * application session management.
  7: *
  8: * The class is intended to be as lightweight as possible while holding
  9: * all session data in the database:
 10: *  - Session hash is not predictable.
 11: *  - No clear text information is held in cookies.
 12: *  - Passwords are generally salted MD5 hashes, but individual users may
 13: *    have plain text passwords set by an administrator.
 14: *  - Temporary passwords are supported.
 15: *  - Logout is supported
 16: *  - "Remember me" cookies are supported, and will result in a new
 17: *    Session for each browser session.
 18: *
 19: * @package   awl
 20: * @subpackage   Session
 21: * @author Andrew McMillan <andrew@mcmillan.net.nz>
 22: * @copyright Catalyst IT Ltd, Morphoss Ltd <http://www.morphoss.com/>
 23: * @license   http://gnu.org/copyleft/gpl.html GNU GPL v2 or later
 24: */
 25: require_once('AWLUtilities.php');
 26: require_once('AwlQuery.php');
 27: require_once('EMail.php');
 28: 
 29: 
 30: /**
 31: * Checks what a user entered against any currently valid temporary passwords on their account.
 32: * @param string $they_sent What the user entered.
 33: * @param int $user_no Which user is attempting to log on.
 34: * @return boolean Whether or not the user correctly guessed a temporary password within the necessary window of opportunity.
 35: */
 36: function check_temporary_passwords( $they_sent, $user_no ) {
 37:   $sql = 'SELECT 1 AS ok FROM tmp_password WHERE user_no = ? AND password = ? AND valid_until > current_timestamp';
 38:   $qry = new AwlQuery( $sql, $user_no, $they_sent );
 39:   if ( $qry->Exec('Session::check_temporary_passwords') ) {
 40:     dbg_error_log( "Login", " check_temporary_passwords: Rows = ".$qry->rows());
 41:     if ( $row = $qry->Fetch() ) {
 42:       dbg_error_log( "Login", " check_temporary_passwords: OK = $row->ok");
 43:       // Remove all the temporary passwords for that user...
 44:       $sql = 'DELETE FROM tmp_password WHERE user_no = ? ';
 45:       $qry = new AwlQuery( $sql, $user_no );
 46:       $qry->Exec('Login',__LINE__,__FILE__);
 47:       return true;
 48:     }
 49:   }
 50:   return false;
 51: }
 52: 
 53: /**
 54: * A class for creating and holding session information.
 55: *
 56: * @package   awl
 57: */
 58: class Session
 59: {
 60:   /**#@+
 61:   * @access private
 62:   */
 63:   var $roles;
 64:   var $cause = '';
 65:   /**#@-*/
 66: 
 67:   /**#@+
 68:   * @access public
 69:   */
 70: 
 71:   /**
 72:   * The user_no of the logged in user.
 73:   * @var int
 74:   */
 75:   var $user_no;
 76: 
 77:   /**
 78:   * A unique id for this user's logged-in session.
 79:   * @var int
 80:   */
 81:   var $session_id = 0;
 82: 
 83:   /**
 84:   * The user's username used to log in.
 85:   * @var int
 86:   */
 87:   var $username = 'guest';
 88: 
 89:   /**
 90:   * The user's full name from their usr record.
 91:   * @var int
 92:   */
 93:   var $fullname = 'Guest';
 94: 
 95:   /**
 96:   * The user's email address from their usr record.
 97:   * @var int
 98:   */
 99:   var $email = '';
100: 
101:   /**
102:   * Whether this user has actually logged in.
103:   * @var int
104:   */
105:   var $logged_in = false;
106: 
107:   /**
108:   * Whether the user logged in to view the current page.  Perhaps some details on the
109:   * login form might pollute an editable form and result in an unplanned submit.  This
110:   * can be used to program around such a problem.
111:   * @var boolean
112:   */
113:   var $just_logged_in = false;
114: 
115:   /**
116:   * The date and time that the user logged on during their last session.
117:   * @var string
118:   */
119:   var $last_session_start;
120: 
121:   /**
122:   * The date and time that the user requested their last page during their last
123:   * session.
124:   * @var string
125:   */
126:   var $last_session_end;
127:   /**#@-*/
128: 
129:   /**
130:   * Create a new Session object.
131:   *
132:   * If a session identifier is supplied, or we can find one in a cookie, we validate it
133:   * and consider the person logged in.  We read some useful session and user data in
134:   * passing as we do this.
135:   *
136:   * The session identifier contains a random value, hashed, to provide validation. This
137:   * could be hijacked if the traffic was sniffable so sites who are paranoid about security
138:   * should only do this across SSL.
139:   *
140:   * A worthwhile enhancement would be to add some degree of external configurability to
141:   * that read.
142:   *
143:   * @param string $sid A session identifier.
144:   */
145:   function Session( $sid="" )
146:   {
147:     global $sid, $sysname;
148: 
149:     $this->roles = array();
150:     $this->logged_in = false;
151:     $this->just_logged_in = false;
152:     $this->login_failed = false;
153: 
154:     if ( $sid == "" ) {
155:       if ( ! isset($_COOKIE['sid']) ) return;
156:       $sid = $_COOKIE['sid'];
157:     }
158: 
159:     list( $session_id, $session_key ) = explode( ';', $sid, 2 );
160: 
161:     /**
162:     * We regularly want to override the SQL for joining against the session record.
163:     * so the calling application can define a function local_session_sql() which
164:     * will return the SQL to join (up to and excluding the WHERE clause.  The standard
165:     * SQL used if this function is not defined is:
166:     * <code>
167:     * SELECT session.*, usr.* FROM session JOIN usr ON ( user_no )
168:     * </code>
169:     */
170:     if ( function_exists('local_session_sql') ) {
171:       $sql = local_session_sql();
172:     }
173:     else {
174:       $sql = "SELECT session.*, usr.* FROM session JOIN usr USING ( user_no )";
175:     }
176:     $sql .= " WHERE session.session_id = ? AND (md5(session.session_start::text) = ? OR session.session_key = ?) ORDER BY session.session_start DESC LIMIT 2";
177: 
178:     $qry = new AwlQuery($sql, $session_id, $session_key, $session_key);
179:     if ( $qry->Exec('Session') && 1 == $qry->rows() ) {
180:       $this->AssignSessionDetails( $qry->Fetch() );
181:       $qry = new AwlQuery('UPDATE session SET session_end = current_timestamp WHERE session_id=?', $session_id);
182:       $qry->Exec('Session');
183:     }
184:     else {
185:       //  Kill the existing cookie, which appears to be bogus
186:       setcookie('sid', '', 0,'/');
187:       $this->cause = 'ERR: Other than one session record matches. ' . $qry->rows();
188:       $this->Log( "WARN: Login $this->cause" );
189:     }
190:   }
191: 
192: 
193:   /**
194:   * DEPRECATED Utility function to log stuff with printf expansion.
195:   *
196:   * This function could be expanded to log something identifying the session, but
197:   * somewhat strangely this has not yet been done.
198:   *
199:   * @param string $whatever A log string
200:   * @param mixed $whatever... Further parameters to be replaced into the log string a la printf
201:   * @deprecated
202:   */
203:   function Log( $whatever )
204:   {
205:     global $c;
206:     deprecated('Session::Log');
207: 
208:     $argc = func_num_args();
209:     $format = func_get_arg(0);
210:     if ( $argc == 1 || ($argc == 2 && func_get_arg(1) == "0" ) ) {
211:       error_log( "$c->sysabbr: $format" );
212:     }
213:     else {
214:       $args = array();
215:       for( $i=1; $i < $argc; $i++ ) {
216:         $args[] = func_get_arg($i);
217:       }
218:       error_log( "$c->sysabbr: " . vsprintf($format,$args) );
219:     }
220:   }
221: 
222:   /**
223:   * DEPRECATED Utility function to log debug stuff with printf expansion, and the ability to
224:   * enable it selectively.
225:   *
226:   * The enabling is done by setting a variable "$debuggroups[$group] = 1"
227:   *
228:   * @param string $group The name of an arbitrary debug group.
229:   * @param string $whatever A log string
230:   * @param mixed $whatever... Further parameters to be replaced into the log string a la printf
231:   * @deprecated
232:   */
233:   function Dbg( $whatever )
234:   {
235:     global $debuggroups, $c;
236:     deprecated('Session::Dbg');
237: 
238:     $argc = func_num_args();
239:     $dgroup = func_get_arg(0);
240: 
241:     if ( ! (isset($debuggroups[$dgroup]) && $debuggroups[$dgroup]) ) return;
242: 
243:     $format = func_get_arg(1);
244:     if ( $argc == 2 || ($argc == 3 && func_get_arg(2) == "0" ) ) {
245:       error_log( "$c->sysabbr: DBG: $dgroup: $format" );
246:     }
247:     else {
248:       $args = array();
249:       for( $i=2; $i < $argc; $i++ ) {
250:         $args[] = func_get_arg($i);
251:       }
252:       error_log( "$c->sysabbr: DBG: $dgroup: " . vsprintf($format,$args) );
253:     }
254:   }
255: 
256:   /**
257:   * Checks whether a user is allowed to do something.
258:   *
259:   * The check is performed to see if the user has that role.
260:   *
261:   * @param string $whatever The role we want to know if the user has.
262:   * @return boolean Whether or not the user has the specified role.
263:   */
264:   function AllowedTo ( $whatever ) {
265:     return ( $this->logged_in && isset($this->roles[$whatever]) && $this->roles[$whatever] );
266:   }
267: 
268: 
269: /**
270: * Internal function used to get the user's roles from the database.
271: */
272:   function GetRoles () {
273:     $this->roles = array();
274:     $qry = new AwlQuery( 'SELECT role_name FROM role_member m join roles r ON r.role_no = m.role_no WHERE user_no = ? ', $this->user_no );
275:     if ( $qry->Exec('Session::GetRoles') && $qry->rows() > 0 ) {
276:       while( $role = $qry->Fetch() ) {
277:         $this->roles[$role->role_name] = true;
278:       }
279:     }
280:   }
281: 
282: 
283: /**
284: * Internal function used to assign the session details to a user's new session.
285: * @param object $u The user+session object we (probably) read from the database.
286: */
287:   function AssignSessionDetails( $u ) {
288:     // Assign each field in the selected record to the object
289:     foreach( $u AS $k => $v ) {
290:       $this->{$k} = $v;
291:     }
292: 
293:     $date_format = ($this->date_format_type == 'E' ? 'European,ISO' : ($this->date_format_type == 'U' ? 'US,ISO' : 'ISO'));
294:     $qry = new AwlQuery( 'SET DATESTYLE TO '. $date_format );
295:     $qry->Exec();
296: 
297:     $this->GetRoles();
298:     $this->logged_in = true;
299:   }
300: 
301: 
302: /**
303: * Attempt to perform a login action.
304: *
305: * This will validate the user's username and password.  If they are OK then a new
306: * session id will be created and the user will be cookied with it for subsequent
307: * pages.  A logged in session will be created, and the $_POST array will be cleared
308: * of the username, password and submit values.  submit will also be cleared from
309: * $_GET and $GLOBALS, just in case.
310: *
311: * @param string $username The user's login name, or at least what they entered it as.
312: * @param string $password The user's password, or at least what they entered it as.
313: * @param string $authenticated If true, then authentication has already happened and the password is not checked, though the user must still exist.
314: * @return boolean Whether or not the user correctly guessed a temporary password within the necessary window of opportunity.
315: */
316:   function Login( $username, $password, $authenticated = false ) {
317:     global $c;
318:     $rc = false;
319:     dbg_error_log( "Login", " Login: Attempting login for $username" );
320:     if ( isset($usr) ) unset($usr);  /** In case someone is running with register_globals on */
321: 
322:     /**
323:     * @todo In here we will need to put code to call the auth plugin, in order to
324:     * ensure the 'usr' table has current valid data.  At this stage we are just
325:     * thinking it through... like ...
326:     *
327:     */
328:     if ( !$authenticated && isset($c->authenticate_hook) && isset($c->authenticate_hook['call']) && function_exists($c->authenticate_hook['call']) ) {
329:       /**
330:       * The authenticate hook needs to:
331:       *   - Accept a username / password
332:       *   - Confirm the username / password are correct
333:       *   - Create (or update) a 'usr' record in our database
334:       *   - Return the 'usr' record as an object
335:       *   - Return === false when authentication fails
336:       * It can expect that:
337:       *   - Configuration data will be in $c->authenticate_hook['config'], which might be an array, or whatever is needed.
338:       */
339:       $usr = call_user_func( $c->authenticate_hook['call'], $username, $password );
340:       if ( $usr === false ) unset($usr); else $authenticated = true;
341:     }
342: 
343:     $sql = "SELECT * FROM usr WHERE lower(username) = text(?) AND active";
344:     $qry = new AwlQuery( $sql, strtolower($username) );
345:     if ( isset($usr) || ($qry->Exec('Login',__LINE__,__FILE__) && $qry->rows() == 1 && $usr = $qry->Fetch() ) ) {
346:       $user_no = ( method_exists( $usr, 'user_no' ) ? $usr->user_no() : $usr->user_no );
347:       if ( $authenticated || session_validate_password( $password, $usr->password ) || check_temporary_passwords( $password, $user_no ) ) {
348:         // Now get the next session ID to create one from...
349:         $qry = new AwlQuery( "SELECT nextval('session_session_id_seq')" );
350:         if ( $qry->Exec('Login') && $qry->rows() == 1 ) {
351:           $seq = $qry->Fetch();
352:           $session_id = $seq->nextval;
353:           $session_key = md5( rand(1010101,1999999999) . microtime() );  // just some random shite
354:           dbg_error_log( "Login", " Login: Valid username/password for $username ($user_no)" );
355: 
356:           // Set the last_used timestamp to match the previous login.
357:           $qry = new AwlQuery('UPDATE usr SET last_used = (SELECT session_start FROM session WHERE session.user_no = ? ORDER BY session_id DESC LIMIT 1) WHERE user_no = ?;', $usr->user_no, $usr->user_no);
358:           $qry->Exec('Session');
359: 
360:           // And create a session
361:           $sql = "INSERT INTO session (session_id, user_no, session_key) VALUES( ?, ?, ? )";
362:           $qry = new AwlQuery( $sql, $session_id, $user_no, $session_key );
363:           if ( $qry->Exec('Login') ) {
364:             // Assign our session ID variable
365:             $sid = "$session_id;$session_key";
366: 
367:             //  Create a cookie for the sesssion
368:             setcookie('sid',$sid, 0,'/');
369:             // Recognise that we have started a session now too...
370:             $this->Session($sid);
371:             dbg_error_log( "Login", " Login: New session $session_id started for $username ($user_no)" );
372:             if ( isset($_POST['remember']) && intval($_POST['remember']) > 0 ) {
373:               $cookie = md5( $user_no ) . ";";
374:               $cookie .= session_salted_md5($user_no . $usr->username . $usr->password);
375:               $GLOBALS['lsid'] = $cookie;
376:               setcookie( "lsid", $cookie, time() + (86400 * 3600), "/" );   // will expire in ten or so years
377:             }
378:             $this->just_logged_in = true;
379: 
380:             // Unset all of the submitted values, so we don't accidentally submit an unexpected form.
381:             unset($_POST['username']);
382:             unset($_POST['password']);
383:             unset($_POST['submit']);
384:             unset($_GET['submit']);
385:             unset($GLOBALS['submit']);
386: 
387:             if ( function_exists('local_session_sql') ) {
388:               $sql = local_session_sql();
389:             }
390:             else {
391:               $sql = "SELECT session.*, usr.* FROM session JOIN usr USING ( user_no )";
392:             }
393:             $sql .= " WHERE session.session_id = ? AND (md5(session.session_start::text) = ? OR session.session_key = ?) ORDER BY session.session_start DESC LIMIT 2";
394: 
395:             $qry = new AwlQuery($sql, $session_id, $session_key, $session_key);
396:             if ( $qry->Exec('Session') && 1 == $qry->rows() ) {
397:               $this->AssignSessionDetails( $qry->Fetch() );
398:             }
399: 
400:             $rc = true;
401:             return $rc;
402:           }
403:    // else ...
404:           $this->cause = 'ERR: Could not create new session.';
405:         }
406:         else {
407:           $this->cause = 'ERR: Could not increment session sequence.';
408:         }
409:       }
410:       else {
411:         $c->messages[] = i18n('Invalid username or password.');
412:         if ( isset($c->dbg['Login']) || isset($c->dbg['ALL']) )
413:           $this->cause = 'WARN: Invalid password.';
414:         else
415:           $this->cause = 'WARN: Invalid username or password.';
416:       }
417:     }
418:     else {
419:     $c->messages[] = i18n('Invalid username or password.');
420:     if ( isset($c->dbg['Login']) || isset($c->dbg['ALL']) )
421:       $this->cause = 'WARN: Invalid username.';
422:     else
423:       $this->cause = 'WARN: Invalid username or password.';
424:     }
425: 
426:     $this->Log( "Login failure: $this->cause" );
427:     $this->login_failed = true;
428:     $rc = false;
429:     return $rc;
430:   }
431: 
432: 
433: 
434: /**
435: * Attempts to logs in using a long-term session ID
436: *
437: * This is all horribly insecure, but its hard not to be.
438: *
439: * @param string $lsid The user's value of the lsid cookie.
440: * @return boolean Whether or not the user's lsid cookie got them in the door.
441: */
442:   function LSIDLogin( $lsid ) {
443:     global $c;
444:     dbg_error_log( "Login", " LSIDLogin: Attempting login for $lsid" );
445: 
446:     list($md5_user_no,$validation_string) = explode( ';', $lsid );
447:     $qry = new AwlQuery( "SELECT * FROM usr WHERE md5(user_no::text)=? AND active", $md5_user_no );
448:     if ( $qry->Exec('Login') && $qry->rows() == 1 ) {
449:       $usr = $qry->Fetch();
450:       list( $x, $salt, $y) = explode('*', $validation_string);
451:       $my_validation = session_salted_md5($usr->user_no . $usr->username . $usr->password, $salt);
452:       if ( $validation_string == $my_validation ) {
453:         // Now get the next session ID to create one from...
454:         $qry = new AwlQuery( "SELECT nextval('session_session_id_seq')" );
455:         if ( $qry->Exec('Login') && $qry->rows() == 1 ) {
456:           $seq = $qry->Fetch();
457:           $session_id = $seq->nextval;
458:           $session_key = md5( rand(1010101,1999999999) . microtime() );  // just some random shite
459:           dbg_error_log( "Login", " LSIDLogin: Valid username/password for $username ($usr->user_no)" );
460: 
461:           // And create a session
462:           $sql = "INSERT INTO session (session_id, user_no, session_key) VALUES( ?, ?, ? )";
463:           $qry = new AwlQuery( $sql, $session_id, $usr->user_no, $session_key );
464:           if ( $qry->Exec('Login') ) {
465:             // Assign our session ID variable
466:             $sid = "$session_id;$session_key";
467: 
468:             //  Create a cookie for the sesssion
469:             setcookie('sid',$sid, 0,'/');
470:             // Recognise that we have started a session now too...
471:             $this->Session($sid);
472:             dbg_error_log( "Login", " LSIDLogin: New session $session_id started for $this->username ($usr->user_no)" );
473: 
474:             $this->just_logged_in = true;
475: 
476:             // Unset all of the submitted values, so we don't accidentally submit an unexpected form.
477:             unset($_POST['username']);
478:             unset($_POST['password']);
479:             unset($_POST['submit']);
480:             unset($_GET['submit']);
481:             unset($GLOBALS['submit']);
482: 
483:             if ( function_exists('local_session_sql') ) {
484:               $sql = local_session_sql();
485:             }
486:             else {
487:               $sql = "SELECT session.*, usr.* FROM session JOIN usr USING ( user_no )";
488:             }
489:             $sql .= " WHERE session.session_id = ? AND (md5(session.session_start::text) = ? OR session.session_key = ?) ORDER BY session.session_start DESC LIMIT 2";
490: 
491:             $qry = new AwlQuery($sql, $session_id, $session_key, $session_key);
492:             if ( $qry->Exec('Session') && 1 == $qry->rows() ) {
493:               $this->AssignSessionDetails( $qry->Fetch() );
494:             }
495: 
496:             $rc = true;
497:             return $rc;
498:           }
499:    // else ...
500:           $this->cause = 'ERR: Could not create new session.';
501:         }
502:         else {
503:           $this->cause = 'ERR: Could not increment session sequence.';
504:         }
505:       }
506:       else {
507:         dbg_error_log( "Login", " LSIDLogin: $validation_string != $my_validation ($salt - $usr->user_no, $usr->username, $usr->password)");
508:         $client_messages[] = i18n('Invalid username or password.');
509:         if ( isset($c->dbg['Login']) || isset($c->dbg['ALL']) )
510:           $this->cause = 'WARN: Invalid password.';
511:         else
512:           $this->cause = 'WARN: Invalid username or password.';
513:       }
514:     }
515:     else {
516:     $client_messages[] = i18n('Invalid username or password.');
517:     if ( isset($c->dbg['Login']) || isset($c->dbg['ALL']) )
518:       $this->cause = 'WARN: Invalid username.';
519:     else
520:       $this->cause = 'WARN: Invalid username or password.';
521:     }
522: 
523:     dbg_error_log( "Login", " LSIDLogin: $this->cause" );
524:     return false;
525:   }
526: 
527: 
528: /**
529: * Renders some HTML for a basic login panel
530: *
531: * @return string The HTML to display a login panel.
532: */
533:   function RenderLoginPanel() {
534:     $action_target = htmlspecialchars(preg_replace('/\?logout.*$/','',$_SERVER['REQUEST_URI']));
535:     dbg_error_log( "Login", " RenderLoginPanel: action_target='%s'", $action_target );
536:     $userprompt = translate("User Name");
537:     $pwprompt = translate("Password");
538:     $rememberprompt = str_replace( ' ', '&nbsp;', translate("forget me not"));
539:     $gobutton = htmlspecialchars(translate("GO!"));
540:     $gotitle = htmlspecialchars(translate("Enter your username and password then click here to log in."));
541:     $temppwprompt = translate("If you have forgotten your password then");
542:     $temppwbutton = htmlspecialchars(translate("Help! I've forgotten my password!"));
543:     $temppwtitle = htmlspecialchars(translate("Enter a username, if you know it, and click here, to be e-mailed a temporary password."));
544:     $html = <<<EOTEXT
545: <div id="logon">
546: <form action="$action_target" method="post">
547: <table>
548: <tr>
549: <th class="prompt">$userprompt:</th>
550: <td class="entry">
551: <input class="text" type="text" name="username" size="12" /></td>
552: </tr>
553: <tr>
554: <th class="prompt">$pwprompt:</th>
555: <td class="entry">
556: <input class="password" type="password" name="password" size="12" />
557:  &nbsp;<label>$rememberprompt: <input class="checkbox" type="checkbox" name="remember" value="1" /></label>
558: </td>
559: </tr>
560: <tr>
561: <th class="prompt">&nbsp;</th>
562: <td class="entry">
563: <input type="submit" value="$gobutton" title="$gotitle" name="submit" class="submit" />
564: </td>
565: </tr>
566: </table>
567: <p>
568: $temppwprompt: <input type="submit" value="$temppwbutton" title="$temppwtitle" name="lostpass" class="submit" />
569: </p>
570: </form>
571: </div>
572: 
573: EOTEXT;
574:     return $html;
575:   }
576: 
577: 
578: /**
579: * Checks that this user is logged in, and presents a login screen if they aren't.
580: *
581: * The function can optionally confirm whether they are a member of one of a list
582: * of groups, and deny access if they are not a member of any of them.
583: *
584: * @param string $groups The list of groups that the user must be a member of one of to be allowed to proceed.
585: * @return boolean Whether or not the user is logged in and is a member of one of the required groups.
586: */
587:   function LoginRequired( $groups = "" ) {
588:     global $c, $session, $page_elements;
589: 
590:     if ( $this->logged_in && $groups == "" ) return;
591:     if ( ! $this->logged_in ) {
592: //      $c->messages[] = i18n("You must log in to use this system.");
593:       if ( function_exists("local_index_not_logged_in") ) {
594:         local_index_not_logged_in();
595:       }
596:       else {
597:         $login_html = translate( "<h1>Log On Please</h1><p>For access to the %s you should log on withthe username and password that have been issued to you.</p><p>If you would like to request access, please e-mail %s.</p>");
598:         $page_content = sprintf( $login_html, $c->system_name, $c->admin_email );
599:         $page_content .= $this->RenderLoginPanel();
600:         if ( isset($page_elements) && gettype($page_elements) == 'array' ) {
601:           $page_elements[] = $page_content;
602:           @include("page-renderer.php");
603:           exit(0);
604:         }
605:         @include("page-header.php");
606:         echo $page_content;
607:         @include("page-footer.php");
608:       }
609:     }
610:     else {
611:       $valid_groups = explode(",", $groups);
612:       foreach( $valid_groups AS $k => $v ) {
613:         if ( $this->AllowedTo($v) ) return;
614:       }
615:       $c->messages[] = i18n("You are not authorised to use this function.");
616:       if ( isset($page_elements) && gettype($page_elements) == 'array' ) {
617:         @include("page-renderer.php");
618:         exit(0);
619:       }
620:       @include("page-header.php");
621:       @include("page-footer.php");
622:     }
623: 
624:     exit;
625:   }
626: 
627: 
628: 
629: /**
630: * E-mails a temporary password in response to a request from a user.
631: *
632: * This could be called from somewhere within the application that allows
633: * someone to set up a user and invite them.
634: *
635: * This function includes EMail.php to actually send the password.
636: */
637:   function EmailTemporaryPassword( $username, $email_address, $body_template="" ) {
638:     global $c;
639: 
640:     $password_sent = false;
641:     $where = "";
642:     $params = array();
643:     if ( isset($username) && $username != "" ) {
644:       $where = 'WHERE active AND lower(usr.username) = :lcusername';
645:       $params[':lcusername'] = strtolower($username);
646:     }
647:     else if ( isset($email_address) && $email_address != "" ) {
648:       $where = 'WHERE active AND lower(usr.email) = :lcemail';
649:       $params[':lcemail'] = strtolower($email_address);
650:     }
651: 
652:     if ( $where != '' ) {
653:       if ( !isset($body_template) || $body_template == "" ) {
654:         $body_template = <<<EOTEXT
655: 
656: @@debugging@@A temporary password has been requested for @@system_name@@.
657: 
658: Temporary Password: @@password@@
659: 
660: This has been applied to the following usernames:
661: 
662: @@usernames@@
663: and will be valid for 24 hours.
664: 
665: If you have any problems, please contact the system administrator.
666: 
667: EOTEXT;
668:       }
669: 
670:       $qry = new AwlQuery( 'SELECT * FROM usr '.$where, $params );
671:       $qry->Exec('Session::EmailTemporaryPassword');
672:       if ( $qry->rows() > 0 ) {
673:         $q2 = new AwlQuery();
674:         $q2->Begin();
675: 
676:         while ( $row = $qry->Fetch() ) {
677:           $mail = new EMail( "Access to $c->system_name" );
678:           $mail->SetFrom($c->admin_email );
679:           $usernames = "";
680:           $debug_to = "";
681:           if ( isset($c->debug_email) ) {
682:             $debug_to = "This e-mail would normally be sent to:\n ";
683:             $mail->AddTo( "Tester <$c->debug_email>" );
684:           }
685: 
686:           $tmp_passwd = '';
687:           for ( $i=0; $i < 8; $i++ ) {
688:             $tmp_passwd .= substr( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ+#.-=*%@0123456789abcdefghijklmnopqrstuvwxyz', rand(0,69), 1);
689:           }
690: 
691:           $q2->QDo('INSERT INTO tmp_password (user_no, password) VALUES(?,?)', array($row->user_no, $tmp_passwd));
692:           if ( isset($c->debug_email) ) {
693:             $debug_to .= "$row->fullname <$row->email> ";
694:           }
695:           else {
696:             $mail->AddTo( "$row->fullname <$row->email>" );
697:           }
698:           $usernames .= "        $row->username\n";
699: 
700:           if ( $mail->To() != "" ) {
701:             if ( isset($c->debug_email) ) {
702:               $debug_to .= "\n============================================================\n";
703:             }
704:             $sql .= "COMMIT;";
705:             $qry = new AwlQuery( $sql );
706:             $qry->Exec("Session::SendTemporaryPassword");
707:             $body = str_replace( '@@system_name@@', $c->system_name, $body_template);
708:             $body = str_replace( '@@password@@', $tmp_passwd, $body);
709:             $body = str_replace( '@@usernames@@', $usernames, $body);
710:             $body = str_replace( '@@debugging@@', $debug_to, $body);
711:             $mail->SetBody($body);
712:             $mail->Send();
713:             $password_sent = true;
714:           }
715:         }
716:       }
717:     }
718:     return $password_sent;
719:   }
720: 
721: 
722: /**
723: * Sends a temporary password in response to a request from a user.
724: *
725: * This is probably only going to be called from somewhere internal.  An external
726: * caller will probably just want the e-mail, without the HTML that this displays.
727: *
728: */
729:   function SendTemporaryPassword( ) {
730:     global $c, $page_elements;
731: 
732:     $password_sent = $this->EmailTemporaryPassword( (isset($_POST['username'])?$_POST['username']:null), (isset($_POST['email_address'])?$_POST['email_address']:null) );
733: 
734:     if ( ! $password_sent && ((isset($_POST['username']) && $_POST['username'] != "" )
735:                               || (isset($_POST['email_address']) && $_POST['email_address'] != "" )) ) {
736:       // Username or EMail were non-null, but we didn't find that user.
737:       $page_content = <<<EOTEXT
738: <div id="logon">
739: <h1>Unable to Reset Password</h1>
740: <p>We were unable to reset your password at this time.  Please contact
741: <a href="mailto:$c->admin_email">$c->admin_email</a>
742: to arrange for an administrator to reset your password.</p>
743: <p>Thank you.</p>
744: </div>
745: EOTEXT;
746:     }
747:     else if ( $password_sent ) {
748:       $page_content = <<<EOTEXT
749: <div id="logon">
750: <h1>Temporary Password Sent</h1>
751: <p>A temporary password has been e-mailed to you.  This password
752: will be valid for 24 hours and you will be required to change
753: your password after logging in.</p>
754: <p><a href=".">Click here to return to the login page.</a></p>
755: </div>
756: EOTEXT;
757:     }
758:     else {
759:       $page_content = <<<EOTEXT
760: <div id="logon">
761: <h1>Temporary Password</h1>
762: <form action="$action_target" method="post">
763: <table>
764: <tr>
765: <th class="prompt" style="white-space: nowrap;">Enter your User Name:</th>
766: <td class="entry"><input class="text" type="text" name="username" size="12" /></td>
767: </tr>
768: <tr>
769: <th class="prompt" style="white-space: nowrap;">Or your EMail Address:</th>
770: <td class="entry"><input class="text" type="text" name="email_address" size="50" /></td>
771: </tr>
772: <tr>
773: <th class="prompt" style="white-space: nowrap;">and click on -></th>
774: <td class="entry">
775: <input class="submit" type="submit" value="Send me a temporary password" alt="Enter a username, or e-mail address, and click here." name="lostpass" />
776: </td>
777: </tr>
778: </table>
779: <p>Note: If you have multiple accounts with the same e-mail address, they will <em>all</em>
780: be assigned a new temporary password, but only the one(s) that you use that temporary password
781: on will have the existing password invalidated.</p>
782: <h2>The temporary password will only be valid for 24 hours.</h2>
783: <p>You will need to log on and change your password during this time.</p>
784: </form>
785: </div>
786: EOTEXT;
787:     }
788:     if ( isset($page_elements) && gettype($page_elements) == 'array' ) {
789:       $page_elements[] = $page_content;
790:       @include("page-renderer.php");
791:       exit(0);
792:     }
793:     @include("page-header.php");
794:     echo $page_content;
795:     @include("page-footer.php");
796:     exit(0);
797:   }
798: 
799:   static function _CheckLogout() {
800:     if ( isset($_GET['logout']) ) {
801:       dbg_error_log( "Login", ":_CheckLogout: Logging out");
802:       if ( isset($_COOKIE['sid']) ) {
803:         // clean up our session in the session table
804:         list( $session_id, $session_key ) = explode( ';', $_COOKIE['sid'], 2 );
805:         $sql = 'DELETE FROM session WHERE session_id = ? AND session_key = ?';
806:         $qry = new AwlQuery( $sql, $session_id, $session_key );
807:         $qry->Exec('Logout',__LINE__,__FILE__);
808:         // expire sessions older than 16 hours
809:         $sql = "DELETE FROM session WHERE session_end < current_timestamp - interval '16 hours'";
810:         $qry = new AwlQuery( $sql );
811:         $qry->Exec('Expire',__LINE__,__FILE__);
812:       }
813:       setcookie( 'sid', '', 0,'/');
814:       unset($_COOKIE['sid']);
815:       unset($GLOBALS['sid']);
816:       unset($_COOKIE['lsid']); // Allow a cookied person to be un-logged-in for one page view.
817:       unset($GLOBALS['lsid']);
818: 
819:       if ( isset($_GET['forget']) ) setcookie( 'lsid', '', 0,'/');
820:     }
821:   }
822: 
823:   function _CheckLogin() {
824:     global $c;
825:     if ( isset($_POST['lostpass']) ) {
826:       dbg_error_log( "Login", ":_CheckLogin: User '$_POST[username]' has lost the password." );
827:       $this->SendTemporaryPassword();
828:     }
829:     else if ( isset($_POST['username']) && isset($_POST['password']) ) {
830:       // Try and log in if we have a username and password
831:       $this->Login( $_POST['username'], $_POST['password'] );
832:       @dbg_error_log( "Login", ":_CheckLogin: User %s(%s) - %s (%d) login status is %d", $_POST['username'], $this->fullname, $this->user_no, $this->logged_in );
833:     }
834:     else if ( !isset($_COOKIE['sid']) && isset($_COOKIE['lsid']) && $_COOKIE['lsid'] != "" ) {
835:       // Validate long-term session details
836:       $this->LSIDLogin( $_COOKIE['lsid'] );
837:       dbg_error_log( "Login", ":_CheckLogin: User $this->username - $this->fullname ($this->user_no) login status is $this->logged_in" );
838:     }
839:     else if ( !isset($_COOKIE['sid']) && isset($c->authenticate_hook['server_auth_type']) ) {
840:       /**
841:       * The authentication has happened in the server, and we should accept it if so.
842:       */
843:       if ( ( is_array($c->authenticate_hook['server_auth_type'])
844:              && in_array( strtolower($_SERVER['AUTH_TYPE']), array_map('strtolower', $c->authenticate_hook['server_auth_type'])) )
845:       ||
846:            ( !is_array($c->authenticate_hook['server_auth_type'])
847:              && strtolower($c->authenticate_hook['server_auth_type']) == strtolower($_SERVER['AUTH_TYPE']) )
848:       ) {
849:         if (isset($_SERVER["REMOTE_USER"]))
850:           $this->Login($_SERVER['REMOTE_USER'], "", true);  // Password will not be checked.
851:         else
852:           $this->Login($_SERVER['REDIRECT_REMOTE_USER'], "", true);  // Password will not be checked.
853:       }
854:     }
855:   }
856: 
857: 
858:   /**
859:   * Function to reformat an ISO date to something nicer and possibly more localised
860:   * @param string $indate The ISO date to be formatted.
861:   * @param string $type If 'timestamp' then the time will also be shown.
862:   * @return string The nicely formatted date.
863:   */
864:   function FormattedDate( $indate, $type='date' ) {
865:     $out = "";
866:     if ( preg_match( '#^\s*$#', $indate ) ) {
867:       // Looks like it's empty - just return empty
868:       return $indate;
869:     }
870:     if ( preg_match( '#^\d{1,2}[/-]\d{1,2}[/-]\d{2,4}#', $indate ) ) {
871:       // Looks like it's nice already - don't screw with it!
872:       return $indate;
873:     }
874:     $yr = substr($indate,0,4);
875:     $mo = substr($indate,5,2);
876:     $dy = substr($indate,8,2);
877:     switch ( $this->date_format_type ) {
878:       case 'U':
879:         $out = sprintf( "%d/%d/%d", $mo, $dy, $yr );
880:         break;
881:       case 'E':
882:         $out = sprintf( "%d/%d/%d", $dy, $mo, $yr );
883:         break;
884:       default:
885:         $out = sprintf( "%d-%02d-%02d", $yr, $mo, $dy );
886:         break;
887:     }
888:     if ( $type == 'timestamp' ) {
889:       $out .= substr($indate,10,6);
890:     }
891:     return $out;
892:   }
893: 
894: 
895:   /**
896:   * Build a hash which we can use for confirmation that we didn't get e-mailed
897:   * a bogus link by someone, and that we actually got here by traversing the
898:   * website.
899:   *
900:   * @param string $method Either 'GET' or 'POST' depending on the way we will use this.
901:   * @param string $varname The name of the variable which we will confirm
902:   * @return string A string we can use as either a GET or POST value (i.e. a hidden field, or a varname=hash pair.
903:   */
904:   function BuildConfirmationHash( $method, $varname ) {
905:     /**
906:     * We include session_start in this because it is never passed to the client
907:     * and since it includes microseconds would be very hard to predict.
908:     */
909:     $confirmation_hash = session_salted_md5( $this->session_start.$varname.$this->session_key, "" );
910:     if ( $method == 'GET' ) {
911:       $confirm = $varname .'='. urlencode($confirmation_hash);
912:     }
913:     else {
914:       $confirm = sprintf( '<input type="hidden" name="%s" value="%s">', $varname, htmlspecialchars($confirmation_hash) );
915:     }
916:     return $confirm;
917:   }
918: 
919: 
920:   /**
921:   * Check a hash which we created through BuildConfirmationHash
922:   *
923:   * @param string $method Either 'GET' or 'POST' depending on the way we will use this.
924:   * @param string $varname The name of the variable which we will confirm
925:   * @return string A string we can use as either a GET or POST value (i.e. a hidden field, or a varname=hash pair.
926:   */
927:   function CheckConfirmationHash( $method, $varname ) {
928:     if ( $method == 'GET' && isset($_GET[$varname])) {
929:       $hashwegot = $_GET[$varname];
930:       dbg_error_log('Session',':CheckConfirmationHash: We got "%s" from GET', $hashwegot );
931:     }
932:     else if ( isset($_POST[$varname]) ) {
933:       $hashwegot = $_POST[$varname];
934:       dbg_error_log('Session',':CheckConfirmationHash: We got "%s" from POST', $hashwegot );
935:     }
936:     else {
937:       return false;
938:     }
939: 
940:     if ( preg_match('{^\*(.+)\*.+$}i', $hashwegot, $regs ) ) {
941:       // A nicely salted md5sum like "*<salt>*<salted_md5>"
942:       $salt = $regs[1];
943:       dbg_error_log('Session',':CheckConfirmationHash: Salt "%s"', $salt );
944:       $test_against = session_salted_md5( $this->session_start.$varname.$this->session_key, $salt ) ;
945:       dbg_error_log('Session',':CheckConfirmationHash: Testing against "%s"', $test_against );
946: 
947:       return ($hashwegot == $test_against);
948:     }
949:     return false;
950:   }
951: 
952: }
953: 
954: 
955: /**
956: * @global resource $session
957: * @name $session
958: * The session object is global.
959: */
960: 
961: if ( !isset($session) ) {
962:   Session::_CheckLogout();
963:   $session = new Session();
964:   $session->_CheckLogin();
965: }
966: 
967: 
AWL API documentation generated by ApiGen 2.8.0