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