Skip to content

Commit

Permalink
DIS-13 - Local Administrators
Browse files Browse the repository at this point in the history
Allow Two Factor Authentication to be used for Local Administrators
  • Loading branch information
mdnoble73 committed Jan 17, 2025
1 parent 05f4a5a commit 8a6b5da
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 68 deletions.
1 change: 1 addition & 0 deletions code/web/release_notes/25.02.00.MD
Original file line number Diff line number Diff line change
Expand Up @@ -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*)
Expand Down
46 changes: 20 additions & 26 deletions code/web/services/MyAccount/Security.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,35 @@
require_once ROOT_DIR . '/services/MyAccount/MyAccount.php';

class Security extends MyAccount {
function launch() {
function launch() : void {
global $interface;

$twoFactor = UserAccount::has2FAEnabledForPType();
$interface->assign('twoFactorEnabled', $twoFactor);
$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);
}
}

}
}
}
Expand Down
58 changes: 33 additions & 25 deletions code/web/sys/Account/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
26 changes: 14 additions & 12 deletions code/web/sys/TwoFactorAuthCode.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php /** @noinspection PhpMissingFieldTypeInspection */

require_once ROOT_DIR . '/sys/DB/DataObject.php';

Expand All @@ -12,7 +12,8 @@ class TwoFactorAuthCode extends DataObject {
public $dateSent;
public $status;

public static function getObjectStructure($context = '') {
/** @noinspection PhpUnusedParameterInspection */
public static function getObjectStructure($context = '') : array {
return [
'id' => [
'property' => 'id',
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 = "";
Expand All @@ -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);
Expand All @@ -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();
Expand Down Expand Up @@ -217,7 +219,7 @@ function validateCode($code) {
return $result;
}

function createNewBackups() {
function createNewBackups() : void {
$oldBackupCodes = new TwoFactorAuthCode();
$oldBackupCodes->userId = UserAccount::getActiveUserId();
$oldBackupCodes->status = "backup";
Expand All @@ -229,7 +231,7 @@ function createNewBackups() {
$this->createCode(5, true);
}

function getBackups() {
function getBackups() : array {
$backupCodes = [];
$backupCode = new TwoFactorAuthCode();
$backupCode->userId = UserAccount::getActiveUserId();
Expand All @@ -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)) {
Expand All @@ -251,15 +253,15 @@ 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";
$codesFromOldSessions->whereAdd("sessionId != 'null'");
$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();
Expand All @@ -276,7 +278,7 @@ function cleanupOldCodes() {
}
}

function deactivate2FA() {
function deactivate2FA() : void {

$user = new User();
$user->id = UserAccount::getActiveUserId();
Expand Down
10 changes: 5 additions & 5 deletions code/web/sys/UserAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
*/
Expand All @@ -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();
Expand Down

0 comments on commit 8a6b5da

Please sign in to comment.