Commit c9728c92 authored by Jacob Priddy's avatar Jacob Priddy 👌
Browse files

Add override check in door access

parent d2fa260b
......@@ -12,6 +12,7 @@ use Source\Gateways\Entries\EntriesRepository;
use Source\Gateways\Attempts\AttemptsRepository;
use Source\UseCases\DoorUserGroup\DoorUserGroupMapUseCase;
use Source\UseCases\Door\Access\Authorizers\DoorOpenModeCheck\DoorOpenModeCheck;
use Source\UseCases\Door\Access\Authorizers\DoorOverrideCheck\DoorOverrideCheck;
use Source\UseCases\Door\Access\Authorizers\UserDoorcodeCheck\UserDoorcodeCheck;
use Source\UseCases\Door\Access\Authorizers\GroupScheduleCheck\GroupScheduleCheck;
......@@ -49,11 +50,17 @@ class Access implements AccessUseCase
*/
protected EntriesRepository $entries;
/**
* @var \Source\UseCases\Door\Access\Authorizers\DoorOverrideCheck\DoorOverrideCheck
*/
protected DoorOverrideCheck $overrideCheck;
public function __construct(
DoorOpenModeCheck $openModeCheck,
UserDoorcodeCheck $userCheck,
GroupScheduleCheck $scheduleCheck,
DoorUserGroupMapUseCase $doorUserMapper,
DoorOverrideCheck $overrideCheck,
AttemptsRepository $attempts,
EntriesRepository $entries
) {
......@@ -63,6 +70,7 @@ class Access implements AccessUseCase
$this->doorUserMapper = $doorUserMapper;
$this->attempts = $attempts;
$this->entries = $entries;
$this->overrideCheck = $overrideCheck;
}
protected function logUserAccess(string $doorId, string $userId, bool $success): void
......@@ -102,17 +110,21 @@ class Access implements AccessUseCase
* 1. Check doorId
* - door (continue)
* - no door (deny)
* 2. Check for open mode on the door
* 2. Check for door overrides
* - door in open mode (allow)
* - door in closed mode (deny)
* - no override (continue)
* 3. Check for open mode on the door
* - open mode on (allow)
* - open mode off (continue)
* 3. Get user from door code
* 4. Get user from door code
* - user exists (continue)
* - no user found (deny and log attempt)
* 4. Get group intersection of doors and users
* 5. Foreach group check all user access schedules
* 5. Get group intersection of doors and users
* 6. Foreach group check all user access schedules
* - schedule has currently active event (allow and log entry)
* - schedule doe snot have currently active event (continue)
* 6. Now all allowing conditions hit (deny and log entry)
* 7. Now all allowing conditions hit (deny and log entry)
*/
if (!$doorId) {
throw new AuthenticationException();
......@@ -120,11 +132,30 @@ class Access implements AccessUseCase
$now = Carbon::now();
/*
* Check overrides
*/
$overrideStatus = $this->overrideCheck->checkForOverrides($doorId, $now);
if ($overrideStatus === DoorOverrideCheck::OPEN) {
return;
}
if ($overrideStatus === DoorOverrideCheck::CLOSED) {
throw new AuthorizationException();
}
/*
* Check for open mode
*/
if ($this->openModeCheck->checkOpenMode($doorId, $now)) {
// No logging needed to be done as we are in open mode. Just return without throwing any exceptions
return;
}
/*
* Check for user
*/
if (!($user = $this->userCheck->checkDoorCode($doorcode))) {
$this->logDoorAttempt($doorId);
throw new AuthorizationException();
......@@ -146,6 +177,9 @@ class Access implements AccessUseCase
// throw new AuthorizationException();
// }
/*
* Check for user schedule
*/
if ($this->scheduleCheck->checkScheduleForGroups($groups, $now)) {
// Log the successful entry
$this->logUserAccess($doorId, $userId, true);
......
......@@ -8,15 +8,18 @@ use Source\Gateways\Users\UsersRepository;
use Source\Gateways\Entries\EntriesRepository;
use Illuminate\Contracts\Foundation\Application;
use Source\Gateways\Attempts\AttemptsRepository;
use Source\Gateways\Overrides\OverridesRepository;
use Source\Gateways\Schedules\SchedulesRepository;
use Illuminate\Contracts\Support\DeferrableProvider;
use Source\Gateways\DoorSchedule\DoorScheduleRepository;
use Source\Gateways\RecurrenceSet\RecurrenceSetRepository;
use Source\UseCases\DoorUserGroup\DoorUserGroupMapUseCase;
use Source\UseCases\Door\Access\Authorizers\DoorOpenModeCheck\DoorOpenModeCheck;
use Source\UseCases\Door\Access\Authorizers\DoorOverrideCheck\DoorOverrideCheck;
use Source\UseCases\Door\Access\Authorizers\UserDoorcodeCheck\UserDoorcodeCheck;
use Source\UseCases\Door\Access\Authorizers\GroupScheduleCheck\GroupScheduleCheck;
use Source\UseCases\Door\Access\Authorizers\DoorOpenModeCheck\DefaultDoorOpenModeCheck;
use Source\UseCases\Door\Access\Authorizers\DoorOverrideCheck\DefaultDoorOverrideCheck;
use Source\UseCases\Door\Access\Authorizers\UserDoorcodeCheck\DefaultUserDoorcodeCheck;
use Source\UseCases\Door\Access\Authorizers\GroupScheduleCheck\DefaultGroupScheduleCheck;
......@@ -53,12 +56,17 @@ class AccessUseCaseServiceProvider extends ServiceProvider implements Deferrable
);
});
$this->app->bind(DoorOverrideCheck::class, static function (Application $app) {
return new DefaultDoorOverrideCheck($app->make(OverridesRepository::class));
});
$this->app->bind(AccessUseCase::class, static function (Application $app) {
return new Access(
$app->make(DoorOpenModeCheck::class),
$app->make(UserDoorcodeCheck::class),
$app->make(GroupScheduleCheck::class),
$app->make(DoorUserGroupMapUseCase::class),
$app->make(DoorOverrideCheck::class),
$app->make(AttemptsRepository::class),
$app->make(EntriesRepository::class)
);
......@@ -84,6 +92,7 @@ class AccessUseCaseServiceProvider extends ServiceProvider implements Deferrable
DoorOpenModeCheck::class,
GroupScheduleCheck::class,
UserDoorcodeCheck::class,
DoorOverrideCheck::class,
];
}
}
<?php
namespace Source\UseCases\Door\Access\Authorizers\DoorOverrideCheck;
use Carbon\Carbon;
use Source\Entities\Override;
use Source\Gateways\Overrides\OverridesRepository;
class DefaultDoorOverrideCheck implements DoorOverrideCheck
{
/**
* @var \Source\Gateways\Overrides\OverridesRepository
*/
protected OverridesRepository $overrides;
/**
* @param \Source\Gateways\Overrides\OverridesRepository $overrides
*/
public function __construct(OverridesRepository $overrides)
{
$this->overrides = $overrides;
}
/**
* @inheritDoc
*/
public function checkForOverrides(string $doorId, Carbon $date): int
{
$active = $this->overrides->activeOverrideForDoor($doorId, $date);
if (!$active) {
return DoorOverrideCheck::NO_ACTION;
}
switch ($active->getType()) {
case Override::TYPE_LOCKED:
return DoorOverrideCheck::CLOSED;
case Override::TYPE_OPEN:
return DoorOverrideCheck::OPEN;
default:
return DoorOverrideCheck::NO_ACTION;
}
}
}
<?php
namespace Source\UseCases\Door\Access\Authorizers\DoorOverrideCheck;
use Carbon\Carbon;
interface DoorOverrideCheck
{
public const OPEN = 0;
public const CLOSED = 1;
public const NO_ACTION = 2;
/**
* Returns one of the constants defined on DoorOverrideCheck
*
* @param string $doorId
* @param \Carbon\Carbon $date
* @return mixed
*/
public function checkForOverrides(string $doorId, Carbon $date): int;
}
<?php
namespace Tests\Doubles\Access;
use Carbon\Carbon;
use Source\UseCases\Door\Access\Authorizers\DoorOverrideCheck\DoorOverrideCheck;
class DoorOverrideCheckStub implements DoorOverrideCheck
{
public int $return = DoorOverrideCheck::NO_ACTION;
/**
* @inheritDoc
*/
public function checkForOverrides(string $doorId, Carbon $date): int
{
return $this->return;
}
}
<?php
namespace Tests\Unit\Source\UseCases\Door\Access\Authorizers;
use Carbon\Carbon;
use Source\Entities\Override;
use PHPUnit\Framework\TestCase;
use Source\Gateways\Overrides\InMemoryOverridesRepository;
use Source\UseCases\Door\Access\Authorizers\DoorOverrideCheck\DoorOverrideCheck;
use Source\UseCases\Door\Access\Authorizers\DoorOverrideCheck\DefaultDoorOverrideCheck;
class DoorOverrideCheckTest extends TestCase
{
/**
* @var \Source\Gateways\Overrides\InMemoryOverridesRepository
*/
protected InMemoryOverridesRepository $overrides;
/**
* @var \Source\UseCases\Door\Access\Authorizers\DoorOverrideCheck\DefaultDoorOverrideCheck
*/
protected DefaultDoorOverrideCheck $authorizer;
public function setUp(): void
{
parent::setUp();
$this->overrides = new InMemoryOverridesRepository();
$this->authorizer = new DefaultDoorOverrideCheck($this->overrides);
}
/**
* @test
*/
public function no_action_if_no_overrides(): void
{
$this->assertEquals(DoorOverrideCheck::NO_ACTION, $this->authorizer->checkForOverrides('1', Carbon::now()));
}
/**
* @test
*/
public function locks_if_lock_override(): void
{
$this->overrides->addOverride(new Override(
0,
1,
1,
Override::TYPE_LOCKED,
Carbon::now()->subMinute(),
Carbon::now()->addMinute()
));
$this->assertEquals(DoorOverrideCheck::CLOSED, $this->authorizer->checkForOverrides('1', Carbon::now()));
}
/**
* @test
*/
public function unlocks_if_open_override(): void
{
$this->overrides->addOverride(new Override(
0,
1,
1,
Override::TYPE_OPEN,
Carbon::now()->subMinute(),
Carbon::now()->addMinute()
));
$this->assertEquals(DoorOverrideCheck::OPEN, $this->authorizer->checkForOverrides('1', Carbon::now()));
}
}
......@@ -11,10 +11,12 @@ use Source\Exceptions\AuthorizationException;
use Source\Exceptions\AuthenticationException;
use Tests\Doubles\Access\DoorUserGroupMapStub;
use Tests\Doubles\Access\DoorOpenModeCheckStub;
use Tests\Doubles\Access\DoorOverrideCheckStub;
use Tests\Doubles\Access\UserDoorcodeCheckStub;
use Tests\Doubles\Access\GroupScheduleCheckStub;
use Source\Gateways\Entries\InMemoryEntriesRepository;
use Source\Gateways\Attempts\InMemoryAttemptsRepository;
use Source\UseCases\Door\Access\Authorizers\DoorOverrideCheck\DoorOverrideCheck;
class UseCaseTest extends TestCase
{
......@@ -57,6 +59,11 @@ class UseCaseTest extends TestCase
*/
protected DoorUserGroupMapStub $doorUserGroupMap;
/**
* @var \Tests\Doubles\Access\DoorOverrideCheckStub
*/
protected DoorOverrideCheckStub $overrideCheck;
public function setUp(): void
{
parent::setUp();
......@@ -64,6 +71,7 @@ class UseCaseTest extends TestCase
$this->openModeCheck = new DoorOpenModeCheckStub();
$this->userDoorcodeCheck = new UserDoorcodeCheckStub();
$this->groupScheduleCheck = new GroupScheduleCheckStub();
$this->overrideCheck = new DoorOverrideCheckStub();
$this->doorUserGroupMap = new DoorUserGroupMapStub();
$this->attempts = new InMemoryAttemptsRepository();
$this->entries = new InMemoryEntriesRepository();
......@@ -72,6 +80,7 @@ class UseCaseTest extends TestCase
$this->userDoorcodeCheck,
$this->groupScheduleCheck,
$this->doorUserGroupMap,
$this->overrideCheck,
$this->attempts,
$this->entries,
);
......@@ -184,4 +193,32 @@ class UseCaseTest extends TestCase
$this->assertCount(1, $this->entries->all());
$this->assertTrue($this->entries->all()[0]->wasSuccessful());
}
/**
* @test
* @throws \Source\Exceptions\AuthenticationException
* @throws \Source\Exceptions\AuthorizationException
*/
public function it_respects_lock_override(): void
{
$this->expectException(AuthorizationException::class);
$this->overrideCheck->return = DoorOverrideCheck::CLOSED;
$this->userDoorcodeCheck->setReturnUser(self::createValidUser('doorcode'));
$this->handleTest(self::DOOR_ID, 'doorcode');
}
/**
* @test
* @throws \Source\Exceptions\AuthenticationException
* @throws \Source\Exceptions\AuthorizationException
*/
public function it_respects_open_override(): void
{
$this->overrideCheck->return = DoorOverrideCheck::OPEN;
$this->handleTest(self::DOOR_ID, '');
// It does nothing on success, otherwise it throws an exception
$this->assertTrue(true);
}
}
Supports Markdown
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