blob: 14c33435e5a97956d919ff9544e63df21855d76c [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3namespace Adldap\Models;
4
5use DateTime;
6use Adldap\Utilities;
7use Adldap\AdldapException;
8use Adldap\Schemas\ActiveDirectory;
9use Adldap\Models\Attributes\AccountControl;
10use Adldap\Models\Attributes\TSPropertyArray;
11use Illuminate\Contracts\Auth\Authenticatable;
12
13/**
14 * Class User.
15 *
16 * Represents an LDAP user.
17 */
18class User extends Entry implements Authenticatable
19{
20 use Concerns\HasUserProperties;
21 use Concerns\HasDescription;
22 use Concerns\HasMemberOf;
23 use Concerns\HasLastLogonAndLogOff;
24 use Concerns\HasUserAccountControl;
25
26 /** @var callable|null */
27 private static $passwordStrategy;
28
29 /**
30 * Password will be processed using given callback before saving.
31 *
32 * @param callable $strategy
33 */
34 public static function usePasswordStrategy(callable $strategy)
35 {
36 static::$passwordStrategy = $strategy;
37 }
38
39 /**
40 * Will return user set password strategy or default one.
41 *
42 * @return callable
43 */
44 public static function getPasswordStrategy(): callable
45 {
46 return static::$passwordStrategy ?? function ($password) {
47 return Utilities::encodePassword($password);
48 };
49 }
50
51 /**
52 * Get the name of the unique identifier for the user.
53 *
54 * @return string
55 */
56 public function getAuthIdentifierName()
57 {
58 return $this->schema->objectGuid();
59 }
60
61 /**
62 * Get the unique identifier for the user.
63 *
64 * @return mixed
65 */
66 public function getAuthIdentifier()
67 {
68 return $this->getConvertedGuid();
69 }
70
71 /**
72 * Get the password for the user.
73 *
74 * @return string
75 */
76 public function getAuthPassword()
77 {
78 }
79
80 /**
81 * Get the token value for the "remember me" session.
82 *
83 * @return string
84 */
85 public function getRememberToken()
86 {
87 }
88
89 /**
90 * Set the token value for the "remember me" session.
91 *
92 * @param string $value
93 *
94 * @return void
95 */
96 public function setRememberToken($value)
97 {
98 }
99
100 /**
101 * Get the column name for the "remember me" token.
102 *
103 * @return string
104 */
105 public function getRememberTokenName()
106 {
107 }
108
109 /**
110 * Returns the department number.
111 *
112 * @return string
113 */
114 public function getDepartmentNumber()
115 {
116 return $this->getFirstAttribute($this->schema->departmentNumber());
117 }
118
119 /**
120 * Sets the department number.
121 *
122 * @param string $number
123 *
124 * @return $this
125 */
126 public function setDepartmentNumber($number)
127 {
128 return $this->setFirstAttribute($this->schema->departmentNumber(), $number);
129 }
130
131 /**
132 * Returns the users info.
133 *
134 * @return mixed
135 */
136 public function getInfo()
137 {
138 return $this->getFirstAttribute($this->schema->info());
139 }
140
141 /**
142 * Sets the users info.
143 *
144 * @param string $info
145 *
146 * @return $this
147 */
148 public function setInfo($info)
149 {
150 return $this->setFirstAttribute($this->schema->info(), $info);
151 }
152
153 /**
154 * Returns the users physical delivery office name.
155 *
156 * @return string
157 */
158 public function getPhysicalDeliveryOfficeName()
159 {
160 return $this->getFirstAttribute($this->schema->physicalDeliveryOfficeName());
161 }
162
163 /**
164 * Sets the users physical delivery office name.
165 *
166 * @param string $deliveryOffice
167 *
168 * @return $this
169 */
170 public function setPhysicalDeliveryOfficeName($deliveryOffice)
171 {
172 return $this->setFirstAttribute($this->schema->physicalDeliveryOfficeName(), $deliveryOffice);
173 }
174
175 /**
176 * Returns the users locale.
177 *
178 * @return string
179 */
180 public function getLocale()
181 {
182 return $this->getFirstAttribute($this->schema->locale());
183 }
184
185 /**
186 * Sets the users locale.
187 *
188 * @param string $locale
189 *
190 * @return $this
191 */
192 public function setLocale($locale)
193 {
194 return $this->setFirstAttribute($this->schema->locale(), $locale);
195 }
196
197 /**
198 * Returns the users company.
199 *
200 * @link https://msdn.microsoft.com/en-us/library/ms675457(v=vs.85).aspx
201 *
202 * @return string
203 */
204 public function getCompany()
205 {
206 return $this->getFirstAttribute($this->schema->company());
207 }
208
209 /**
210 * Sets the users company.
211 *
212 * @param string $company
213 *
214 * @return $this
215 */
216 public function setCompany($company)
217 {
218 return $this->setFirstAttribute($this->schema->company(), $company);
219 }
220
221 /**
222 * Returns the users mailbox store DN.
223 *
224 * @link https://msdn.microsoft.com/en-us/library/aa487565(v=exchg.65).aspx
225 *
226 * @return string
227 */
228 public function getHomeMdb()
229 {
230 return $this->getFirstAttribute($this->schema->homeMdb());
231 }
232
233 /**
234 * Sets the users home drive.
235 *
236 * @link https://msdn.microsoft.com/en-us/library/ms676191(v=vs.85).aspx
237 *
238 * @return $this
239 */
240 public function setHomeDrive($drive)
241 {
242 return $this->setAttribute($this->schema->homeDrive(), $drive);
243 }
244
245 /**
246 * Specifies the drive letter to which to map the UNC path specified by homeDirectory.
247 *
248 * @link https://msdn.microsoft.com/en-us/library/ms676191(v=vs.85).aspx
249 *
250 * @return string|null
251 */
252 public function getHomeDrive()
253 {
254 return $this->getFirstAttribute($this->schema->homeDrive());
255 }
256
257 /**
258 * Sets the users home directory.
259 *
260 * @link https://msdn.microsoft.com/en-us/library/ms676190(v=vs.85).aspx
261 *
262 * @param string $directory
263 *
264 * @return $this
265 */
266 public function setHomeDirectory($directory)
267 {
268 return $this->setAttribute($this->schema->homeDirectory(), $directory);
269 }
270
271 /**
272 * The home directory for the account.
273 *
274 * @link https://msdn.microsoft.com/en-us/library/ms676190(v=vs.85).aspx
275 *
276 * @return string|null
277 */
278 public function getHomeDirectory()
279 {
280 return $this->getFirstAttribute($this->schema->homeDirectory());
281 }
282
283 /**
284 * The user's main home phone number.
285 *
286 * @link https://docs.microsoft.com/en-us/windows/desktop/ADSchema/a-homephone
287 *
288 * @return string|null
289 */
290 public function getHomePhone()
291 {
292 return $this->getFirstAttribute($this->schema->homePhone());
293 }
294
295 /**
296 * Returns the users principal name.
297 *
298 * This is usually their email address.
299 *
300 * @link https://msdn.microsoft.com/en-us/library/ms680857(v=vs.85).aspx
301 *
302 * @return string
303 */
304 public function getUserPrincipalName()
305 {
306 return $this->getFirstAttribute($this->schema->userPrincipalName());
307 }
308
309 /**
310 * Sets the users user principal name.
311 *
312 * @param string $userPrincipalName
313 *
314 * @return $this
315 */
316 public function setUserPrincipalName($userPrincipalName)
317 {
318 return $this->setFirstAttribute($this->schema->userPrincipalName(), $userPrincipalName);
319 }
320
321 /**
322 * Returns an array of workstations the user is assigned to.
323 *
324 * @return array
325 */
326 public function getUserWorkstations()
327 {
328 $workstations = $this->getFirstAttribute($this->schema->userWorkstations());
329
330 return array_filter(explode(',', $workstations));
331 }
332
333 /**
334 * Sets the workstations the user can login to.
335 *
336 * @param string|array $workstations The names of the workstations the user can login to.
337 * Must be an array of names, or a comma separated
338 * list of names.
339 *
340 * @return $this
341 */
342 public function setUserWorkstations($workstations = [])
343 {
344 if (is_array($workstations)) {
345 $workstations = implode(',', $workstations);
346 }
347
348 return $this->setFirstAttribute($this->schema->userWorkstations(), $workstations);
349 }
350
351 /**
352 * Returns the users script path if the user has one.
353 *
354 * @link https://msdn.microsoft.com/en-us/library/ms679656(v=vs.85).aspx
355 *
356 * @return string
357 */
358 public function getScriptPath()
359 {
360 return $this->getFirstAttribute($this->schema->scriptPath());
361 }
362
363 /**
364 * Sets the users script path.
365 *
366 * @param string $path
367 *
368 * @return $this
369 */
370 public function setScriptPath($path)
371 {
372 return $this->setFirstAttribute($this->schema->scriptPath(), $path);
373 }
374
375 /**
376 * Returns the users bad password count.
377 *
378 * @return string
379 */
380 public function getBadPasswordCount()
381 {
382 return $this->getFirstAttribute($this->schema->badPasswordCount());
383 }
384
385 /**
386 * Returns the users bad password time.
387 *
388 * @return string
389 */
390 public function getBadPasswordTime()
391 {
392 return $this->getFirstAttribute($this->schema->badPasswordTime());
393 }
394
395 /**
396 * Returns the bad password time unix timestamp.
397 *
398 * @return float|null
399 */
400 public function getBadPasswordTimestamp()
401 {
402 if ($time = $this->getBadPasswordTime()) {
403 return Utilities::convertWindowsTimeToUnixTime($time);
404 }
405 }
406
407 /**
408 * Returns the formatted timestamp of the bad password date.
409 *
410 * @throws \Exception
411 *
412 * @return string|null
413 */
414 public function getBadPasswordDate()
415 {
416 if ($timestamp = $this->getBadPasswordTimestamp()) {
417 return (new DateTime())->setTimestamp($timestamp)->format($this->dateFormat);
418 }
419 }
420
421 /**
422 * Returns the time when the users password was set last.
423 *
424 * @return string
425 */
426 public function getPasswordLastSet()
427 {
428 return $this->getFirstAttribute($this->schema->passwordLastSet());
429 }
430
431 /**
432 * Returns the password last set unix timestamp.
433 *
434 * @return float|null
435 */
436 public function getPasswordLastSetTimestamp()
437 {
438 if ($time = $this->getPasswordLastSet()) {
439 return Utilities::convertWindowsTimeToUnixTime($time);
440 }
441 }
442
443 /**
444 * Returns the formatted timestamp of the password last set date.
445 *
446 * @throws \Exception
447 *
448 * @return string|null
449 */
450 public function getPasswordLastSetDate()
451 {
452 if ($timestamp = $this->getPasswordLastSetTimestamp()) {
453 return (new DateTime())->setTimestamp($timestamp)->format($this->dateFormat);
454 }
455 }
456
457 /**
458 * Returns the users lockout time.
459 *
460 * @return string
461 */
462 public function getLockoutTime()
463 {
464 return $this->getFirstAttribute($this->schema->lockoutTime());
465 }
466
467 /**
468 * Returns the users lockout unix timestamp.
469 *
470 * @return float|null
471 */
472 public function getLockoutTimestamp()
473 {
474 if ($time = $this->getLockoutTime()) {
475 return Utilities::convertWindowsTimeToUnixTime($time);
476 }
477 }
478
479 /**
480 * Returns the formatted timestamp of the lockout date.
481 *
482 * @throws \Exception
483 *
484 * @return string|null
485 */
486 public function getLockoutDate()
487 {
488 if ($timestamp = $this->getLockoutTimestamp()) {
489 return (new DateTime())->setTimestamp($timestamp)->format($this->dateFormat);
490 }
491 }
492
493 /**
494 * Clears the accounts lockout time, unlocking the account.
495 *
496 * @return $this
497 */
498 public function setClearLockoutTime()
499 {
500 return $this->setFirstAttribute($this->schema->lockoutTime(), 0);
501 }
502
503 /**
504 * Returns the users profile file path.
505 *
506 * @return string
507 */
508 public function getProfilePath()
509 {
510 return $this->getFirstAttribute($this->schema->profilePath());
511 }
512
513 /**
514 * Sets the users profile path.
515 *
516 * @param string $path
517 *
518 * @return $this
519 */
520 public function setProfilePath($path)
521 {
522 return $this->setFirstAttribute($this->schema->profilePath(), $path);
523 }
524
525 /**
526 * Returns the users legacy exchange distinguished name.
527 *
528 * @return string
529 */
530 public function getLegacyExchangeDn()
531 {
532 return $this->getFirstAttribute($this->schema->legacyExchangeDn());
533 }
534
535 /**
536 * Sets the users account expiry date.
537 *
538 * If no expiry time is given, the account is set to never expire.
539 *
540 * @link https://msdn.microsoft.com/en-us/library/ms675098(v=vs.85).aspx
541 *
542 * @param float $expiryTime
543 *
544 * @return $this
545 */
546 public function setAccountExpiry($expiryTime)
547 {
548 $time = is_null($expiryTime) ? '9223372036854775807' : (string) Utilities::convertUnixTimeToWindowsTime($expiryTime);
549
550 return $this->setFirstAttribute($this->schema->accountExpires(), $time);
551 }
552
553 /**
554 * Returns an array of address book DNs
555 * that the user is listed to be shown in.
556 *
557 * @return array
558 */
559 public function getShowInAddressBook()
560 {
561 return $this->getAttribute($this->schema->showInAddressBook());
562 }
563
564 /**
565 * Returns the users thumbnail photo base 64 encoded.
566 *
567 * Suitable for inserting into an HTML image element.
568 *
569 * @return string|null
570 */
571 public function getThumbnailEncoded()
572 {
573 if ($data = base64_decode($this->getThumbnail(), $strict = true)) {
574 // In case we don't have the file info extension enabled,
575 // we'll set the jpeg mime type as default.
576 $mime = 'image/jpeg';
577
578 $image = base64_encode($data);
579
580 if (function_exists('finfo_open')) {
581 $finfo = finfo_open();
582
583 $mime = finfo_buffer($finfo, $data, FILEINFO_MIME_TYPE);
584
585 return "data:$mime;base64,$image";
586 }
587
588 return "data:$mime;base64,$image";
589 }
590 }
591
592 /**
593 * Returns the users thumbnail photo.
594 *
595 * @return mixed
596 */
597 public function getThumbnail()
598 {
599 return $this->getFirstAttribute($this->schema->thumbnail());
600 }
601
602 /**
603 * Sets the users thumbnail photo.
604 *
605 * @param string $data
606 * @param bool $encode
607 *
608 * @return $this
609 */
610 public function setThumbnail($data, $encode = true)
611 {
612 if ($encode && !base64_decode($data, $strict = true)) {
613 // If the string we're given is not base 64 encoded, then
614 // we will encode it before setting it on the user.
615 $data = base64_encode($data);
616 }
617
618 return $this->setAttribute($this->schema->thumbnail(), $data);
619 }
620
621 /**
622 * Returns the users JPEG photo.
623 *
624 * @return null|string
625 */
626 public function getJpegPhotoEncoded()
627 {
628 $jpeg = $this->getJpegPhoto();
629
630 return is_null($jpeg) ? $jpeg : 'data:image/jpeg;base64,'.base64_encode($jpeg);
631 }
632
633 /**
634 * Returns the users JPEG photo.
635 *
636 * @return mixed
637 */
638 public function getJpegPhoto()
639 {
640 return $this->getFirstAttribute($this->schema->jpegPhoto());
641 }
642
643 /**
644 * Sets the users JPEG photo.
645 *
646 * @param string $string
647 *
648 * @return $this
649 */
650 public function setJpegPhoto($string)
651 {
652 if (!base64_decode($string, $strict = true)) {
653 $string = base64_encode($string);
654 }
655
656 return $this->setAttribute($this->schema->jpegPhoto(), $string);
657 }
658
659 /**
660 * Return the employee ID.
661 *
662 * @return string
663 */
664 public function getEmployeeId()
665 {
666 return $this->getFirstAttribute($this->schema->employeeId());
667 }
668
669 /**
670 * Sets the employee ID.
671 *
672 * @param string $employeeId
673 *
674 * @return $this
675 */
676 public function setEmployeeId($employeeId)
677 {
678 return $this->setFirstAttribute($this->schema->employeeId(), $employeeId);
679 }
680
681 /**
682 * Returns the employee type.
683 *
684 * @return string|null
685 */
686 public function getEmployeeType()
687 {
688 return $this->getFirstAttribute($this->schema->employeeType());
689 }
690
691 /**
692 * Sets the employee type.
693 *
694 * @param string $type
695 *
696 * @return $this
697 */
698 public function setEmployeeType($type)
699 {
700 return $this->setFirstAttribute($this->schema->employeeType(), $type);
701 }
702
703 /**
704 * Returns the employee number.
705 *
706 * @return string
707 */
708 public function getEmployeeNumber()
709 {
710 return $this->getFirstAttribute($this->schema->employeeNumber());
711 }
712
713 /**
714 * Sets the employee number.
715 *
716 * @param string $number
717 *
718 * @return $this
719 */
720 public function setEmployeeNumber($number)
721 {
722 return $this->setFirstAttribute($this->schema->employeeNumber(), $number);
723 }
724
725 /**
726 * Returns the room number.
727 *
728 * @return string
729 */
730 public function getRoomNumber()
731 {
732 return $this->getFirstAttribute($this->schema->roomNumber());
733 }
734
735 /**
736 * Sets the room number.
737 *
738 * @param string $number
739 *
740 * @return $this
741 */
742 public function setRoomNumber($number)
743 {
744 return $this->setFirstAttribute($this->schema->roomNumber(), $number);
745 }
746
747 /**
748 * Return the personal title.
749 *
750 * @return $this
751 */
752 public function getPersonalTitle()
753 {
754 return $this->getFirstAttribute($this->schema->personalTitle());
755 }
756
757 /**
758 * Sets the personal title.
759 *
760 * @param string $personalTitle
761 *
762 * @return $this
763 */
764 public function setPersonalTitle($personalTitle)
765 {
766 return $this->setFirstAttribute($this->schema->personalTitle(), $personalTitle);
767 }
768
769 /**
770 * Return the user parameters.
771 *
772 * @return TSPropertyArray
773 */
774 public function getUserParameters()
775 {
776 return new TSPropertyArray($this->getFirstAttribute('userparameters'));
777 }
778
779 /**
780 * Sets the user parameters.
781 *
782 * @param TSPropertyArray $userParameters
783 *
784 * @return $this
785 */
786 public function setUserParameters(TSPropertyArray $userParameters)
787 {
788 return $this->setFirstAttribute('userparameters', $userParameters->toBinary());
789 }
790
791 /**
792 * Retrieves the primary group of the current user.
793 *
794 * @return Model|bool
795 */
796 public function getPrimaryGroup()
797 {
798 $groupSid = preg_replace('/\d+$/', $this->getPrimaryGroupId(), $this->getConvertedSid());
799
800 return $this->query->newInstance()->findBySid($groupSid);
801 }
802
803 /**
804 * Sets the password on the current user.
805 *
806 * @param string $password
807 *
808 * @throws AdldapException When no SSL or TLS secured connection is present.
809 *
810 * @return $this
811 */
812 public function setPassword($password)
813 {
814 $this->validateSecureConnection();
815
816 $encodedPassword = call_user_func(static::getPasswordStrategy(), $password);
817
818 if ($this->exists) {
819 // If the record exists, we need to add a batch replace
820 // modification, otherwise we'll receive a "type or
821 // value" exists exception from our LDAP server.
822 return $this->addModification(
823 $this->newBatchModification(
824 $this->schema->unicodePassword(),
825 LDAP_MODIFY_BATCH_REPLACE,
826 [$encodedPassword]
827 )
828 );
829 } else {
830 // Otherwise, we are creating a new record
831 // and we can set the attribute normally.
832 return $this->setFirstAttribute(
833 $this->schema->unicodePassword(),
834 $encodedPassword
835 );
836 }
837 }
838
839 /**
840 * Sets the option to force the password change at the next logon.
841 *
842 * Does not work if the "Password never expires" option is enabled.
843 *
844 * @return $this
845 */
846 public function setEnableForcePasswordChange()
847 {
848 return $this->setFirstAttribute($this->schema->passwordLastSet(), 0);
849 }
850
851 /**
852 * Sets the option to disable forcing a password change at the next logon.
853 *
854 * @return $this
855 */
856 public function setDisableForcePasswordChange()
857 {
858 return $this->setFirstAttribute($this->schema->passwordLastSet(), -1);
859 }
860
861 /**
862 * Change the password of the current user. This must be performed over SSL / TLS.
863 *
864 * Throws an exception on failure.
865 *
866 * @param string $oldPassword The new password
867 * @param string $newPassword The old password
868 * @param bool $replaceNotRemove Alternative password change method. Set to true if you're receiving 'CONSTRAINT'
869 * errors.
870 *
871 * @throws UserPasswordPolicyException When the new password does not match your password policy.
872 * @throws UserPasswordIncorrectException When the old password is incorrect.
873 * @throws AdldapException When an unknown cause of failure occurs.
874 *
875 * @return true
876 */
877 public function changePassword($oldPassword, $newPassword, $replaceNotRemove = false)
878 {
879 $this->validateSecureConnection();
880
881 $attribute = $this->schema->unicodePassword();
882
883 $modifications = [];
884
885 if ($replaceNotRemove) {
886 $modifications[] = $this->newBatchModification(
887 $attribute,
888 LDAP_MODIFY_BATCH_REPLACE,
889 [call_user_func(static::getPasswordStrategy(), $newPassword)]
890 );
891 } else {
892 // Create batch modification for removing the old password.
893 $modifications[] = $this->newBatchModification(
894 $attribute,
895 LDAP_MODIFY_BATCH_REMOVE,
896 [call_user_func(static::getPasswordStrategy(), $oldPassword)]
897 );
898
899 // Create batch modification for adding the new password.
900 $modifications[] = $this->newBatchModification(
901 $attribute,
902 LDAP_MODIFY_BATCH_ADD,
903 [call_user_func(static::getPasswordStrategy(), $newPassword)]
904 );
905 }
906
907 // Add the modifications.
908 foreach ($modifications as $modification) {
909 $this->addModification($modification);
910 }
911
912 $result = @$this->update();
913
914 if (!$result) {
915 // If the user failed to update, we'll see if we can
916 // figure out why by retrieving the extended error.
917 $error = $this->query->getConnection()->getExtendedError();
918 $code = $this->query->getConnection()->getExtendedErrorCode();
919
920 switch ($code) {
921 case '0000052D':
922 throw new UserPasswordPolicyException(
923 "Error: $code. Your new password does not match the password policy."
924 );
925 case '00000056':
926 throw new UserPasswordIncorrectException(
927 "Error: $code. Your old password is incorrect."
928 );
929 default:
930 throw new AdldapException($error);
931 }
932 }
933
934 return $result;
935 }
936
937 /**
938 * Return true / false if LDAP User is active (enabled & not expired).
939 *
940 * @return bool
941 */
942 public function isActive()
943 {
944 return $this->isEnabled() && !$this->isExpired();
945 }
946
947 /**
948 * Return true / false if the LDAP User is expired.
949 *
950 * @param DateTime $date Optional date
951 *
952 * @return bool
953 */
954 public function isExpired(DateTime $date = null)
955 {
956 // Here we'll determine if the account expires by checking is expiration date.
957 if ($expirationDate = $this->expirationDate()) {
958 $date = $date ?: new DateTime();
959
960 return $expirationDate <= $date;
961 }
962
963 // The account has no expiry date.
964 return false;
965 }
966
967 /**
968 * Return the expiration date of the user account.
969 *
970 * @throws \Exception
971 *
972 * @return DateTime|null
973 */
974 public function expirationDate()
975 {
976 $accountExpiry = $this->getAccountExpiry();
977
978 // If the account expiry is zero or the expiry is equal to
979 // ActiveDirectory's 'never expire' value,
980 // then we'll return null here.
981 if ($accountExpiry == 0 || $accountExpiry == $this->getSchema()->neverExpiresDate()) {
982 return;
983 }
984
985 $unixTime = Utilities::convertWindowsTimeToUnixTime($accountExpiry);
986
987 return (new DateTime())->setTimestamp($unixTime);
988 }
989
990 /**
991 * Returns the users account expiry date.
992 *
993 * @return string
994 */
995 public function getAccountExpiry()
996 {
997 return $this->getFirstAttribute($this->schema->accountExpires());
998 }
999
1000 /**
1001 * Returns true / false if the users password is expired.
1002 *
1003 * @return bool
1004 */
1005 public function passwordExpired()
1006 {
1007 // First we'll check the users userAccountControl to see if
1008 // it contains the 'password does not expire' flag.
1009 if ($this->getUserAccountControlObject()->has(AccountControl::DONT_EXPIRE_PASSWORD)) {
1010 return false;
1011 }
1012
1013 $lastSet = (int) $this->getPasswordLastSet();
1014
1015 if ($lastSet === 0) {
1016 // If the users last set time is zero, the password has
1017 // been manually expired by an administrator.
1018 return true;
1019 }
1020
1021 // We'll check if we're using the ActiveDirectory schema to retrieve
1022 // the max password age, as this is an AD-only feature.
1023 if ($this->schema instanceof ActiveDirectory) {
1024 $query = $this->query->newInstance();
1025
1026 // We need to get the root domain object to be able to
1027 // retrieve the max password age on the domain.
1028 $rootDomainObject = $query->select($this->schema->maxPasswordAge())
1029 ->whereHas($this->schema->objectClass())
1030 ->first();
1031
1032 $maxPasswordAge = $rootDomainObject->getMaxPasswordAge();
1033
1034 if (empty($maxPasswordAge)) {
1035 // There is not a max password age set on the LDAP server.
1036 return false;
1037 }
1038
1039 // convert from 100 nanosecond ticks to seconds
1040 $maxPasswordAgeSeconds = $maxPasswordAge / 10000000;
1041
1042 $lastSetUnixEpoch = Utilities::convertWindowsTimeToUnixTime($lastSet);
1043 $passwordExpiryTime = $lastSetUnixEpoch - $maxPasswordAgeSeconds;
1044
1045 $expiresAt = (new DateTime())->setTimestamp($passwordExpiryTime);
1046
1047 // If our current time is greater than the users password
1048 // expiry time, the users password has expired.
1049 return (new DateTime())->getTimestamp() >= $expiresAt->getTimestamp();
1050 }
1051
1052 return false;
1053 }
1054}