Commit 5abd428c authored by Jacob Priddy's avatar Jacob Priddy 👌

Merge branch 'add-reason-field-to-entry' into 'master'

add reason field for entries

See merge request !94
parents ca5d6635 73a8a555
Pipeline #12907 passed with stages
in 3 minutes and 56 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
*/
protected bool $success;
/**
* @var string
*/
protected string $reason;
public function __construct(
int $id,
int $userId,
int $doorId,
bool $success,
string $reason,
?Carbon $createdAt = null,
?Carbon $updatedAt = null
) {
$this->id = $id;
$this->userId = $userId;
$this->doorId = $doorId;
$this->reason = $reason;
$this->success = $success;
$this->createdAt = $createdAt;
$this->updatedAt = $updatedAt;
......@@ -102,8 +109,8 @@ class Entry
}
/**
* @param \Carbon\Carbon $begin
* @param \Carbon\Carbon $end
* @param \Carbon\Carbon|null $begin
* @param \Carbon\Carbon|null $end
* @return bool
*/
public function isBetween(?Carbon $begin, ?Carbon $end): bool
......@@ -152,4 +159,12 @@ class Entry
return $this->getDoorId() === (int)$doorId;
}
/**
* @return string
*/
public function getReason(): string
{
return $this->reason;
}
}
......@@ -22,6 +22,7 @@ class DatabaseEntriesRepository implements EntriesRepository
$entry->getAttribute('user_id'),
$entry->getAttribute('door_id'),
$entry->getAttribute('success'),
$entry->getAttribute('reason'),
$entry->getAttribute('created_at'),
$entry->getAttribute('updated_at')
);
......@@ -35,6 +36,7 @@ class DatabaseEntriesRepository implements EntriesRepository
$e = new \App\Entry();
$e->setAttribute('user_id', $entry->getUserId());
$e->setAttribute('door_id', $entry->getDoorId());
$e->setAttribute('reason', $entry->getReason());
$e->setAttribute('success', $entry->wasSuccessful());
if (!$e->save()) {
......
......@@ -20,6 +20,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository
LocalUsersRepository::getEngineeringLabAccessStudent()->getId(),
LocalDoorsRepository::getAmazonDoor()->getId(),
false,
'reason',
Carbon::now()->subDays(1)
));
......@@ -28,6 +29,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository
LocalUsersRepository::getEngineeringLabAccessStudent()->getId(),
LocalDoorsRepository::getTheBatCave()->getId(),
true,
'reason',
Carbon::now()->subDays(1)
));
......@@ -36,6 +38,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository
LocalUsersRepository::getComputerScienceStudent()->getId(),
LocalDoorsRepository::getAmazonDoor()->getId(),
true,
'reason',
Carbon::now()->subMinutes(4)
));
......@@ -44,6 +47,7 @@ class LocalEntriesRepository extends InMemoryEntriesRepository
LocalUsersRepository::getComputerScienceStudent()->getId(),
LocalDoorsRepository::getTheBatCave()->getId(),
false,
'reason',
Carbon::now()->subYear()
));
}
......
......@@ -185,6 +185,7 @@ abstract class BasePresenter
'user_id' => $entry->getUserId(),
'door_id' => $entry->getDoorId(),
'success' => $entry->wasSuccessful(),
'reason' => $entry->getReason(),
'created_at' => self::formatDateTime($entry->getCreatedAt()),
];
}
......
......@@ -61,14 +61,16 @@ class Access implements AccessUseCase
* @param string $doorId
* @param string $userId
* @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(
0,
$userId,
$doorId,
$success
$success,
$reason
));
}
......@@ -83,22 +85,24 @@ class Access implements AccessUseCase
/**
* @param \Source\Entities\User|null $user
* @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) {
$this->logUserAccess($doorId, $user->getId(), true);
$this->logUserAccess($doorId, $user->getId(), true, $reason);
}
}
/**
* @param \Source\Entities\User|null $user
* @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) {
$this->logUserAccess($doorId, $user->getId(), false);
$this->logUserAccess($doorId, $user->getId(), false, $reason);
} else {
$this->logDoorAttempt($doorId);
}
......@@ -141,10 +145,10 @@ class Access implements AccessUseCase
foreach ($this->authorizers as $authorizer) {
switch ($authorizer->check($user, $date, $door, $code, $command)) {
case AccessAuthorizer::ALLOW:
$this->logAllow($user, $door->getId());
$this->logAllow($user, $door->getId(), $authorizer->getReason());
return;
case AccessAuthorizer::DENY:
$this->logDeny($user, $door->getId());
$this->logDeny($user, $door->getId(), $authorizer->getReason());
throw new AuthorizationException();
case AccessAuthorizer::CONTINUE:
// Fall through to default.
......@@ -154,7 +158,7 @@ class Access implements AccessUseCase
}
// 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();
}
}
......@@ -20,8 +20,16 @@ interface AccessAuthorizer
* @param \Carbon\Carbon $date
* @param \Source\Entities\Door $door
* @param string $doorcode
* @param string $commandString
* @param string|null $commandString
* @return 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
*/
protected CommandHandler $commandHandler;
protected string $reason = '';
public function __construct(Authorizer $authorizer, CommandHandler $commandHandler)
{
$this->authorizer = $authorizer;
......@@ -40,15 +42,26 @@ class CommandAuthorizer implements AccessAuthorizer
try {
if ($this->authorizer->allowsOne([Permissions::DOOR_COMMANDER, Permissions::MANAGE_DOORS]) &&
$this->commandHandler->handle($user, $door, $commandString)) {
$this->reason = 'Accepted door command: ' . $commandString;
return self::ALLOW;
}
} catch (EntityNotFoundException $e) {
// 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::CONTINUE;
}
/**
* @inheritDoc
*/
public function getReason(): string
{
return $this->reason;
}
}
......@@ -32,7 +32,7 @@ class CommanderAuthorizer implements AccessAuthorizer
$this->authorizer->setCurrentUserId($user->getId());
try {
if ($this->authorizer->allowsOne([Permissions::DOOR_COMMANDER, Permissions::MANAGE_DOORS])) {
if ($this->authorizer->allows(Permissions::DOOR_COMMANDER)) {
return self::ALLOW;
}
} catch (EntityNotFoundException $e) {
......@@ -42,4 +42,13 @@ class CommanderAuthorizer implements AccessAuthorizer
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
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
*/
protected RecurrenceSetRepository $recurrenceSet;
protected int $allowedSchedule = 0;
public function __construct(DoorScheduleRepository $doorSchedules, RecurrenceSetRepository $recurrenceSet)
{
$this->doorSchedules = $doorSchedules;
......@@ -45,6 +47,7 @@ class OpenModeAuthorizer implements AccessAuthorizer
}
if ($this->recurrenceSet->occurrenceHappeningAtDate($date, $schedule->getDuration())) {
$this->allowedSchedule = $schedule->getId();
return self::ALLOW;
}
}
......@@ -52,4 +55,13 @@ class OpenModeAuthorizer implements AccessAuthorizer
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
*/
protected OverridesRepository $overrides;
protected string $reason = '';
public function __construct(OverridesRepository $overrides)
{
$this->overrides = $overrides;
......@@ -36,11 +38,21 @@ class OverrideAuthorizer implements AccessAuthorizer
switch ($active->getType()) {
case Override::TYPE_LOCKED:
$this->reason = 'Door is in override locked mode.';
return self::DENY;
case Override::TYPE_OPEN:
$this->reason = 'Door is in override open mode.';
return self::ALLOW;
default:
return self::CONTINUE;
}
}
/**
* @inheritDoc
*/
public function getReason(): string
{
return $this->reason;
}
}
......@@ -30,6 +30,8 @@ class ScheduleAuthorizer implements AccessAuthorizer
*/
protected GroupScheduleRepository $groupSchedule;
protected int $allowedSchedule = 0;
public function __construct(
GroupScheduleRepository $groupSchedule,
RecurrenceSetRepository $recurrenceSet,
......@@ -63,6 +65,7 @@ class ScheduleAuthorizer implements AccessAuthorizer
}
if ($this->recurrenceSet->occurrenceHappeningAtDate($date, $schedule->getDuration())) {
$this->allowedSchedule = $schedule->getId();
return self::ALLOW;
}
}
......@@ -71,4 +74,13 @@ class ScheduleAuthorizer implements AccessAuthorizer
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
'user_id' => $entry->getUserId(),
'door_id' => $entry->getDoorId(),
'success' => $entry->wasSuccessful() ? 'Allowed' : 'Denied',
'reason' => $entry->getReason(),
'created_at' => self::formatDateTime($entry->getCreatedAt(), self::HUMAN_DATE_FORMAT) ?? 'Unknown',
];
}, $responseModel->getEntries());
......@@ -36,6 +37,7 @@ class WebPresenter extends BasePresenter implements Presenter
'User' => 'user_id',
'Door' => 'door_id',
'Status' => 'success',
'Reason' => 'reason',
'Entry Time' => 'created_at',
],
];
......
......@@ -78,9 +78,9 @@ class EntriesDatabaseTest extends DatabaseTestCase
$u1 = $this->createUser('u1');
$d2 = $this->createDoor('d2');
$u2 = $this->createUser('u2');
$this->repository->add(new Entry(0, $u1->getId(), $d1->getId(), true));
$this->repository->add(new Entry(0, $u2->getId(), $d1->getId(), true));
$this->repository->add(new Entry(0, $u2->getId(), $d2->getId(), false));
$this->repository->add(new Entry(0, $u1->getId(), $d1->getId(), true, 'reason'));
$this->repository->add(new Entry(0, $u2->getId(), $d1->getId(), true, 'reason'));
$this->repository->add(new Entry(0, $u2->getId(), $d2->getId(), false, 'reason'));
self::assertCount(2, $this->repository->get($begin, $end, null, $d1->getId()));
self::assertCount(2, $this->repository->get($begin, $end, $u2->getId(), null));
self::assertCount(1, $this->repository->get($begin, $end, $u2->getId(), $d1->getId()));
......@@ -100,9 +100,9 @@ class EntriesDatabaseTest extends DatabaseTestCase
$e->setAttribute('user_id', $u1->getId());
$e->setAttribute('door_id', $d2->getId());
$e->setCreatedAt(Carbon::now()->subDays(100));
$this->repository->add(new Entry(0, $u1->getId(), $d1->getId(), true));
$this->repository->add(new Entry(0, $u2->getId(), $d1->getId(), true));
$this->repository->add(new Entry(0, $u2->getId(), $d2->getId(), false));
$this->repository->add(new Entry(0, $u1->getId(), $d1->getId(), true, 'reason'));
$this->repository->add(new Entry(0, $u2->getId(), $d1->getId(), true, 'reason'));
$this->repository->add(new Entry(0, $u2->getId(), $d2->getId(), false, 'reason'));
self::assertCount(3, $this->repository->get(Carbon::now()->subDays(1), Carbon::now()->addDay()));
}
}
......@@ -32,6 +32,11 @@ class DoorAccessAuthorizerStub implements AccessAuthorizer
return $this->commandString;
}
public function setReason(string $reason): void
{
$this->reason = $reason;
}
/**
* @inheritDoc
*/
......@@ -41,4 +46,12 @@ class DoorAccessAuthorizerStub implements AccessAuthorizer
$this->commandString = $commandString;
return $this->checkReturn;
}
/**
* @inheritDoc
*/
public function getReason(): string
{
return 'reason goes here';
}
}
......@@ -63,8 +63,8 @@ class EntriesGetApiTest extends AuthenticatesWithApplicationTestCase
*/
public function it_filters_by_start(): void
{
$this->entries->add(new Entry(1, 1, 2, true, new Carbon('2020-01-02')));
$this->entries->add(new Entry(2, 1, 2, true, new Carbon('2020-04-02')));
$this->entries->add(new Entry(1, 1, 2, true, 'reason', new Carbon('2020-01-02')));
$this->entries->add(new Entry(2, 1, 2, true, 'reason', new Carbon('2020-04-02')));
$this->handleTest('2020-02-05', null, null, null);
$this->response->assertJsonCount(1, 'data');
......@@ -76,8 +76,8 @@ class EntriesGetApiTest extends AuthenticatesWithApplicationTestCase
*/
public function it_filters_by_end(): void
{
$this->entries->add(new Entry(1, 1, 2, true, new Carbon('2020-01-02')));
$this->entries->add(new Entry(2, 1, 2, true, new Carbon('2020-04-02')));
$this->entries->add(new Entry(1, 1, 2, true, 'reason', new Carbon('2020-01-02')));
$this->entries->add(new Entry(2, 1, 2, true, 'reason', new Carbon('2020-04-02')));
$this->handleTest(null, '2020-02-05', null, null);
$this->response->assertJsonCount(1, 'data');
$this->response->assertJson(['data' => [['id' => 1]]]);
......@@ -88,8 +88,8 @@ class EntriesGetApiTest extends AuthenticatesWithApplicationTestCase
*/
public function it_filters_by_user_id(): void
{
$this->entries->add(new Entry(1, 1, 2, true));
$this->entries->add(new Entry(2, 2, 1, true));
$this->entries->add(new Entry(1, 1, 2, true, 'reason'));
$this->entries->add(new Entry(2, 2, 1, true, 'reason'));
$this->handleTest(null, null, null, 1);
$this->response->assertJsonCount(1, 'data');
$this->response->assertJson(['data' => [['id' => 1]]]);
......@@ -100,8 +100,8 @@ class EntriesGetApiTest extends AuthenticatesWithApplicationTestCase
*/
public function it_filters_by_door_id(): void
{
$this->entries->add(new Entry(1, 1, 2, true));
$this->entries->add(new Entry(2, 2, 1, true));
$this->entries->add(new Entry(1, 1, 2, true, 'reason'));
$this->entries->add(new Entry(2, 2, 1, true, 'reason'));
$this->handleTest(null, null, 1, null);
$this->response->assertJsonCount(1, 'data');
$this->response->assertJson(['data' => [['id' => 2]]]);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment