Commit 73a8a555 authored by Jacob Priddy's avatar Jacob Priddy 👌
Browse files

add reason field for entries

parent ca5d6635
Pipeline #12906 passed with stages
in 3 minutes and 49 seconds
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddEntryReasonColumn extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('entries', static function (Blueprint $table) {
$table->string('reason')->default('Unknown');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('entries', static function (Blueprint $table) {
$table->dropColumn('reason');
});
}
}
...@@ -37,17 +37,24 @@ class Entry ...@@ -37,17 +37,24 @@ class Entry
*/ */
protected bool $success; protected bool $success;
/**
* @var string
*/
protected string $reason;
public function __construct( public function __construct(
int $id, int $id,
int $userId, int $userId,
int $doorId, int $doorId,
bool $success, bool $success,
string $reason,
?Carbon $createdAt = null, ?Carbon $createdAt = null,
?Carbon $updatedAt = null ?Carbon $updatedAt = null
) { ) {
$this->id = $id; $this->id = $id;
$this->userId = $userId; $this->userId = $userId;
$this->doorId = $doorId; $this->doorId = $doorId;
$this->reason = $reason;
$this->success = $success; $this->success = $success;
$this->createdAt = $createdAt; $this->createdAt = $createdAt;
$this->updatedAt = $updatedAt; $this->updatedAt = $updatedAt;
...@@ -102,8 +109,8 @@ class Entry ...@@ -102,8 +109,8 @@ class Entry
} }
/** /**
* @param \Carbon\Carbon $begin * @param \Carbon\Carbon|null $begin
* @param \Carbon\Carbon $end * @param \Carbon\Carbon|null $end
* @return bool * @return bool
*/ */
public function isBetween(?Carbon $begin, ?Carbon $end): bool public function isBetween(?Carbon $begin, ?Carbon $end): bool
...@@ -152,4 +159,12 @@ class Entry ...@@ -152,4 +159,12 @@ class Entry
return $this->getDoorId() === (int)$doorId; return $this->getDoorId() === (int)$doorId;
} }
/**
* @return string
*/
public function getReason(): string
{
return $this->reason;
}
} }
...@@ -22,6 +22,7 @@ class DatabaseEntriesRepository implements EntriesRepository ...@@ -22,6 +22,7 @@ class DatabaseEntriesRepository implements EntriesRepository
$entry->getAttribute('user_id'), $entry->getAttribute('user_id'),
$entry->getAttribute('door_id'), $entry->getAttribute('door_id'),
$entry->getAttribute('success'), $entry->getAttribute('success'),
$entry->getAttribute('reason'),
$entry->getAttribute('created_at'), $entry->getAttribute('created_at'),
$entry->getAttribute('updated_at') $entry->getAttribute('updated_at')
); );
...@@ -35,6 +36,7 @@ class DatabaseEntriesRepository implements EntriesRepository ...@@ -35,6 +36,7 @@ class DatabaseEntriesRepository implements EntriesRepository
$e = new \App\Entry(); $e = new \App\Entry();
$e->setAttribute('user_id', $entry->getUserId()); $e->setAttribute('user_id', $entry->getUserId());
$e->setAttribute('door_id', $entry->getDoorId()); $e->setAttribute('door_id', $entry->getDoorId());
$e->setAttribute('reason', $entry->getReason());
$e->setAttribute('success', $entry->wasSuccessful()); $e->setAttribute('success', $entry->wasSuccessful());
if (!$e->save()) { if (!$e->save()) {
......
...@@ -20,6 +20,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository ...@@ -20,6 +20,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository
LocalUsersRepository::getEngineeringLabAccessStudent()->getId(), LocalUsersRepository::getEngineeringLabAccessStudent()->getId(),
LocalDoorsRepository::getAmazonDoor()->getId(), LocalDoorsRepository::getAmazonDoor()->getId(),
false, false,
'reason',
Carbon::now()->subDays(1) Carbon::now()->subDays(1)
)); ));
...@@ -28,6 +29,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository ...@@ -28,6 +29,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository
LocalUsersRepository::getEngineeringLabAccessStudent()->getId(), LocalUsersRepository::getEngineeringLabAccessStudent()->getId(),
LocalDoorsRepository::getTheBatCave()->getId(), LocalDoorsRepository::getTheBatCave()->getId(),
true, true,
'reason',
Carbon::now()->subDays(1) Carbon::now()->subDays(1)
)); ));
...@@ -36,6 +38,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository ...@@ -36,6 +38,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository
LocalUsersRepository::getComputerScienceStudent()->getId(), LocalUsersRepository::getComputerScienceStudent()->getId(),
LocalDoorsRepository::getAmazonDoor()->getId(), LocalDoorsRepository::getAmazonDoor()->getId(),
true, true,
'reason',
Carbon::now()->subMinutes(4) Carbon::now()->subMinutes(4)
)); ));
...@@ -44,6 +47,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository ...@@ -44,6 +47,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository
LocalUsersRepository::getComputerScienceStudent()->getId(), LocalUsersRepository::getComputerScienceStudent()->getId(),
LocalDoorsRepository::getTheBatCave()->getId(), LocalDoorsRepository::getTheBatCave()->getId(),
false, false,
'reason',
Carbon::now()->subYear() Carbon::now()->subYear()
)); ));
} }
......
...@@ -185,6 +185,7 @@ abstract class BasePresenter ...@@ -185,6 +185,7 @@ abstract class BasePresenter
'user_id' => $entry->getUserId(), 'user_id' => $entry->getUserId(),
'door_id' => $entry->getDoorId(), 'door_id' => $entry->getDoorId(),
'success' => $entry->wasSuccessful(), 'success' => $entry->wasSuccessful(),
'reason' => $entry->getReason(),
'created_at' => self::formatDateTime($entry->getCreatedAt()), 'created_at' => self::formatDateTime($entry->getCreatedAt()),
]; ];
} }
......
...@@ -61,14 +61,16 @@ class Access implements AccessUseCase ...@@ -61,14 +61,16 @@ class Access implements AccessUseCase
* @param string $doorId * @param string $doorId
* @param string $userId * @param string $userId
* @param bool $success * @param bool $success
* @param string $reason
*/ */
protected function logUserAccess(string $doorId, string $userId, bool $success): void protected function logUserAccess(string $doorId, string $userId, bool $success, string $reason): void
{ {
$this->entries->add(new Entry( $this->entries->add(new Entry(
0, 0,
$userId, $userId,
$doorId, $doorId,
$success $success,
$reason
)); ));
} }
...@@ -83,22 +85,24 @@ class Access implements AccessUseCase ...@@ -83,22 +85,24 @@ class Access implements AccessUseCase
/** /**
* @param \Source\Entities\User|null $user * @param \Source\Entities\User|null $user
* @param string $doorId * @param string $doorId
* @param string $reason
*/ */
protected function logAllow(?User $user, string $doorId): void protected function logAllow(?User $user, string $doorId, string $reason): void
{ {
if ($user) { if ($user) {
$this->logUserAccess($doorId, $user->getId(), true); $this->logUserAccess($doorId, $user->getId(), true, $reason);
} }
} }
/** /**
* @param \Source\Entities\User|null $user * @param \Source\Entities\User|null $user
* @param string $doorId * @param string $doorId
* @param string $reason
*/ */
protected function logDeny(?User $user, string $doorId): void protected function logDeny(?User $user, string $doorId, string $reason): void
{ {
if ($user) { if ($user) {
$this->logUserAccess($doorId, $user->getId(), false); $this->logUserAccess($doorId, $user->getId(), false, $reason);
} else { } else {
$this->logDoorAttempt($doorId); $this->logDoorAttempt($doorId);
} }
...@@ -141,10 +145,10 @@ class Access implements AccessUseCase ...@@ -141,10 +145,10 @@ class Access implements AccessUseCase
foreach ($this->authorizers as $authorizer) { foreach ($this->authorizers as $authorizer) {
switch ($authorizer->check($user, $date, $door, $code, $command)) { switch ($authorizer->check($user, $date, $door, $code, $command)) {
case AccessAuthorizer::ALLOW: case AccessAuthorizer::ALLOW:
$this->logAllow($user, $door->getId()); $this->logAllow($user, $door->getId(), $authorizer->getReason());
return; return;
case AccessAuthorizer::DENY: case AccessAuthorizer::DENY:
$this->logDeny($user, $door->getId()); $this->logDeny($user, $door->getId(), $authorizer->getReason());
throw new AuthorizationException(); throw new AuthorizationException();
case AccessAuthorizer::CONTINUE: case AccessAuthorizer::CONTINUE:
// Fall through to default. // Fall through to default.
...@@ -154,7 +158,7 @@ class Access implements AccessUseCase ...@@ -154,7 +158,7 @@ class Access implements AccessUseCase
} }
// Default to deny if no authorizer allowed us // Default to deny if no authorizer allowed us
$this->logDeny($user, $door->getId()); $this->logDeny($user, $door->getId(), 'No rules allowed the entry. Default denying.');
throw new AuthorizationException(); throw new AuthorizationException();
} }
} }
...@@ -20,8 +20,16 @@ interface AccessAuthorizer ...@@ -20,8 +20,16 @@ interface AccessAuthorizer
* @param \Carbon\Carbon $date * @param \Carbon\Carbon $date
* @param \Source\Entities\Door $door * @param \Source\Entities\Door $door
* @param string $doorcode * @param string $doorcode
* @param string $commandString * @param string|null $commandString
* @return int * @return int
*/ */
public function check(?User $user, Carbon $date, Door $door, string $doorcode, ?string $commandString): int; public function check(?User $user, Carbon $date, Door $door, string $doorcode, ?string $commandString): int;
/**
* Get reason for the allow/deny. Will not be called before check, and should only be called if
* check has already been called and returned either ALLOW or DENY
*
* @return string
*/
public function getReason(): string;
} }
...@@ -24,6 +24,8 @@ class CommandAuthorizer implements AccessAuthorizer ...@@ -24,6 +24,8 @@ class CommandAuthorizer implements AccessAuthorizer
*/ */
protected CommandHandler $commandHandler; protected CommandHandler $commandHandler;
protected string $reason = '';
public function __construct(Authorizer $authorizer, CommandHandler $commandHandler) public function __construct(Authorizer $authorizer, CommandHandler $commandHandler)
{ {
$this->authorizer = $authorizer; $this->authorizer = $authorizer;
...@@ -40,15 +42,26 @@ class CommandAuthorizer implements AccessAuthorizer ...@@ -40,15 +42,26 @@ class CommandAuthorizer implements AccessAuthorizer
try { try {
if ($this->authorizer->allowsOne([Permissions::DOOR_COMMANDER, Permissions::MANAGE_DOORS]) && if ($this->authorizer->allowsOne([Permissions::DOOR_COMMANDER, Permissions::MANAGE_DOORS]) &&
$this->commandHandler->handle($user, $door, $commandString)) { $this->commandHandler->handle($user, $door, $commandString)) {
$this->reason = 'Accepted door command: ' . $commandString;
return self::ALLOW; return self::ALLOW;
} }
} catch (EntityNotFoundException $e) { } catch (EntityNotFoundException $e) {
// Do nothing and let it deny as the permissions could not be found for the user // Do nothing and let it deny as the permissions could not be found for the user
} }
$this->reason = 'Unknown command, or user does not have permissions to execute the command: ' . $commandString;
return self::DENY; return self::DENY;
} }
return self::CONTINUE; return self::CONTINUE;
} }
/**
* @inheritDoc
*/
public function getReason(): string
{
return $this->reason;
}
} }
...@@ -32,7 +32,7 @@ class CommanderAuthorizer implements AccessAuthorizer ...@@ -32,7 +32,7 @@ class CommanderAuthorizer implements AccessAuthorizer
$this->authorizer->setCurrentUserId($user->getId()); $this->authorizer->setCurrentUserId($user->getId());
try { try {
if ($this->authorizer->allowsOne([Permissions::DOOR_COMMANDER, Permissions::MANAGE_DOORS])) { if ($this->authorizer->allows(Permissions::DOOR_COMMANDER)) {
return self::ALLOW; return self::ALLOW;
} }
} catch (EntityNotFoundException $e) { } catch (EntityNotFoundException $e) {
...@@ -42,4 +42,13 @@ class CommanderAuthorizer implements AccessAuthorizer ...@@ -42,4 +42,13 @@ class CommanderAuthorizer implements AccessAuthorizer
return self::CONTINUE; return self::CONTINUE;
} }
/**
* @inheritDoc
*/
public function getReason(): string
{
// We only allow, we don't deny. Can be only one reason.
return 'User is a door commander.';
}
} }
...@@ -21,4 +21,13 @@ class ExpiredAuthorizer implements AccessAuthorizer ...@@ -21,4 +21,13 @@ class ExpiredAuthorizer implements AccessAuthorizer
return self::CONTINUE; return self::CONTINUE;
} }
/**
* @inheritDoc
*/
public function getReason(): string
{
// We only deny, can only be one reason
return 'User has expired.';
}
} }
...@@ -24,6 +24,8 @@ class OpenModeAuthorizer implements AccessAuthorizer ...@@ -24,6 +24,8 @@ class OpenModeAuthorizer implements AccessAuthorizer
*/ */
protected RecurrenceSetRepository $recurrenceSet; protected RecurrenceSetRepository $recurrenceSet;
protected int $allowedSchedule = 0;
public function __construct(DoorScheduleRepository $doorSchedules, RecurrenceSetRepository $recurrenceSet) public function __construct(DoorScheduleRepository $doorSchedules, RecurrenceSetRepository $recurrenceSet)
{ {
$this->doorSchedules = $doorSchedules; $this->doorSchedules = $doorSchedules;
...@@ -45,6 +47,7 @@ class OpenModeAuthorizer implements AccessAuthorizer ...@@ -45,6 +47,7 @@ class OpenModeAuthorizer implements AccessAuthorizer
} }
if ($this->recurrenceSet->occurrenceHappeningAtDate($date, $schedule->getDuration())) { if ($this->recurrenceSet->occurrenceHappeningAtDate($date, $schedule->getDuration())) {
$this->allowedSchedule = $schedule->getId();
return self::ALLOW; return self::ALLOW;
} }
} }
...@@ -52,4 +55,13 @@ class OpenModeAuthorizer implements AccessAuthorizer ...@@ -52,4 +55,13 @@ class OpenModeAuthorizer implements AccessAuthorizer
return self::CONTINUE; return self::CONTINUE;
} }
/**
* @inheritDoc
*/
public function getReason(): string
{
// We only allow, can only be one reason.
return 'Allowed by open schedule: ' . $this->allowedSchedule;
}
} }
...@@ -17,6 +17,8 @@ class OverrideAuthorizer implements AccessAuthorizer ...@@ -17,6 +17,8 @@ class OverrideAuthorizer implements AccessAuthorizer
*/ */
protected OverridesRepository $overrides; protected OverridesRepository $overrides;
protected string $reason = '';
public function __construct(OverridesRepository $overrides) public function __construct(OverridesRepository $overrides)
{ {
$this->overrides = $overrides; $this->overrides = $overrides;
...@@ -36,11 +38,21 @@ class OverrideAuthorizer implements AccessAuthorizer ...@@ -36,11 +38,21 @@ class OverrideAuthorizer implements AccessAuthorizer
switch ($active->getType()) { switch ($active->getType()) {
case Override::TYPE_LOCKED: case Override::TYPE_LOCKED:
$this->reason = 'Door is in override locked mode.';
return self::DENY; return self::DENY;
case Override::TYPE_OPEN: case Override::TYPE_OPEN:
$this->reason = 'Door is in override open mode.';
return self::ALLOW; return self::ALLOW;
default: default:
return self::CONTINUE; return self::CONTINUE;
} }
} }
/**
* @inheritDoc
*/
public function getReason(): string
{
return $this->reason;
}
} }
...@@ -30,6 +30,8 @@ class ScheduleAuthorizer implements AccessAuthorizer ...@@ -30,6 +30,8 @@ class ScheduleAuthorizer implements AccessAuthorizer
*/ */
protected GroupScheduleRepository $groupSchedule; protected GroupScheduleRepository $groupSchedule;
protected int $allowedSchedule = 0;
public function __construct( public function __construct(
GroupScheduleRepository $groupSchedule, GroupScheduleRepository $groupSchedule,
RecurrenceSetRepository $recurrenceSet, RecurrenceSetRepository $recurrenceSet,
...@@ -63,6 +65,7 @@ class ScheduleAuthorizer implements AccessAuthorizer ...@@ -63,6 +65,7 @@ class ScheduleAuthorizer implements AccessAuthorizer
} }
if ($this->recurrenceSet->occurrenceHappeningAtDate($date, $schedule->getDuration())) { if ($this->recurrenceSet->occurrenceHappeningAtDate($date, $schedule->getDuration())) {
$this->allowedSchedule = $schedule->getId();
return self::ALLOW; return self::ALLOW;
} }
} }
...@@ -71,4 +74,13 @@ class ScheduleAuthorizer implements AccessAuthorizer ...@@ -71,4 +74,13 @@ class ScheduleAuthorizer implements AccessAuthorizer
return self::CONTINUE; return self::CONTINUE;
} }
/**
* @inheritDoc
*/
public function getReason(): string
{
// We only allow, can only be one reason.
return 'Allowed by user access schedule: ' . $this->allowedSchedule;
}
} }
...@@ -21,6 +21,7 @@ class WebPresenter extends BasePresenter implements Presenter ...@@ -21,6 +21,7 @@ class WebPresenter extends BasePresenter implements Presenter
'user_id' => $entry->getUserId(), 'user_id' => $entry->getUserId(),
'door_id' => $entry->getDoorId(), 'door_id' => $entry->getDoorId(),
'success' => $entry->wasSuccessful() ? 'Allowed' : 'Denied', 'success' => $entry->wasSuccessful() ? 'Allowed' : 'Denied',
'reason' => $entry->getReason(),
'created_at' => self::formatDateTime($entry->getCreatedAt(), self::HUMAN_DATE_FORMAT) ?? 'Unknown', 'created_at' => self::formatDateTime($entry->getCreatedAt(), self::HUMAN_DATE_FORMAT) ?? 'Unknown',
]; ];
}, $responseModel->getEntries()); }, $responseModel->getEntries());
...@@ -36,6 +37,7 @@ class WebPresenter extends BasePresenter implements Presenter ...@@ -36,6 +37,7 @@ class WebPresenter extends BasePresenter implements Presenter
'User' => 'user_id', 'User' => 'user_id',
'Door' => 'door_id', 'Door' => 'door_id',
'Status' => 'success', 'Status' => 'success',
'Reason' => 'reason',
'Entry Time' => 'created_at', 'Entry Time' => 'created_at',
], ],
]; ];
......
...@@ -78,9 +78,9 @@ class EntriesDatabaseTest extends DatabaseTestCase ...@@ -78,9 +78,9 @@ class EntriesDatabaseTest extends DatabaseTestCase
$u1 = $this->createUser('u1'); $u1 = $this->createUser('u1');
$d2 = $this->createDoor('d2'); $d2 = $this->createDoor('d2');
$u2 = $this->createUser('u2'); $u2 = $this->createUser('u2');