diff --git a/code/web/release_notes/25.02.00.MD b/code/web/release_notes/25.02.00.MD index 582f4182e1..ad75cb18b3 100644 --- a/code/web/release_notes/25.02.00.MD +++ b/code/web/release_notes/25.02.00.MD @@ -46,6 +46,7 @@ ### Local Administrators - Allow local administrators to be defined within Aspen. (DIS-13) (*MDN*) +- Local administrators may be given the ability to use Two-Factor Authentication if desired. (DIS-13) (*MDN*) - Local administrators are not allowed to link to other accounts and cannot be linked to from other accounts. (DIS-13) (*MDN*) - Hide preference for *Show Checkouts and Holds in Results* for local administrators within My Preferences. (DIS-13) (*MDN*) - Hide Contact Information for local administrators. (DIS-13) (*MDN*) diff --git a/code/web/services/MyAccount/Security.php b/code/web/services/MyAccount/Security.php index 895b2a78d9..a9fae92f27 100644 --- a/code/web/services/MyAccount/Security.php +++ b/code/web/services/MyAccount/Security.php @@ -5,7 +5,7 @@ require_once ROOT_DIR . '/services/MyAccount/MyAccount.php'; class Security extends MyAccount { - function launch() { + function launch() : void { global $interface; $twoFactor = UserAccount::has2FAEnabledForPType(); @@ -13,33 +13,27 @@ function launch() { $user = new User(); $user->id = UserAccount::getActiveUserId(); if ($user->find(true)) { - require_once ROOT_DIR . '/sys/Account/PType.php'; - $patronType = new PType(); - $patronType->pType = $user->patronType; - if ($patronType->find(true)) { - require_once ROOT_DIR . '/sys/TwoFactorAuthSetting.php'; - $twoFactorAuthSetting = new TwoFactorAuthSetting(); - $twoFactorAuthSetting->id = $patronType->twoFactorAuthSettingId; - if ($twoFactorAuthSetting->find(true)) { - $isEnabled = $twoFactorAuthSetting->isEnabled; - if ($isEnabled != 'notAvailable') { - $interface->assign('twoFactorStatus', $user->twoFactorStatus); - $interface->assign('showBackupCodes', false); - $interface->assign('enableDeactivation', true); - if ($user->twoFactorStatus == '1') { - $interface->assign('showBackupCodes', true); - require_once ROOT_DIR . '/sys/TwoFactorAuthCode.php'; - $backupCode = new TwoFactorAuthCode(); - $backupCodes = $backupCode->getBackups(); - $numBackupCodes = count($backupCodes); - $interface->assign('backupCodes', $backupCodes); - $interface->assign('numBackupCodes', $numBackupCodes); - if ($isEnabled == 'mandatory') { - $interface->assign('enableDeactivation', false); - } - } + $twoFactorAuthSetting = $user->getTwoFactorAuthenticationSetting(); + if ($twoFactorAuthSetting != null) { + $isEnabled = $twoFactorAuthSetting->isEnabled; + if ($isEnabled != 'notAvailable') { + $interface->assign('twoFactorStatus', $user->twoFactorStatus); + $interface->assign('showBackupCodes', false); + $interface->assign('enableDeactivation', true); + if ($user->twoFactorStatus == '1') { + $interface->assign('showBackupCodes', true); + require_once ROOT_DIR . '/sys/TwoFactorAuthCode.php'; + $backupCode = new TwoFactorAuthCode(); + $backupCodes = $backupCode->getBackups(); + $numBackupCodes = count($backupCodes); + $interface->assign('backupCodes', $backupCodes); + $interface->assign('numBackupCodes', $numBackupCodes); + if ($isEnabled == 'mandatory') { + $interface->assign('enableDeactivation', false); + } } + } } } diff --git a/code/web/sys/Account/User.php b/code/web/sys/Account/User.php index f2d5a35aa8..d4afea6333 100644 --- a/code/web/sys/Account/User.php +++ b/code/web/sys/Account/User.php @@ -75,7 +75,6 @@ class User extends DataObject { public $lastLoginValidation; public $twoFactorStatus; //Whether the user has enrolled - public $twoFactorAuthSettingId; //The settings based on their PType public $updateMessage; public $updateMessageIsError; @@ -597,7 +596,7 @@ function saveAdditionalAdministrationLocations(): void { public function getRolesAssignedByPType(): array { $rolesAssignedByPType = []; if ($this->id) { - //Get role based on patron type + //Get the user role based on their patron type $patronType = $this->getPTypeObj(); if (!empty($patronType)) { if ($patronType->assignedRoleId != -1) { @@ -3079,6 +3078,7 @@ public function getPTypeObj(): ?PType { require_once ROOT_DIR . '/sys/Account/PType.php'; $patronType = new PType(); $patronType->pType = $this->patronType; + $patronType->accountProfileId = $this->getAccountProfile()->id; if ($patronType->find(true)) { $this->_pTypeObj = $patronType; } else { @@ -5171,37 +5171,45 @@ function newCurbsidePickup($pickupLocation, $pickupTime, $pickupNote) { return $result; } - public function get2FAStatusForPType() { - $patronType = $this->getPTypeObj(); - if (!empty($patronType)) { + private false|null|TwoFactorAuthSetting $_twoFactorAuthenticationSetting = false; + public function getTwoFactorAuthenticationSetting() : ?TwoFactorAuthSetting { + if ($this->_twoFactorAuthenticationSetting === false) { require_once ROOT_DIR . '/sys/TwoFactorAuthSetting.php'; - $twoFactorAuthSetting = new TwoFactorAuthSetting(); - $twoFactorAuthSetting->id = $patronType->twoFactorAuthSettingId; - if ($twoFactorAuthSetting->find(true)) { - if ($twoFactorAuthSetting->isEnabled != 'notAvailable') { - return true; - } + $this->_twoFactorAuthenticationSetting = new TwoFactorAuthSetting(); + //If the user has a patron type, we will use that to determine the two factor authentication settings. + //Otherwise, we can use the account profile. + $patronType = $this->getPTypeObj(); + if (!empty($patronType)) { + $this->_twoFactorAuthenticationSetting->id = $patronType->twoFactorAuthSettingId; + }else{ + $this->_twoFactorAuthenticationSetting->accountProfileId = $this->getAccountProfile()->id; + } + if (!$this->_twoFactorAuthenticationSetting->find(true)) { + $this->_twoFactorAuthenticationSetting = null; } } - return false; + return $this->_twoFactorAuthenticationSetting; } - public function is2FARequired() { - $patronType = $this->getPTypeObj(); - if (!empty($patronType)) { - require_once ROOT_DIR . '/sys/TwoFactorAuthSetting.php'; - $twoFactorAuthSetting = new TwoFactorAuthSetting(); - $twoFactorAuthSetting->id = $patronType->twoFactorAuthSettingId; - if ($twoFactorAuthSetting->find(true)) { - if ($twoFactorAuthSetting->isEnabled == 'mandatory') { - return true; - } - } + public function get2FAStatusForPType() : bool { + $twoFactorAuthSetting = $this->getTwoFactorAuthenticationSetting(); + if ($twoFactorAuthSetting == null) { + return false; + }else{ + return $twoFactorAuthSetting->isEnabled != 'notAvailable'; + } + } + + public function is2FARequired() : bool { + $twoFactorAuthSetting = $this->getTwoFactorAuthenticationSetting(); + if ($twoFactorAuthSetting == null) { + return false; + }else{ + return $twoFactorAuthSetting->isEnabled == 'mandatory'; } - return false; } - public function get2FAStatus() { + public function get2FAStatus() : bool { $status = $this->twoFactorStatus; if ($status == '1') { //Make sure that 2-factor authentication has not been disabled by ptype even though the user previously opted in diff --git a/code/web/sys/TwoFactorAuthCode.php b/code/web/sys/TwoFactorAuthCode.php index d64fbc8e6b..4ad01de7b6 100644 --- a/code/web/sys/TwoFactorAuthCode.php +++ b/code/web/sys/TwoFactorAuthCode.php @@ -1,4 +1,4 @@ - [ 'property' => 'id', @@ -58,7 +59,7 @@ public static function getObjectStructure($context = '') { ]; } - public function createCode($num = 1, $backup = false) { + public function createCode($num = 1, $backup = false) : bool { for ($i = 1; $i <= $num; $i++) { $twoFactorAuthCode = new TwoFactorAuthCode(); $twoFactorAuthCode->code = mt_rand(100000, 999999); @@ -122,7 +123,7 @@ public function createRecoveryCode($username) : array { return $result; } - function sendCode() { + function sendCode() : bool { require_once ROOT_DIR . '/sys/Email/Mailer.php'; $mail = new Mailer(); $replyToAddress = ""; @@ -143,7 +144,8 @@ function sendCode() { $patron->id = $this->userId; if ($patron->find(true)) { if (!empty($patron->email)) { - $email = $mail->send($patron->email, translate([ + /** @noinspection PhpUnusedLocalVariableInspection */ + $emailResult = $mail->send($patron->email, translate([ 'text' => "Your one-time login code", 'isPublicFacing' => true, ]), $body, $replyToAddress); @@ -158,7 +160,7 @@ function sendCode() { } } - function validateCode($code) { + function validateCode($code) : array { global $library; require_once ROOT_DIR . '/sys/TwoFactorAuthSetting.php'; $authSetting = new TwoFactorAuthSetting(); @@ -217,7 +219,7 @@ function validateCode($code) { return $result; } - function createNewBackups() { + function createNewBackups() : void { $oldBackupCodes = new TwoFactorAuthCode(); $oldBackupCodes->userId = UserAccount::getActiveUserId(); $oldBackupCodes->status = "backup"; @@ -229,7 +231,7 @@ function createNewBackups() { $this->createCode(5, true); } - function getBackups() { + function getBackups() : array { $backupCodes = []; $backupCode = new TwoFactorAuthCode(); $backupCode->userId = UserAccount::getActiveUserId(); @@ -241,7 +243,7 @@ function getBackups() { return $backupCodes; } - function deleteCode($code) { + function deleteCode($code) : bool { $codeToCheck = new TwoFactorAuthCode(); $codeToCheck->code = $code; if ($codeToCheck->find(true)) { @@ -251,7 +253,7 @@ function deleteCode($code) { return false; } - function cleanupOldCodes() { + function cleanupOldCodes() : void { // delete codes with a used status and no longer have a valid session id $codesFromOldSessions = new TwoFactorAuthCode(); $codesFromOldSessions->status = "used"; @@ -259,7 +261,7 @@ function cleanupOldCodes() { $codesFromOldSessions->find(); while ($codesFromOldSessions->fetch()) { $session = new Session(); - $session->session_id = $codesFromOldSessions->sessionId; + $session->setSessionId($codesFromOldSessions->sessionId); if (!$session->find()) { $codeToDelete = clone $codesFromOldSessions; $codeToDelete->delete(); @@ -276,7 +278,7 @@ function cleanupOldCodes() { } } - function deactivate2FA() { + function deactivate2FA() : void { $user = new User(); $user->id = UserAccount::getActiveUserId(); diff --git a/code/web/sys/UserAccount.php b/code/web/sys/UserAccount.php index 03bcab8d60..8e4a566299 100644 --- a/code/web/sys/UserAccount.php +++ b/code/web/sys/UserAccount.php @@ -4,8 +4,8 @@ require_once ROOT_DIR . '/sys/Account/TwoFactorAuthenticationError.php'; class UserAccount { - public static $isLoggedIn = null; - public static $isAuthenticated = false; + public static ?bool $isLoggedIn = null; + public static ?bool $isAuthenticated = false; public static $primaryUserData = null; /** @var User|false */ private static $primaryUserObjectFromDB = null; @@ -16,7 +16,7 @@ class UserAccount { private static $ssoAuthOnly = false; /** - * Check to see if the user is enrolled in 2 factor authentication and has been sent a verification code, but has not verified it. + * Check to see if the user is enrolled in two-factor authentication and has been sent a verification code, but has not verified it. * * @return bool */ @@ -28,9 +28,9 @@ public static function needsToComplete2FA(): bool { if ($twoFactorSetting->find()) { if (!UserAccount::isUserMasquerading()) { - //Two factor might be required + //Two-factor authentication might be required if (UserAccount::has2FAEnabled()) { - //Two factor is required, check to see if it's complete. + //Two-factor authentication is required, check to see if it's complete. //Check the session to see if it is complete require_once ROOT_DIR . '/sys/TwoFactorAuthCode.php'; $authCodeForSession = new TwoFactorAuthCode();