blob: 14c33435e5a97956d919ff9544e63df21855d76c [file] [log] [blame]
<?php
namespace Adldap\Models;
use DateTime;
use Adldap\Utilities;
use Adldap\AdldapException;
use Adldap\Schemas\ActiveDirectory;
use Adldap\Models\Attributes\AccountControl;
use Adldap\Models\Attributes\TSPropertyArray;
use Illuminate\Contracts\Auth\Authenticatable;
/**
* Class User.
*
* Represents an LDAP user.
*/
class User extends Entry implements Authenticatable
{
use Concerns\HasUserProperties;
use Concerns\HasDescription;
use Concerns\HasMemberOf;
use Concerns\HasLastLogonAndLogOff;
use Concerns\HasUserAccountControl;
/** @var callable|null */
private static $passwordStrategy;
/**
* Password will be processed using given callback before saving.
*
* @param callable $strategy
*/
public static function usePasswordStrategy(callable $strategy)
{
static::$passwordStrategy = $strategy;
}
/**
* Will return user set password strategy or default one.
*
* @return callable
*/
public static function getPasswordStrategy(): callable
{
return static::$passwordStrategy ?? function ($password) {
return Utilities::encodePassword($password);
};
}
/**
* Get the name of the unique identifier for the user.
*
* @return string
*/
public function getAuthIdentifierName()
{
return $this->schema->objectGuid();
}
/**
* Get the unique identifier for the user.
*
* @return mixed
*/
public function getAuthIdentifier()
{
return $this->getConvertedGuid();
}
/**
* Get the password for the user.
*
* @return string
*/
public function getAuthPassword()
{
}
/**
* Get the token value for the "remember me" session.
*
* @return string
*/
public function getRememberToken()
{
}
/**
* Set the token value for the "remember me" session.
*
* @param string $value
*
* @return void
*/
public function setRememberToken($value)
{
}
/**
* Get the column name for the "remember me" token.
*
* @return string
*/
public function getRememberTokenName()
{
}
/**
* Returns the department number.
*
* @return string
*/
public function getDepartmentNumber()
{
return $this->getFirstAttribute($this->schema->departmentNumber());
}
/**
* Sets the department number.
*
* @param string $number
*
* @return $this
*/
public function setDepartmentNumber($number)
{
return $this->setFirstAttribute($this->schema->departmentNumber(), $number);
}
/**
* Returns the users info.
*
* @return mixed
*/
public function getInfo()
{
return $this->getFirstAttribute($this->schema->info());
}
/**
* Sets the users info.
*
* @param string $info
*
* @return $this
*/
public function setInfo($info)
{
return $this->setFirstAttribute($this->schema->info(), $info);
}
/**
* Returns the users physical delivery office name.
*
* @return string
*/
public function getPhysicalDeliveryOfficeName()
{
return $this->getFirstAttribute($this->schema->physicalDeliveryOfficeName());
}
/**
* Sets the users physical delivery office name.
*
* @param string $deliveryOffice
*
* @return $this
*/
public function setPhysicalDeliveryOfficeName($deliveryOffice)
{
return $this->setFirstAttribute($this->schema->physicalDeliveryOfficeName(), $deliveryOffice);
}
/**
* Returns the users locale.
*
* @return string
*/
public function getLocale()
{
return $this->getFirstAttribute($this->schema->locale());
}
/**
* Sets the users locale.
*
* @param string $locale
*
* @return $this
*/
public function setLocale($locale)
{
return $this->setFirstAttribute($this->schema->locale(), $locale);
}
/**
* Returns the users company.
*
* @link https://msdn.microsoft.com/en-us/library/ms675457(v=vs.85).aspx
*
* @return string
*/
public function getCompany()
{
return $this->getFirstAttribute($this->schema->company());
}
/**
* Sets the users company.
*
* @param string $company
*
* @return $this
*/
public function setCompany($company)
{
return $this->setFirstAttribute($this->schema->company(), $company);
}
/**
* Returns the users mailbox store DN.
*
* @link https://msdn.microsoft.com/en-us/library/aa487565(v=exchg.65).aspx
*
* @return string
*/
public function getHomeMdb()
{
return $this->getFirstAttribute($this->schema->homeMdb());
}
/**
* Sets the users home drive.
*
* @link https://msdn.microsoft.com/en-us/library/ms676191(v=vs.85).aspx
*
* @return $this
*/
public function setHomeDrive($drive)
{
return $this->setAttribute($this->schema->homeDrive(), $drive);
}
/**
* Specifies the drive letter to which to map the UNC path specified by homeDirectory.
*
* @link https://msdn.microsoft.com/en-us/library/ms676191(v=vs.85).aspx
*
* @return string|null
*/
public function getHomeDrive()
{
return $this->getFirstAttribute($this->schema->homeDrive());
}
/**
* Sets the users home directory.
*
* @link https://msdn.microsoft.com/en-us/library/ms676190(v=vs.85).aspx
*
* @param string $directory
*
* @return $this
*/
public function setHomeDirectory($directory)
{
return $this->setAttribute($this->schema->homeDirectory(), $directory);
}
/**
* The home directory for the account.
*
* @link https://msdn.microsoft.com/en-us/library/ms676190(v=vs.85).aspx
*
* @return string|null
*/
public function getHomeDirectory()
{
return $this->getFirstAttribute($this->schema->homeDirectory());
}
/**
* The user's main home phone number.
*
* @link https://docs.microsoft.com/en-us/windows/desktop/ADSchema/a-homephone
*
* @return string|null
*/
public function getHomePhone()
{
return $this->getFirstAttribute($this->schema->homePhone());
}
/**
* Returns the users principal name.
*
* This is usually their email address.
*
* @link https://msdn.microsoft.com/en-us/library/ms680857(v=vs.85).aspx
*
* @return string
*/
public function getUserPrincipalName()
{
return $this->getFirstAttribute($this->schema->userPrincipalName());
}
/**
* Sets the users user principal name.
*
* @param string $userPrincipalName
*
* @return $this
*/
public function setUserPrincipalName($userPrincipalName)
{
return $this->setFirstAttribute($this->schema->userPrincipalName(), $userPrincipalName);
}
/**
* Returns an array of workstations the user is assigned to.
*
* @return array
*/
public function getUserWorkstations()
{
$workstations = $this->getFirstAttribute($this->schema->userWorkstations());
return array_filter(explode(',', $workstations));
}
/**
* Sets the workstations the user can login to.
*
* @param string|array $workstations The names of the workstations the user can login to.
* Must be an array of names, or a comma separated
* list of names.
*
* @return $this
*/
public function setUserWorkstations($workstations = [])
{
if (is_array($workstations)) {
$workstations = implode(',', $workstations);
}
return $this->setFirstAttribute($this->schema->userWorkstations(), $workstations);
}
/**
* Returns the users script path if the user has one.
*
* @link https://msdn.microsoft.com/en-us/library/ms679656(v=vs.85).aspx
*
* @return string
*/
public function getScriptPath()
{
return $this->getFirstAttribute($this->schema->scriptPath());
}
/**
* Sets the users script path.
*
* @param string $path
*
* @return $this
*/
public function setScriptPath($path)
{
return $this->setFirstAttribute($this->schema->scriptPath(), $path);
}
/**
* Returns the users bad password count.
*
* @return string
*/
public function getBadPasswordCount()
{
return $this->getFirstAttribute($this->schema->badPasswordCount());
}
/**
* Returns the users bad password time.
*
* @return string
*/
public function getBadPasswordTime()
{
return $this->getFirstAttribute($this->schema->badPasswordTime());
}
/**
* Returns the bad password time unix timestamp.
*
* @return float|null
*/
public function getBadPasswordTimestamp()
{
if ($time = $this->getBadPasswordTime()) {
return Utilities::convertWindowsTimeToUnixTime($time);
}
}
/**
* Returns the formatted timestamp of the bad password date.
*
* @throws \Exception
*
* @return string|null
*/
public function getBadPasswordDate()
{
if ($timestamp = $this->getBadPasswordTimestamp()) {
return (new DateTime())->setTimestamp($timestamp)->format($this->dateFormat);
}
}
/**
* Returns the time when the users password was set last.
*
* @return string
*/
public function getPasswordLastSet()
{
return $this->getFirstAttribute($this->schema->passwordLastSet());
}
/**
* Returns the password last set unix timestamp.
*
* @return float|null
*/
public function getPasswordLastSetTimestamp()
{
if ($time = $this->getPasswordLastSet()) {
return Utilities::convertWindowsTimeToUnixTime($time);
}
}
/**
* Returns the formatted timestamp of the password last set date.
*
* @throws \Exception
*
* @return string|null
*/
public function getPasswordLastSetDate()
{
if ($timestamp = $this->getPasswordLastSetTimestamp()) {
return (new DateTime())->setTimestamp($timestamp)->format($this->dateFormat);
}
}
/**
* Returns the users lockout time.
*
* @return string
*/
public function getLockoutTime()
{
return $this->getFirstAttribute($this->schema->lockoutTime());
}
/**
* Returns the users lockout unix timestamp.
*
* @return float|null
*/
public function getLockoutTimestamp()
{
if ($time = $this->getLockoutTime()) {
return Utilities::convertWindowsTimeToUnixTime($time);
}
}
/**
* Returns the formatted timestamp of the lockout date.
*
* @throws \Exception
*
* @return string|null
*/
public function getLockoutDate()
{
if ($timestamp = $this->getLockoutTimestamp()) {
return (new DateTime())->setTimestamp($timestamp)->format($this->dateFormat);
}
}
/**
* Clears the accounts lockout time, unlocking the account.
*
* @return $this
*/
public function setClearLockoutTime()
{
return $this->setFirstAttribute($this->schema->lockoutTime(), 0);
}
/**
* Returns the users profile file path.
*
* @return string
*/
public function getProfilePath()
{
return $this->getFirstAttribute($this->schema->profilePath());
}
/**
* Sets the users profile path.
*
* @param string $path
*
* @return $this
*/
public function setProfilePath($path)
{
return $this->setFirstAttribute($this->schema->profilePath(), $path);
}
/**
* Returns the users legacy exchange distinguished name.
*
* @return string
*/
public function getLegacyExchangeDn()
{
return $this->getFirstAttribute($this->schema->legacyExchangeDn());
}
/**
* Sets the users account expiry date.
*
* If no expiry time is given, the account is set to never expire.
*
* @link https://msdn.microsoft.com/en-us/library/ms675098(v=vs.85).aspx
*
* @param float $expiryTime
*
* @return $this
*/
public function setAccountExpiry($expiryTime)
{
$time = is_null($expiryTime) ? '9223372036854775807' : (string) Utilities::convertUnixTimeToWindowsTime($expiryTime);
return $this->setFirstAttribute($this->schema->accountExpires(), $time);
}
/**
* Returns an array of address book DNs
* that the user is listed to be shown in.
*
* @return array
*/
public function getShowInAddressBook()
{
return $this->getAttribute($this->schema->showInAddressBook());
}
/**
* Returns the users thumbnail photo base 64 encoded.
*
* Suitable for inserting into an HTML image element.
*
* @return string|null
*/
public function getThumbnailEncoded()
{
if ($data = base64_decode($this->getThumbnail(), $strict = true)) {
// In case we don't have the file info extension enabled,
// we'll set the jpeg mime type as default.
$mime = 'image/jpeg';
$image = base64_encode($data);
if (function_exists('finfo_open')) {
$finfo = finfo_open();
$mime = finfo_buffer($finfo, $data, FILEINFO_MIME_TYPE);
return "data:$mime;base64,$image";
}
return "data:$mime;base64,$image";
}
}
/**
* Returns the users thumbnail photo.
*
* @return mixed
*/
public function getThumbnail()
{
return $this->getFirstAttribute($this->schema->thumbnail());
}
/**
* Sets the users thumbnail photo.
*
* @param string $data
* @param bool $encode
*
* @return $this
*/
public function setThumbnail($data, $encode = true)
{
if ($encode && !base64_decode($data, $strict = true)) {
// If the string we're given is not base 64 encoded, then
// we will encode it before setting it on the user.
$data = base64_encode($data);
}
return $this->setAttribute($this->schema->thumbnail(), $data);
}
/**
* Returns the users JPEG photo.
*
* @return null|string
*/
public function getJpegPhotoEncoded()
{
$jpeg = $this->getJpegPhoto();
return is_null($jpeg) ? $jpeg : 'data:image/jpeg;base64,'.base64_encode($jpeg);
}
/**
* Returns the users JPEG photo.
*
* @return mixed
*/
public function getJpegPhoto()
{
return $this->getFirstAttribute($this->schema->jpegPhoto());
}
/**
* Sets the users JPEG photo.
*
* @param string $string
*
* @return $this
*/
public function setJpegPhoto($string)
{
if (!base64_decode($string, $strict = true)) {
$string = base64_encode($string);
}
return $this->setAttribute($this->schema->jpegPhoto(), $string);
}
/**
* Return the employee ID.
*
* @return string
*/
public function getEmployeeId()
{
return $this->getFirstAttribute($this->schema->employeeId());
}
/**
* Sets the employee ID.
*
* @param string $employeeId
*
* @return $this
*/
public function setEmployeeId($employeeId)
{
return $this->setFirstAttribute($this->schema->employeeId(), $employeeId);
}
/**
* Returns the employee type.
*
* @return string|null
*/
public function getEmployeeType()
{
return $this->getFirstAttribute($this->schema->employeeType());
}
/**
* Sets the employee type.
*
* @param string $type
*
* @return $this
*/
public function setEmployeeType($type)
{
return $this->setFirstAttribute($this->schema->employeeType(), $type);
}
/**
* Returns the employee number.
*
* @return string
*/
public function getEmployeeNumber()
{
return $this->getFirstAttribute($this->schema->employeeNumber());
}
/**
* Sets the employee number.
*
* @param string $number
*
* @return $this
*/
public function setEmployeeNumber($number)
{
return $this->setFirstAttribute($this->schema->employeeNumber(), $number);
}
/**
* Returns the room number.
*
* @return string
*/
public function getRoomNumber()
{
return $this->getFirstAttribute($this->schema->roomNumber());
}
/**
* Sets the room number.
*
* @param string $number
*
* @return $this
*/
public function setRoomNumber($number)
{
return $this->setFirstAttribute($this->schema->roomNumber(), $number);
}
/**
* Return the personal title.
*
* @return $this
*/
public function getPersonalTitle()
{
return $this->getFirstAttribute($this->schema->personalTitle());
}
/**
* Sets the personal title.
*
* @param string $personalTitle
*
* @return $this
*/
public function setPersonalTitle($personalTitle)
{
return $this->setFirstAttribute($this->schema->personalTitle(), $personalTitle);
}
/**
* Return the user parameters.
*
* @return TSPropertyArray
*/
public function getUserParameters()
{
return new TSPropertyArray($this->getFirstAttribute('userparameters'));
}
/**
* Sets the user parameters.
*
* @param TSPropertyArray $userParameters
*
* @return $this
*/
public function setUserParameters(TSPropertyArray $userParameters)
{
return $this->setFirstAttribute('userparameters', $userParameters->toBinary());
}
/**
* Retrieves the primary group of the current user.
*
* @return Model|bool
*/
public function getPrimaryGroup()
{
$groupSid = preg_replace('/\d+$/', $this->getPrimaryGroupId(), $this->getConvertedSid());
return $this->query->newInstance()->findBySid($groupSid);
}
/**
* Sets the password on the current user.
*
* @param string $password
*
* @throws AdldapException When no SSL or TLS secured connection is present.
*
* @return $this
*/
public function setPassword($password)
{
$this->validateSecureConnection();
$encodedPassword = call_user_func(static::getPasswordStrategy(), $password);
if ($this->exists) {
// If the record exists, we need to add a batch replace
// modification, otherwise we'll receive a "type or
// value" exists exception from our LDAP server.
return $this->addModification(
$this->newBatchModification(
$this->schema->unicodePassword(),
LDAP_MODIFY_BATCH_REPLACE,
[$encodedPassword]
)
);
} else {
// Otherwise, we are creating a new record
// and we can set the attribute normally.
return $this->setFirstAttribute(
$this->schema->unicodePassword(),
$encodedPassword
);
}
}
/**
* Sets the option to force the password change at the next logon.
*
* Does not work if the "Password never expires" option is enabled.
*
* @return $this
*/
public function setEnableForcePasswordChange()
{
return $this->setFirstAttribute($this->schema->passwordLastSet(), 0);
}
/**
* Sets the option to disable forcing a password change at the next logon.
*
* @return $this
*/
public function setDisableForcePasswordChange()
{
return $this->setFirstAttribute($this->schema->passwordLastSet(), -1);
}
/**
* Change the password of the current user. This must be performed over SSL / TLS.
*
* Throws an exception on failure.
*
* @param string $oldPassword The new password
* @param string $newPassword The old password
* @param bool $replaceNotRemove Alternative password change method. Set to true if you're receiving 'CONSTRAINT'
* errors.
*
* @throws UserPasswordPolicyException When the new password does not match your password policy.
* @throws UserPasswordIncorrectException When the old password is incorrect.
* @throws AdldapException When an unknown cause of failure occurs.
*
* @return true
*/
public function changePassword($oldPassword, $newPassword, $replaceNotRemove = false)
{
$this->validateSecureConnection();
$attribute = $this->schema->unicodePassword();
$modifications = [];
if ($replaceNotRemove) {
$modifications[] = $this->newBatchModification(
$attribute,
LDAP_MODIFY_BATCH_REPLACE,
[call_user_func(static::getPasswordStrategy(), $newPassword)]
);
} else {
// Create batch modification for removing the old password.
$modifications[] = $this->newBatchModification(
$attribute,
LDAP_MODIFY_BATCH_REMOVE,
[call_user_func(static::getPasswordStrategy(), $oldPassword)]
);
// Create batch modification for adding the new password.
$modifications[] = $this->newBatchModification(
$attribute,
LDAP_MODIFY_BATCH_ADD,
[call_user_func(static::getPasswordStrategy(), $newPassword)]
);
}
// Add the modifications.
foreach ($modifications as $modification) {
$this->addModification($modification);
}
$result = @$this->update();
if (!$result) {
// If the user failed to update, we'll see if we can
// figure out why by retrieving the extended error.
$error = $this->query->getConnection()->getExtendedError();
$code = $this->query->getConnection()->getExtendedErrorCode();
switch ($code) {
case '0000052D':
throw new UserPasswordPolicyException(
"Error: $code. Your new password does not match the password policy."
);
case '00000056':
throw new UserPasswordIncorrectException(
"Error: $code. Your old password is incorrect."
);
default:
throw new AdldapException($error);
}
}
return $result;
}
/**
* Return true / false if LDAP User is active (enabled & not expired).
*
* @return bool
*/
public function isActive()
{
return $this->isEnabled() && !$this->isExpired();
}
/**
* Return true / false if the LDAP User is expired.
*
* @param DateTime $date Optional date
*
* @return bool
*/
public function isExpired(DateTime $date = null)
{
// Here we'll determine if the account expires by checking is expiration date.
if ($expirationDate = $this->expirationDate()) {
$date = $date ?: new DateTime();
return $expirationDate <= $date;
}
// The account has no expiry date.
return false;
}
/**
* Return the expiration date of the user account.
*
* @throws \Exception
*
* @return DateTime|null
*/
public function expirationDate()
{
$accountExpiry = $this->getAccountExpiry();
// If the account expiry is zero or the expiry is equal to
// ActiveDirectory's 'never expire' value,
// then we'll return null here.
if ($accountExpiry == 0 || $accountExpiry == $this->getSchema()->neverExpiresDate()) {
return;
}
$unixTime = Utilities::convertWindowsTimeToUnixTime($accountExpiry);
return (new DateTime())->setTimestamp($unixTime);
}
/**
* Returns the users account expiry date.
*
* @return string
*/
public function getAccountExpiry()
{
return $this->getFirstAttribute($this->schema->accountExpires());
}
/**
* Returns true / false if the users password is expired.
*
* @return bool
*/
public function passwordExpired()
{
// First we'll check the users userAccountControl to see if
// it contains the 'password does not expire' flag.
if ($this->getUserAccountControlObject()->has(AccountControl::DONT_EXPIRE_PASSWORD)) {
return false;
}
$lastSet = (int) $this->getPasswordLastSet();
if ($lastSet === 0) {
// If the users last set time is zero, the password has
// been manually expired by an administrator.
return true;
}
// We'll check if we're using the ActiveDirectory schema to retrieve
// the max password age, as this is an AD-only feature.
if ($this->schema instanceof ActiveDirectory) {
$query = $this->query->newInstance();
// We need to get the root domain object to be able to
// retrieve the max password age on the domain.
$rootDomainObject = $query->select($this->schema->maxPasswordAge())
->whereHas($this->schema->objectClass())
->first();
$maxPasswordAge = $rootDomainObject->getMaxPasswordAge();
if (empty($maxPasswordAge)) {
// There is not a max password age set on the LDAP server.
return false;
}
// convert from 100 nanosecond ticks to seconds
$maxPasswordAgeSeconds = $maxPasswordAge / 10000000;
$lastSetUnixEpoch = Utilities::convertWindowsTimeToUnixTime($lastSet);
$passwordExpiryTime = $lastSetUnixEpoch - $maxPasswordAgeSeconds;
$expiresAt = (new DateTime())->setTimestamp($passwordExpiryTime);
// If our current time is greater than the users password
// expiry time, the users password has expired.
return (new DateTime())->getTimestamp() >= $expiresAt->getTimestamp();
}
return false;
}
}