CWIS Developer Documentation
User.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # User.php
4 # An Object for Handling User Information
5 #
6 # Copyright 1999-2001 Axis Data
7 # This code is free software that can be used or redistributed under the
8 # terms of Version 2 of the GNU General Public License, as published by the
9 # Free Software Foundation (http://www.fsf.org).
10 #
11 # Author: Edward Almasy (almasy@axisdata.com)
12 #
13 # Part of the AxisPHP library v1.2.4
14 # For more information see http://www.axisdata.com/AxisPHP/
15 #
16 
17 # status values (error codes)
18 define("U_OKAY", 0);
19 define("U_ERROR", 1);
20 define("U_BADPASSWORD", 2);
21 define("U_NOSUCHUSER", 3);
22 define("U_PASSWORDSDONTMATCH", 4);
23 define("U_EMAILSDONTMATCH", 5);
24 define("U_DUPLICATEUSERNAME", 6);
25 define("U_ILLEGALUSERNAME", 7);
26 define("U_EMPTYUSERNAME", 8);
27 define("U_ILLEGALPASSWORD", 9);
28 define("U_ILLEGALPASSWORDAGAIN", 10);
29 define("U_EMPTYPASSWORD", 11);
30 define("U_EMPTYPASSWORDAGAIN", 12);
31 define("U_ILLEGALEMAIL", 13);
32 define("U_ILLEGALEMAILAGAIN", 14);
33 define("U_EMPTYEMAIL", 15);
34 define("U_EMPTYEMAILAGAIN", 16);
35 define("U_NOTLOGGEDIN", 17);
36 define("U_MAILINGERROR", 18);
37 define("U_TEMPLATENOTFOUND", 19);
38 define("U_DUPLICATEEMAIL", 20);
39 define("U_NOTACTIVATED", 21);
40 define("U_PASSWORDCONTAINSUSERNAME", 22);
41 define("U_PASSWORDCONTAINSEMAIL", 23);
42 define("U_PASSWORDTOOSHORT", 24);
43 define("U_PASSWORDTOOSIMPLE", 25);
44 define("U_PASSWORDNEEDSPUNCTUATION", 26);
45 define("U_PASSWORDNEEDSMIXEDCASE", 27);
46 define("U_PASSWORDNEEDSDIGIT", 28);
47 
48 class User
49 {
50  # ---- CLASS CONSTANTS ---------------------------------------------------
53  const PW_REQUIRE_DIGITS = 4;
54 
55  # ---- PUBLIC INTERFACE --------------------------------------------------
56 
57  public function __construct($UserInfoOne = NULL, $UserInfoTwo = NULL)
58  {
59  # create database connection
60  $this->DB = new Database();
61 
62  # if we're looking up a user by UserId
63  if (is_numeric($UserInfoOne) || is_numeric($UserInfoTwo))
64  {
65  $UserId = is_numeric($UserInfoOne) ? $UserInfoOne : $UserInfoTwo;
66  $this->DB->Query("SELECT * FROM APUsers"
67  ." WHERE UserId='".intval($UserId)."'");
68  $Record = $this->DB->FetchRow();
69  }
70  # if we're looking up a user by name or email address
71  elseif (is_string($UserInfoOne) || is_string($UserInfoTwo))
72  {
73  $UserName = is_string($UserInfoOne) ? $UserInfoOne : $UserInfoTwo;
74  $this->DB->Query("SELECT * FROM APUsers"
75  ." WHERE UserName='".addslashes($UserName)."'");
76  $Record = $this->DB->FetchRow();
77 
78  if ($Record === FALSE)
79  {
80  $this->DB->Query("SELECT * FROM APUsers"
81  ." WHERE EMail='".addslashes(
82  self::NormalizeEMailAddress($UserName))."'");
83  $Record = $this->DB->FetchRow();
84  }
85  }
86  # if a UserId is available from the session
87  elseif (isset($_SESSION["APUserId"]))
88  {
89  $UserId = $_SESSION["APUserId"];
90  $this->DB->Query("SELECT * FROM APUsers"
91  ." WHERE UserId='".intval($UserId)."'");
92  $Record = $this->DB->FetchRow();
93  }
94  # otherwise, create an anonymous user
95  else
96  {
97  $Record = array(
98  "UserId" => NULL,
99  "LoggedIn" => FALSE);
100  }
101 
102  # if a record was found, load data from it
103  if ($Record !== FALSE)
104  {
105  $this->DBFields = $Record;
106  $this->UserId = $Record["UserId"];
107  $this->LoggedIn = $Record["LoggedIn"] ? TRUE : FALSE;
108  $this->Result = U_OKAY;
109  }
110  else
111  {
112  # otherwise, set code indicating no user found
113  $this->Result = U_NOSUCHUSER;
114  }
115  }
116 
117  public function Status()
118  {
119  return $this->Result;
120  }
121 
122  # return text message corresponding to current status code
123  public function StatusMessage()
124  {
125  return self::GetStatusMessageForCode($this->Result);
126  }
127 
133  public static function GetStatusMessageForCode($StatusCode)
134  {
135  $APUserStatusMessages = array(
136  U_OKAY => "The operation was successful.",
137  U_ERROR => "There has been an error.",
138  U_BADPASSWORD => "The password you entered was incorrect.",
139  U_NOSUCHUSER => "No such user name was found.",
141  "The new passwords you entered do not match.",
143  "The e-mail addresses you entered do not match.",
145  "The user name you requested is already in use.",
147  "The user name you requested is too short, too long, "
148  ."or contains illegal characters.",
150  "The new password you requested is not valid.",
151  U_ILLEGALEMAIL =>
152  "The e-mail address you entered appears to be invalid.",
153  U_NOTLOGGEDIN => "The user is not logged in.",
154  U_MAILINGERROR =>
155  "An error occurred while attempting to send e-mail. "
156  ."Please notify the system administrator.",
158  "An error occurred while attempting to generate e-mail. "
159  ."Please notify the system administrator.",
161  "The e-mail address you supplied already has an account "
162  ."associated with it.",
164  "The password you entered contains your username.",
166  "The password you entered contains your email address.",
167  U_EMPTYPASSWORD => "Passwords may not be empty.",
169  "Passwords must be at least ".self::$PasswordMinLength
170  ." characters long.",
172  "Passwords must have at least ".self::$PasswordMinUniqueChars
173  ." different characters.",
175  "Passwords must contain at least one punctuation character.",
177  "Passwords must contain a mixture of uppercase and "
178  ."lowercase letters",
180  "Passwords must contain at least one number.",
181  );
182 
183  return (isset($APUserStatusMessages[$StatusCode]) ?
184  $APUserStatusMessages[$StatusCode] :
185  "Unknown user status code: ".$StatusCode );
186  }
187 
188  public function Delete()
189  {
190  # clear priv list values
191  $this->DB->Query("DELETE FROM APUserPrivileges WHERE UserId = '"
192  .$this->UserId."'");
193 
194  # delete user record from database
195  $this->DB->Query("DELETE FROM APUsers WHERE UserId = '".$this->UserId."'");
196 
197  # report to caller that everything succeeded
198  $this->Result = U_OKAY;
199  return $this->Result;
200  }
201 
207  public static function SetEmailFunction($NewValue)
208  {
209  if (is_callable($NewValue))
210  {
211  self::$EmailFunc = $NewValue;
212  }
213  }
214 
215  # ---- Getting/Setting Values --------------------------------------------
216 
217  public function Id()
218  {
219  return $this->UserId;
220  }
221  public function Name()
222  {
223  return $this->Get("UserName");
224  }
225 
231  public function GetBestName()
232  {
233  $RealName = $this->Get("RealName");
234 
235  # the real name is available, so use it
236  if (strlen(trim($RealName)))
237  {
238  return $RealName;
239  }
240 
241  # the real name isn't available, so use the user name
242  return $this->Get("UserName");
243  }
244 
245  public function LastLocation($NewLocation = NULL)
246  {
247  # return NULL if not associated with a particular user
248  if ($this->UserId === NULL) { return NULL; }
249 
250  if ($NewLocation)
251  {
252  $this->DB->Query("UPDATE APUsers SET"
253  ." LastLocation = '".addslashes($NewLocation)."',"
254  ." LastActiveDate = NOW(),"
255  ." LastIPAddress = '".$_SERVER["REMOTE_ADDR"]."'"
256  ." WHERE UserId = '".addslashes($this->UserId)."'");
257  if (isset($this->DBFields))
258  {
259  $this->DBFields["LastLocation"] = $NewLocation;
260  $this->DBFields["LastActiveDate"] = date("Y-m-d H:i:s");
261  }
262  }
263  return $this->Get("LastLocation");
264  }
265  public function LastActiveDate()
266  {
267  return $this->Get("LastActiveDate");
268  }
269  public function LastIPAddress()
270  {
271  return $this->Get("LastIPAddress");
272  }
273 
274  # get value from specified field
275  public function Get($FieldName)
276  {
277  # return NULL if not associated with a particular user
278  if ($this->UserId === NULL) { return NULL; }
279 
280  return $this->UpdateValue($FieldName);
281  }
282 
283  # get value (formatted as a date) from specified field
284  public function GetDate($FieldName, $Format = "")
285  {
286  # return NULL if not associated with a particular user
287  if ($this->UserId === NULL) { return NULL; }
288 
289  # retrieve specified value from database
290  if (strlen($Format) > 0)
291  {
292  $this->DB->Query("SELECT DATE_FORMAT(`".addslashes($FieldName)
293  ."`, '".addslashes($Format)."') AS `".addslashes($FieldName)
294  ."` FROM APUsers WHERE UserId='".$this->UserId."'");
295  }
296  else
297  {
298  $this->DB->Query("SELECT `".addslashes($FieldName)."` FROM APUsers WHERE UserId='".$this->UserId."'");
299  }
300  $Record = $this->DB->FetchRow();
301 
302  # return value to caller
303  return $Record[$FieldName];
304  }
305 
306  # set value in specified field
307  public function Set($FieldName, $NewValue)
308  {
309  # return error if not associated with a particular user
310  if ($this->UserId === NULL) { return U_NOTLOGGEDIN; }
311 
312  # transform booleans to 0 or 1 for storage
313  if (is_bool($NewValue))
314  {
315  $NewValue = $NewValue ? 1 : 0;
316  }
317 
318 
319  $this->UpdateValue($FieldName, $NewValue);
320  $this->Result = U_OKAY;
321  return $this->Result;
322  }
323 
324 
325  # ---- Login Functions ---------------------------------------------------
326 
327  public function Login($UserName, $Password, $IgnorePassword = FALSE)
328  {
329  # if user not found in DB
330  $this->DB->Query("SELECT * FROM APUsers"
331  ." WHERE UserName = '"
332  .addslashes(self::NormalizeUserName($UserName))."'");
333  if ($this->DB->NumRowsSelected() < 1)
334  {
335  # result is no user by that name
336  $this->Result = U_NOSUCHUSER;
337  }
338  else
339  {
340  # if user account not yet activated
341  $Record = $this->DB->FetchRow();
342  if (!$Record["RegistrationConfirmed"])
343  {
344  # result is user registration not confirmed
345  $this->Result = U_NOTACTIVATED;
346  }
347  else
348  {
349  # grab password from DB
350  $StoredPassword = $Record["UserPassword"];
351 
352  if (isset($Password[0]) && $Password[0] == " ")
353  {
354  $Challenge = md5(date("Ymd").$_SERVER["REMOTE_ADDR"]);
355  $StoredPassword = md5( $Challenge . $StoredPassword );
356 
357  $EncryptedPassword = trim($Password);
358  }
359  else
360  {
361  # if supplied password matches encrypted password
362  $EncryptedPassword = crypt($Password, $StoredPassword);
363  }
364 
365  if (($EncryptedPassword == $StoredPassword) || $IgnorePassword)
366  {
367  # result is success
368  $this->Result = U_OKAY;
369 
370  # store user ID for session
371  $this->UserId = $Record["UserId"];
372  $_SESSION["APUserId"] = $this->UserId;
373 
374  # update last login date
375  $this->DB->Query("UPDATE APUsers SET LastLoginDate = NOW(),"
376  ." LoggedIn = '1'"
377  ." WHERE UserId = '".$this->UserId."'");
378 
379  # Check for old format hashes, and rehash if possible
380  if ($EncryptedPassword === $StoredPassword &&
381  substr($StoredPassword, 0, 3) !== "$1$" &&
382  $Password[0] !== " " &&
383  CRYPT_MD5 )
384  {
385  $NewPassword = crypt($Password, self::GetSaltForCrypt() );
386  $this->DB->Query(
387  "UPDATE APUsers SET UserPassword='"
388  .addslashes($NewPassword)."' "
389  ."WHERE UserId='".$this->UserId."'");
390  }
391 
392  # since self::DBFields might already have been set to false if
393  # the user wasn't logged in when this is called, populate it
394  # with user data so that a call to self::UpdateValue will be
395  # able to properly fetch the data associated with the user
396  $this->DBFields = $Record;
397 
398  # set flag to indicate we are logged in
399  $this->LoggedIn = TRUE;
400  }
401  else
402  {
403  # result is bad password
404  $this->Result = U_BADPASSWORD;
405  }
406  }
407  }
408 
409  # return result to caller
410  return $this->Result;
411  }
412 
413  # log this user out
414  public function Logout()
415  {
416  # clear user ID (if any) for session
417  unset($_SESSION["APUserId"]);
418 
419  # if user is marked as logged in
420  if ($this->IsLoggedIn())
421  {
422  # set flag to indicate user is no longer logged in
423  $this->LoggedIn = FALSE;
424 
425  # clear login flag in database
426  $this->DB->Query(
427  "UPDATE APUsers SET LoggedIn = '0' "
428  ."WHERE UserId='".$this->UserId."'");
429  }
430  }
431 
432  public function GetPasswordSalt($UserName)
433  {
434  $this->DB->Query(
435  "SELECT * FROM APUsers WHERE UserName = '"
436  .addslashes(self::NormalizeUserName($UserName))."'");
437 
438  if ($this->DB->NumRowsSelected() < 1)
439  {
440  # result is no user by that name, generate a fake salt
441  # to discourage user enumeration. Make it be an old-format
442  # crypt() salt so that it's harder.
443  $SaltString = $_SERVER["SERVER_ADDR"].$UserName;
444  $Result = substr(base64_encode(md5($SaltString)), 0, 2);
445  }
446  else
447  {
448  # grab password from DB
449  # Assumes that we used php's crypt() for the passowrd
450  # management stuff, and will need to be changed if we
451  # go to something else.
452  $Record = $this->DB->FetchRow();
453  $StoredPassword = $Record["UserPassword"];
454 
455  if (substr($StoredPassword, 0, 3) === "$1$")
456  {
457  $Result = substr($StoredPassword, 0, 12);
458  }
459  else
460  {
461  $Result = substr($StoredPassword, 0, 2);
462  }
463  }
464 
465  return $Result;
466  }
467 
473  public function IsLoggedIn()
474  {
475  if (!isset($this->LoggedIn))
476  {
477  $this->LoggedIn = $this->DB->Query("
478  SELECT LoggedIn FROM APUsers
479  WHERE UserId='".addslashes($this->UserId)."'",
480  "LoggedIn") ? TRUE : FALSE;
481  }
482  return $this->LoggedIn;
483  }
484 
490  public function IsNotLoggedIn()
491  {
492  return $this->IsLoggedIn() ? FALSE : TRUE;
493  }
494 
499  public function IsAnonymous()
500  {
501  return ($this->UserId === NULL) ? TRUE : FALSE;
502  }
503 
504 
505  # ---- Password Functions ------------------------------------------------
506 
507  # set new password (with checks against old password)
508 
515  public function ChangePassword($OldPassword, $NewPassword, $NewPasswordAgain)
516  {
517  # return error if not associated with a particular user
518  if ($this->UserId === NULL)
519  {
520  return U_NOTLOGGEDIN;
521  }
522 
523  # if old password is not correct
524  $StoredPassword = $this->DB->Query("SELECT UserPassword FROM APUsers"
525  ." WHERE UserId='".$this->UserId."'", "UserPassword");
526  $EncryptedPassword = crypt($OldPassword, $StoredPassword);
527  if ($EncryptedPassword != $StoredPassword)
528  {
529  # set status to indicate error
530  $this->Result = U_BADPASSWORD;
531  }
532  # else if both instances of new password do not match
533  elseif (self::NormalizePassword($NewPassword)
534  != self::NormalizePassword($NewPasswordAgain))
535  {
536  # set status to indicate error
537  $this->Result = U_PASSWORDSDONTMATCH;
538  }
539  # perform other validity checks
540  elseif (!self::IsValidPassword(
541  $NewPassword, $this->Get("UserName"), $this->Get("EMail")) )
542  {
543  # set status to indicate error
544  $this->Result = U_ILLEGALPASSWORD;
545  }
546  else
547  {
548  # set new password
549  $this->SetPassword($NewPassword);
550 
551  # set status to indicate password successfully changed
552  $this->Result = U_OKAY;
553  }
554 
555  # report to caller that everything succeeded
556  return $this->Result;
557  }
558 
559  # set new password
560  public function SetPassword($NewPassword)
561  {
562  # generate encrypted password
563  $EncryptedPassword = crypt(self::NormalizePassword($NewPassword),
564  self::GetSaltForCrypt() );
565 
566  # save encrypted password
567  $this->UpdateValue("UserPassword", $EncryptedPassword);
568  }
569 
570  public function SetEncryptedPassword($NewEncryptedPassword)
571  {
572  # save encrypted password
573  $this->UpdateValue("UserPassword", $NewEncryptedPassword);
574  }
575 
577  $UserName, $EMail, $EMailAgain,
578  $TemplateFile = "Axis--User--EMailTemplate.txt")
579  {
581  $UserName, $EMail, $EMailAgain, $TemplateFile);
582  }
583 
585  $UserName, $EMail, $EMailAgain,
586  $TemplateFile = "Axis--User--EMailTemplate.txt")
587  {
588  # load e-mail template from file (first line is subject)
589  $Template = file($TemplateFile, 1);
590  $EMailSubject = array_shift($Template);
591  $EMailBody = join("", $Template);
592 
594  $UserName, $EMail, $EMailAgain, $EMailSubject, $EMailBody);
595  }
596 
598  $UserName, $EMail, $EMailAgain, $EMailSubject, $EMailBody)
599  {
600  # make sure e-mail addresses match
601  if ($EMail != $EMailAgain)
602  {
603  $this->Result = U_EMAILSDONTMATCH;
604  return $this->Result;
605  }
606 
607  # make sure e-mail address looks valid
608  if ($this->IsValidLookingEMailAddress($EMail) == FALSE)
609  {
610  $this->Result = U_ILLEGALEMAIL;
611  return $this->Result;
612  }
613 
614  # generate random password
615  $Password = $this->GetRandomPassword();
616 
617  # attempt to create new user with password
618  $Result = $this->CreateNewUser($UserName, $Password, $Password);
619 
620  # if user creation failed
621  if ($Result != U_OKAY)
622  {
623  # report error result to caller
624  return $Result;
625  }
626  # else
627  else
628  {
629  # set e-mail address in user record
630  $this->Set("EMail", $EMail);
631 
632  # plug appropriate values into subject and body of e-mail message
633  $EMailSubject = str_replace("X-USERNAME-X", $UserName, $EMailSubject);
634  $EMailBody = str_replace("X-USERNAME-X", $UserName, $EMailBody);
635  $EMailBody = str_replace("X-PASSWORD-X", $Password, $EMailBody);
636 
637  # send out e-mail message with new account info
638  if (is_Callable(self::$EmailFunc))
639  {
640  $Result = call_user_func(self::$EmailFunc,
641  $EMail, $EMailSubject, $EMailBody,
642  "Auto-Submitted: auto-generated");
643  }
644  else
645  {
646  $Result = mail($EMail, $EMailSubject, $EMailBody,
647  "Auto-Submitted: auto-generated");
648  }
649 
650  # if mailing attempt failed
651  if ($Result != TRUE)
652  {
653  # report error to caller
654  $this->Result = U_MAILINGERROR;
655  return $this->Result;
656  }
657  # else
658  else
659  {
660  # report success to caller
661  $this->Result = U_OKAY;
662  return $this->Result;
663  }
664  }
665  }
666 
667  # get code for user to submit to confirm registration
668  public function GetActivationCode()
669  {
670  # code is MD5 sum based on user name and encrypted password
671  $ActivationCodeLength = 6;
672  return $this->GetUniqueCode("Activation", $ActivationCodeLength);
673  }
674 
675  # check whether confirmation code is valid
676  public function IsActivationCodeGood($Code)
677  {
678  return (strtoupper(trim($Code)) == $this->GetActivationCode())
679  ? TRUE : FALSE;
680  }
681 
682  # get/set whether user registration has been confirmed
683  public function IsActivated($NewValue = DB_NOVALUE)
684  {
685  return $this->UpdateValue("RegistrationConfirmed", $NewValue);
686  }
687 
688  # get code for user to submit to confirm password reset
689  public function GetResetCode()
690  {
691  # code is MD5 sum based on user name and encrypted password
692  $ResetCodeLength = 10;
693  return $this->GetUniqueCode("Reset", $ResetCodeLength);
694  }
695 
696  # check whether password reset code is valid
697  public function IsResetCodeGood($Code)
698  {
699  return (strtoupper(trim($Code)) == $this->GetResetCode())
700  ? TRUE : FALSE;
701  }
702 
703  # get code for user to submit to confirm mail change request
704  public function GetMailChangeCode()
705  {
706  $ResetCodeLength = 10;
707  return $this->GetUniqueCode("MailChange".$this->Get("EMail")
708  .$this->Get("EMailNew"),
709  $ResetCodeLength);
710  }
711 
712  public function IsMailChangeCodeGood($Code)
713  {
714  return (strtoupper(trim($Code)) == $this->GetMailChangeCode())
715  ? TRUE : FALSE;
716  }
717 
718  # send e-mail to user (returns TRUE on success)
719  public function SendEMail(
720  $TemplateTextOrFileName, $FromAddress = NULL, $MoreSubstitutions = NULL,
721  $ToAddress = NULL)
722  {
723  # if template is file name
724  if (@is_file($TemplateTextOrFileName))
725  {
726  # load in template from file
727  $Template = file($TemplateTextOrFileName, 1);
728 
729  # report error to caller if template load failed
730  if ($Template == FALSE)
731  {
732  $this->Status = U_TEMPLATENOTFOUND;
733  return $this->Status;
734  }
735 
736  # join into one text block
737  $TemplateTextOrFileName = join("", $Template);
738  }
739 
740  # split template into lines
741  $Template = explode("\n", $TemplateTextOrFileName);
742 
743  # strip any comments out of template
744  $FilteredTemplate = array();
745  foreach ($Template as $Line)
746  {
747  if (!preg_match("/^[\\s]*#/", $Line))
748  {
749  $FilteredTemplate[] = $Line;
750  }
751  }
752 
753  # split subject line out of template (first non-comment line in file)
754  $EMailSubject = array_shift($FilteredTemplate);
755  $EMailBody = join("\n", $FilteredTemplate);
756 
757  # set up our substitutions
758  $Substitutions = array(
759  "X-USERNAME-X" => $this->Get("UserName"),
760  "X-EMAILADDRESS-X" => $this->Get("EMail"),
761  "X-ACTIVATIONCODE-X" => $this->GetActivationCode(),
762  "X-RESETCODE-X" => $this->GetResetCode(),
763  "X-CHANGECODE-X" => $this->GetMailChangeCode(),
764  "X-IPADDRESS-X" => @$_SERVER["REMOTE_ADDR"],
765  );
766 
767  # if caller provided additional substitutions
768  if (is_array($MoreSubstitutions))
769  {
770  # add in entries from caller to substitution list
771  $Substitutions = array_merge(
772  $Substitutions, $MoreSubstitutions);
773  }
774 
775  # perform substitutions on subject and body of message
776  $EMailSubject = str_replace(array_keys($Substitutions),
777  array_values($Substitutions), $EMailSubject);
778  $EMailBody = str_replace(array_keys($Substitutions),
779  array_values($Substitutions), $EMailBody);
780 
781  $AdditionalHeaders = "Auto-Submitted: auto-generated";
782 
783  # if caller provided "From" address
784  if ($FromAddress)
785  {
786  # prepend "From" address onto message
787  $AdditionalHeaders .= "\r\nFrom: ".$FromAddress;
788  }
789 
790  # send out mail message
791  if (is_Callable(self::$EmailFunc))
792  {
793  $Result = call_user_func(self::$EmailFunc,
794  is_null($ToAddress)?$this->Get("EMail"):$ToAddress,
795  $EMailSubject, $EMailBody, $AdditionalHeaders);
796  }
797  else
798  {
799  $Result = mail(is_null($ToAddress)?$this->Get("EMail"):$ToAddress,
800  $EMailSubject,
801  $EMailBody, $AdditionalHeaders);
802  }
803 
804  # report result of mailing attempt to caller
805  $this->Status = ($Result == TRUE) ? U_OKAY : U_MAILINGERROR;
806  return ($this->Status == U_OKAY);
807  }
808 
809 
810  # ---- Privilege Functions -----------------------------------------------
811 
820  public function HasPriv($Privilege, $Privileges = NULL)
821  {
822  # return FALSE if not associated with a particular user
823  if ($this->UserId === NULL) { return FALSE; }
824 
825  # bail out if empty array of privileges passed in
826  if (is_array($Privilege) && !count($Privilege) && (func_num_args() < 2))
827  { return FALSE; }
828 
829  # set up beginning of database query
830  $Query = "SELECT COUNT(*) AS PrivCount FROM APUserPrivileges "
831  ."WHERE UserId='".$this->UserId."' AND (";
832 
833  # add first privilege(s) to query (first arg may be single value or array)
834  if (is_array($Privilege))
835  {
836  $Sep = "";
837  foreach ($Privilege as $Priv)
838  {
839  $Query .= $Sep."Privilege='".addslashes($Priv)."'";
840  $Sep = " OR ";
841  }
842  }
843  else
844  {
845  $Query .= "Privilege='".$Privilege."'";
846  $Sep = " OR ";
847  }
848 
849  # add any privileges from additional args to query
850  $Args = func_get_args();
851  array_shift($Args);
852  foreach ($Args as $Arg)
853  {
854  $Query .= $Sep."Privilege='".$Arg."'";
855  $Sep = " OR ";
856  }
857 
858  # close out query
859  $Query .= ")";
860 
861  # look for privilege in database
862  $PrivCount = $this->DB->Query($Query, "PrivCount");
863 
864  # return value to caller
865  return ($PrivCount > 0) ? TRUE : FALSE;
866  }
867 
876  public static function GetSqlQueryForUsersWithPriv($Privilege, $Privileges = NULL)
877  {
878  # set up beginning of database query
879  $Query = "SELECT DISTINCT UserId FROM APUserPrivileges "
880  ."WHERE ";
881 
882  # add first privilege(s) to query (first arg may be single value or array)
883  if (is_array($Privilege))
884  {
885  $Sep = "";
886  foreach ($Privilege as $Priv)
887  {
888  $Query .= $Sep."Privilege='".addslashes($Priv)."'";
889  $Sep = " OR ";
890  }
891  }
892  else
893  {
894  $Query .= "Privilege='".$Privilege."'";
895  $Sep = " OR ";
896  }
897 
898  # add any privileges from additional args to query
899  $Args = func_get_args();
900  array_shift($Args);
901  foreach ($Args as $Arg)
902  {
903  $Query .= $Sep."Privilege='".$Arg."'";
904  $Sep = " OR ";
905  }
906 
907  # return query to caller
908  return $Query;
909  }
910 
919  public static function GetSqlQueryForUsersWithoutPriv($Privilege, $Privileges = NULL)
920  {
921  # set up beginning of database query
922  $Query = "SELECT DISTINCT UserId FROM APUserPrivileges "
923  ."WHERE ";
924 
925  # add first privilege(s) to query (first arg may be single value or array)
926  if (is_array($Privilege))
927  {
928  $Sep = "";
929  foreach ($Privilege as $Priv)
930  {
931  $Query .= $Sep."Privilege != '".addslashes($Priv)."'";
932  $Sep = " AND ";
933  }
934  }
935  else
936  {
937  $Query .= "Privilege != '".$Privilege."'";
938  $Sep = " AND ";
939  }
940 
941  # add any privileges from additional args to query
942  $Args = func_get_args();
943  array_shift($Args);
944  foreach ($Args as $Arg)
945  {
946  $Query .= $Sep."Privilege != '".$Arg."'";
947  $Sep = " AND ";
948  }
949 
950  # return query to caller
951  return $Query;
952  }
953 
954  public function GrantPriv($Privilege)
955  {
956  # return error if not associated with a particular user
957  if ($this->UserId === NULL) { return U_NOTLOGGEDIN; }
958 
959  # if privilege value is invalid
960  if (intval($Privilege) != trim($Privilege))
961  {
962  # set code to indicate error
963  $this->Result = U_ERROR;
964  }
965  else
966  {
967  # if user does not already have privilege
968  $PrivCount = $this->DB->Query("SELECT COUNT(*) AS PrivCount"
969  ." FROM APUserPrivileges"
970  ." WHERE UserId='".$this->UserId."'"
971  ." AND Privilege='".$Privilege."'",
972  "PrivCount");
973  if ($PrivCount == 0)
974  {
975  # add privilege for this user to database
976  $this->DB->Query("INSERT INTO APUserPrivileges"
977  ." (UserId, Privilege) VALUES"
978  ." ('".$this->UserId."', ".$Privilege.")");
979  }
980 
981  # set code to indicate success
982  $this->Result = U_OKAY;
983  }
984 
985  # report result to caller
986  return $this->Result;
987  }
988 
989  public function RevokePriv($Privilege)
990  {
991  # return error if not associated with a particular user
992  if ($this->UserId === NULL) { return U_NOTLOGGEDIN; }
993 
994  # remove privilege from database (if present)
995  $this->DB->Query("DELETE FROM APUserPrivileges"
996  ." WHERE UserId = '".$this->UserId."'"
997  ." AND Privilege = '".$Privilege."'");
998 
999  # report success to caller
1000  $this->Result = U_OKAY;
1001  return $this->Result;
1002  }
1003 
1004  public function GetPrivList()
1005  {
1006  # return empty list if not associated with a particular user
1007  if ($this->UserId === NULL) { return array(); }
1008 
1009  # read privileges from database and return array to caller
1010  $this->DB->Query("SELECT Privilege FROM APUserPrivileges"
1011  ." WHERE UserId='".$this->UserId."'");
1012  return $this->DB->FetchColumn("Privilege");
1013  }
1014 
1015  public function SetPrivList($NewPrivileges)
1016  {
1017  # return error if not associated with a particular user
1018  if ($this->UserId === NULL) { return U_NOTLOGGEDIN; }
1019 
1020  # clear old priv list values
1021  $this->DB->Query("DELETE FROM APUserPrivileges"
1022  ." WHERE UserId='".$this->UserId."'");
1023 
1024  # for each priv value passed in
1025  foreach ($NewPrivileges as $Privilege)
1026  {
1027  # set priv for user
1028  $this->GrantPriv($Privilege);
1029  }
1030  }
1031 
1038  public static function GetAnonymousUser()
1039  {
1040  # if we have a UserId in the session, move it aside
1041  if (isset($_SESSION["APUserId"]))
1042  {
1043  $OldUserId = $_SESSION["APUserId"];
1044  unset($_SESSION["APUserId"]);
1045  }
1046 
1047  # create a new anonymous user
1048  $CalledClass = get_called_class();
1049 
1050  $Result = new $CalledClass();
1051 
1052  # restore the $_SESSION value
1053  if (isset($OldUserId))
1054  {
1055  $_SESSION["APUserId"] = $OldUserId;
1056  }
1057 
1058  # return our anonymous user
1059  return $Result;
1060  }
1061 
1062  # ---- Miscellaneous Functions -------------------------------------------
1063 
1064  # get unique alphanumeric code for user
1065  public function GetUniqueCode($SeedString, $CodeLength)
1066  {
1067  # return NULL if not associated with a particular user
1068  if ($this->UserId === NULL) { return NULL; }
1069 
1070  return substr(strtoupper(md5(
1071  $this->Get("UserName").$this->Get("UserPassword").$SeedString)),
1072  0, $CodeLength);
1073  }
1074 
1075 
1076  # ---- PRIVATE INTERFACE -------------------------------------------------
1077 
1078  protected $DB; # handle to SQL database we use to store user information
1079  protected $UserId = NULL; # user ID number for reference into database
1080  protected $Result; # result of last operation
1081  protected $LoggedIn; # flag indicating whether user is logged in
1082  private $DBFields; # used for caching user values
1083 
1084  # optional mail function to use instead of mail()
1085  private static $EmailFunc = NULL;
1086 
1087  # check whether a user name is valid (alphanumeric string of 2-24 chars)
1088  public static function IsValidUserName($UserName)
1089  {
1090  if (preg_match("/^[a-zA-Z0-9]{2,24}$/", $UserName))
1091  {
1092  return TRUE;
1093  }
1094  else
1095  {
1096  return FALSE;
1097  }
1098  }
1099 
1100  # check whether a password is valid (at least 6 characters)
1101  public static function IsValidPassword(
1102  $Password, $UserName, $Email)
1103  {
1104  return count(self::CheckPasswordForErrors(
1105  $Password, $UserName, $Email)) == 0 ?
1106  TRUE : FALSE ;
1107  }
1108 
1118  public static function CheckPasswordForErrors(
1119  $Password, $UserName = NULL, $Email = NULL)
1120  {
1121  # start off assuming no errors
1122  $Errors = array();
1123 
1124  # normalize incoming password
1125  $Password = self::NormalizePassword($Password);
1126 
1127  # username provided and password contains username
1128  if ($UserName !== NULL &&
1129  stripos($Password, $UserName) !== FALSE)
1130  {
1131  $Errors[]= U_PASSWORDCONTAINSUSERNAME;
1132  }
1133 
1134  # email provided and password contains email
1135  if ($Email !== NULL &&
1136  stripos($Password, $Email) !== FALSE)
1137  {
1138  $Errors[]= U_PASSWORDCONTAINSEMAIL;
1139  }
1140 
1141  # length requirement
1142  if (strlen($Password) == 0)
1143  {
1144  $Errors[]= U_EMPTYPASSWORD;
1145  }
1146  elseif (strlen($Password) < self::$PasswordMinLength)
1147  {
1148  $Errors[]= U_PASSWORDTOOSHORT;
1149  }
1150 
1151  # unique characters requirement
1152  $UniqueChars = count(array_unique(
1153  preg_split('//u', $Password, NULL, PREG_SPLIT_NO_EMPTY)));
1154 
1155  if ($UniqueChars < self::$PasswordMinUniqueChars)
1156  {
1157  $Errors[]= U_PASSWORDTOOSIMPLE;
1158  }
1159 
1160  # for the following complexity checks, use unicode character properties
1161  # in PCRE as in: http://php.net/manual/en/regexp.reference.unicode.php
1162 
1163  # check for punctuation, uppercase letters, and numbers as per the system
1164  # configuration
1165  if (self::$PasswordRules & self::PW_REQUIRE_PUNCTUATION &&
1166  !preg_match('/\p{P}/u', $Password) )
1167  {
1168  $Errors[]= U_PASSWORDNEEDSPUNCTUATION;
1169  }
1170 
1171  if (self::$PasswordRules & self::PW_REQUIRE_MIXEDCASE &&
1172  (!preg_match('/\p{Lu}/u', $Password) ||
1173  !preg_match('/\p{Ll}/u', $Password) ) )
1174 
1175  {
1176  $Errors[]= U_PASSWORDNEEDSMIXEDCASE;
1177  }
1178 
1179  if (self::$PasswordRules & self::PW_REQUIRE_DIGITS &&
1180  !preg_match('/\p{N}/u', $Password))
1181  {
1182  $Errors[]= U_PASSWORDNEEDSDIGIT;
1183  }
1184 
1185  return $Errors;
1186  }
1187 
1188  # check whether an e-mail address looks valid
1189  public static function IsValidLookingEMailAddress($EMail)
1190  {
1191  if (preg_match("/^[a-zA-Z0-9._\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]{2,3}$/",
1192  $EMail))
1193  {
1194  return TRUE;
1195  }
1196  else
1197  {
1198  return FALSE;
1199  }
1200  }
1201 
1202  # get normalized version of e-mail address
1203  public static function NormalizeEMailAddress($EMailAddress)
1204  {
1205  return strtolower(trim($EMailAddress));
1206  }
1207 
1208  # get normalized version of user name
1209  public static function NormalizeUserName($UserName)
1210  {
1211  return trim($UserName);
1212  }
1213 
1214  # get normalized version of password
1215  public static function NormalizePassword($Password)
1216  {
1217  return trim($Password);
1218  }
1219 
1220  # generate random password
1221  public function GetRandomPassword($PasswordMinLength = 6, $PasswordMaxLength = 8)
1222  {
1223  # seed random number generator
1224  mt_srand((double)microtime() * 1000000);
1225 
1226  # generate password of requested length
1227  return sprintf("%06d", mt_rand(pow(10, ($PasswordMinLength - 1)),
1228  (pow(10, $PasswordMaxLength) - 1)));
1229  }
1230 
1231  # convenience function to supply parameters to Database->UpdateValue()
1232  public function UpdateValue($FieldName, $NewValue = DB_NOVALUE)
1233  {
1234  return $this->DB->UpdateValue("APUsers", $FieldName, $NewValue,
1235  "UserId = '".$this->UserId."'", $this->DBFields);
1236  }
1237 
1238  # methods for backward compatibility with earlier versions of User
1239  public function GivePriv($Privilege)
1240  {
1241  $this->GrantPriv($Privilege);
1242  }
1243 
1249  public static function SetPasswordRules($NewValue)
1250  {
1251  self::$PasswordRules = $NewValue;
1252  }
1253 
1258  public static function SetPasswordMinLength($NewValue)
1259  {
1260  self::$PasswordMinLength = $NewValue;
1261  }
1262 
1267  public static function SetPasswordMinUniqueChars($NewValue)
1268  {
1269  self::$PasswordMinUniqueChars = $NewValue;
1270  }
1271 
1276  public static function GetPasswordRulesDescription()
1277  {
1278  return "Passwords are case-sensitive, cannot contain your username or email, "
1279  ."must be at least ".self::$PasswordMinLength
1280  ." characters long, "
1281  ." have at least ".self::$PasswordMinUniqueChars
1282  ." different characters"
1283  .(self::$PasswordRules & self::PW_REQUIRE_PUNCTUATION ?
1284  ", include punctuation":"")
1285  .(self::$PasswordRules & self::PW_REQUIRE_MIXEDCASE ?
1286  ", include capital and lowercase letters":"")
1287  .(self::$PasswordRules & self::PW_REQUIRE_DIGITS ?
1288  ", include a number":"").".";
1289  }
1290 
1294  private static function GetSaltForCrypt()
1295  {
1296  # generate a password salt by grabbing CRYPT_SALT_LENGTH
1297  # random bytes, then base64 encoding while filtering out
1298  # non-alphanumeric characters to get a string all the hashes
1299  # accept as a salt
1300  $Salt = preg_replace("/[^A-Za-z0-9]/","",
1301  base64_encode(openssl_random_pseudo_bytes(
1302  CRYPT_SALT_LENGTH) ));
1303 
1304  # select the best available hashing algorithm, provide a salt
1305  # in the correct format for that algorithm
1306  if (CRYPT_SHA512==1)
1307  {
1308  return '$6$'.substr($Salt, 0, 16);
1309  }
1310  elseif (CRYPT_SHA256==1)
1311  {
1312  return '$5$'.substr($Salt, 0, 16);
1313  }
1314  elseif (CRYPT_BLOWFISH==1)
1315  {
1316  return '$2y$'.substr($Salt, 0, 22);
1317  }
1318  elseif (CRYPT_MD5==1)
1319  {
1320  return '$1$'.substr($Salt, 0, 12);
1321  }
1322  elseif (CRYPT_EXT_DES==1)
1323  {
1324  return '_'.substr($Salt, 0, 8);
1325  }
1326  else
1327  {
1328  return substr($Salt, 0, 2);
1329  }
1330  }
1331 
1332  private static $PasswordMinLength = 6;
1333  private static $PasswordMinUniqueChars = 4;
1334 
1335  # default to no additional requirements beyond length
1336  private static $PasswordRules = 0;
1337 }
Get($FieldName)
Definition: User.php:275
$DB
Definition: User.php:1078
GetRandomPassword($PasswordMinLength=6, $PasswordMaxLength=8)
Definition: User.php:1221
static GetAnonymousUser()
Get the anonymous user (i.e., the User object that exists when no user is logged in), useful when a permission check needs to know if something should be visible to the general public.
Definition: User.php:1038
static NormalizeUserName($UserName)
Definition: User.php:1209
const U_TEMPLATENOTFOUND
Definition: User.php:37
IsLoggedIn()
Report whether user is currently logged in.
Definition: User.php:473
static IsValidLookingEMailAddress($EMail)
Definition: User.php:1189
static SetPasswordMinLength($NewValue)
Set password minimum length.
Definition: User.php:1258
GetMailChangeCode()
Definition: User.php:704
GetUniqueCode($SeedString, $CodeLength)
Definition: User.php:1065
GivePriv($Privilege)
Definition: User.php:1239
__construct($UserInfoOne=NULL, $UserInfoTwo=NULL)
Definition: User.php:57
GrantPriv($Privilege)
Definition: User.php:954
SQL database abstraction object with smart query caching.
Definition: Database.php:22
static CheckPasswordForErrors($Password, $UserName=NULL, $Email=NULL)
Determine if a provided password complies with the configured rules, optionally checking that it does...
Definition: User.php:1118
IsMailChangeCodeGood($Code)
Definition: User.php:712
const U_EMAILSDONTMATCH
Definition: User.php:23
GetResetCode()
Definition: User.php:689
UpdateValue($FieldName, $NewValue=DB_NOVALUE)
Definition: User.php:1232
static NormalizePassword($Password)
Definition: User.php:1215
CreateNewUserAndMailPassword($UserName, $EMail, $EMailAgain, $EMailSubject, $EMailBody)
Definition: User.php:597
const U_EMPTYPASSWORD
Definition: User.php:29
const U_PASSWORDNEEDSPUNCTUATION
Definition: User.php:44
GetActivationCode()
Definition: User.php:668
const U_ERROR
Definition: User.php:19
const U_PASSWORDNEEDSDIGIT
Definition: User.php:46
const U_NOTLOGGEDIN
Definition: User.php:35
const U_BADPASSWORD
Definition: User.php:20
const U_ILLEGALEMAIL
Definition: User.php:31
Login($UserName, $Password, $IgnorePassword=FALSE)
Definition: User.php:327
Definition: User.php:48
SetEncryptedPassword($NewEncryptedPassword)
Definition: User.php:570
const U_PASSWORDCONTAINSEMAIL
Definition: User.php:41
Delete()
Definition: User.php:188
IsResetCodeGood($Code)
Definition: User.php:697
const PW_REQUIRE_MIXEDCASE
Definition: User.php:52
static IsValidUserName($UserName)
Definition: User.php:1088
static GetSqlQueryForUsersWithPriv($Privilege, $Privileges=NULL)
Get an SQL query that will return IDs of all users that have the specified privilege flags...
Definition: User.php:876
GetPasswordSalt($UserName)
Definition: User.php:432
LastLocation($NewLocation=NULL)
Definition: User.php:245
static SetPasswordMinUniqueChars($NewValue)
Set password minimum unique characters.
Definition: User.php:1267
GetPrivList()
Definition: User.php:1004
static SetPasswordRules($NewValue)
Set password requirements.
Definition: User.php:1249
IsActivated($NewValue=DB_NOVALUE)
Definition: User.php:683
static GetStatusMessageForCode($StatusCode)
Get text error message for a specified error code.
Definition: User.php:133
const PW_REQUIRE_PUNCTUATION
Definition: User.php:51
HasPriv($Privilege, $Privileges=NULL)
Check whether user has specified privilege(s).
Definition: User.php:820
const U_PASSWORDNEEDSMIXEDCASE
Definition: User.php:45
$LoggedIn
Definition: User.php:1081
GetBestName()
Get the best available name associated with a user, i.e., the real name or, if it isn&#39;t available...
Definition: User.php:231
SendEMail($TemplateTextOrFileName, $FromAddress=NULL, $MoreSubstitutions=NULL, $ToAddress=NULL)
Definition: User.php:719
const U_PASSWORDCONTAINSUSERNAME
Definition: User.php:40
const U_PASSWORDTOOSIMPLE
Definition: User.php:43
Set($FieldName, $NewValue)
Definition: User.php:307
const DB_NOVALUE
Definition: Database.php:1706
const U_MAILINGERROR
Definition: User.php:36
IsAnonymous()
Report whether user is anonymous user.
Definition: User.php:499
static GetPasswordRulesDescription()
Get a string describing the password rules.
Definition: User.php:1276
const U_DUPLICATEUSERNAME
Definition: User.php:24
static SetEmailFunction($NewValue)
Set email function to use instead of mail().
Definition: User.php:207
const U_OKAY
Definition: User.php:18
static IsValidPassword($Password, $UserName, $Email)
Definition: User.php:1101
static NormalizeEMailAddress($EMailAddress)
Definition: User.php:1203
GetDate($FieldName, $Format="")
Definition: User.php:284
const U_DUPLICATEEMAIL
Definition: User.php:38
CreateNewUserWithEMailedPassword($UserName, $EMail, $EMailAgain, $TemplateFile="Axis--User--EMailTemplate.txt")
Definition: User.php:576
Status()
Definition: User.php:117
IsNotLoggedIn()
Report whether user is not currently logged in.
Definition: User.php:490
Logout()
Definition: User.php:414
LastActiveDate()
Definition: User.php:265
$Result
Definition: User.php:1080
CreateNewUserAndMailPasswordFromFile($UserName, $EMail, $EMailAgain, $TemplateFile="Axis--User--EMailTemplate.txt")
Definition: User.php:584
LastIPAddress()
Definition: User.php:269
const U_NOSUCHUSER
Definition: User.php:21
Id()
Definition: User.php:217
SetPassword($NewPassword)
Definition: User.php:560
const U_NOTACTIVATED
Definition: User.php:39
$UserId
Definition: User.php:1079
SetPrivList($NewPrivileges)
Definition: User.php:1015
ChangePassword($OldPassword, $NewPassword, $NewPasswordAgain)
Check provided password and set a new one if it war correct.
Definition: User.php:515
IsActivationCodeGood($Code)
Definition: User.php:676
const U_ILLEGALUSERNAME
Definition: User.php:25
RevokePriv($Privilege)
Definition: User.php:989
const U_PASSWORDTOOSHORT
Definition: User.php:42
Name()
Definition: User.php:221
const PW_REQUIRE_DIGITS
Definition: User.php:53
const U_ILLEGALPASSWORD
Definition: User.php:27
static GetSqlQueryForUsersWithoutPriv($Privilege, $Privileges=NULL)
Get an SQL query that will return IDs of all users that do not have the specified privilege flags...
Definition: User.php:919
StatusMessage()
Definition: User.php:123
const U_PASSWORDSDONTMATCH
Definition: User.php:22