Commit 2192d7ca authored by Jacob Priddy's avatar Jacob Priddy 👌

Documentation for door and auth routes. Also make in memory

implmentation of overrides more realistic
parent 0ab32134
...@@ -13,7 +13,6 @@ use Source\UseCases\Users\Authenticate\APIPresenter; ...@@ -13,7 +13,6 @@ use Source\UseCases\Users\Authenticate\APIPresenter;
use Source\UseCases\Users\Authenticate\AuthenticateUseCase; use Source\UseCases\Users\Authenticate\AuthenticateUseCase;
use Source\UseCases\Users\Authenticate\UserCreationException; use Source\UseCases\Users\Authenticate\UserCreationException;
/** /**
* @group Authentication * @group Authentication
* *
......
...@@ -14,6 +14,12 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse; ...@@ -14,6 +14,12 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Source\UseCases\Door\UpdateBinary\UpdateBinaryUseCase; use Source\UseCases\Door\UpdateBinary\UpdateBinaryUseCase;
use Source\UseCases\Door\StatusResponse\StatusResponseUseCase; use Source\UseCases\Door\StatusResponse\StatusResponseUseCase;
/**
* @group Door Routes
*
* Set of routes for door clients wanting to protect access to doors.
* Includes routes for verifying a doorcode as well as getting open mode times from schedules and overrides
*/
class DoorController extends ApiController class DoorController extends ApiController
{ {
/** /**
...@@ -35,15 +41,22 @@ class DoorController extends ApiController ...@@ -35,15 +41,22 @@ class DoorController extends ApiController
/** /**
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException
*/ */
protected function respondStatus(): JsonResponse protected function respondStatus(): JsonResponse
{ {
$this->validate($this->request, [
'foresight' => 'integer',
]);
$foresight = $this->request->input('foresight') ?? config('app.status_foresight');
$presenter = new JsonPresenter(); $presenter = new JsonPresenter();
$this->response->getStatusForDoor( $this->response->getStatusForDoor(
$this->doorGuard->id(), $this->doorGuard->id(),
Carbon::now(), Carbon::now(),
Carbon::now()->addMinutes(config('app.status_foresight')), Carbon::now()->addMinutes($foresight),
$presenter $presenter
); );
...@@ -51,11 +64,27 @@ class DoorController extends ApiController ...@@ -51,11 +64,27 @@ class DoorController extends ApiController
} }
/** /**
* Door Access
*
* Checks to see if the given doorcode can currently access the authenticated door.
* Also processes commands that are separated by a '*' from the doorcode. If a command is accepted a 200 is
* returned. If a command is rejected, a 403 is given.
*
* @authenticated
* @urlParam doorcode required The doorcode to query. Example: 123456*00110
* @queryParam Number of minutes ahead of now to get the open mode times for
*
* @response 422 {"message":"The given data was invalid.","errors":{"foresight":["The foresight must be an
* integer."]}}
* @response 403
* {"events":[{"begins_at":"2020-06-03T11:54:07-07:00","ends_at":"2020-06-03T11:55:07-07:00"},{"begins_at":"2020-06-03T11:55:07-07:00","ends_at":"2020-06-03T12:15:07-07:00"},{"begins_at":"2020-06-03T21:54:07-07:00","ends_at":"2020-06-03T21:55:07-07:00"}]}
*
* @param string $doorcode * @param string $doorcode
* @param \Source\UseCases\Door\Access\AccessUseCase $access * @param \Source\UseCases\Door\Access\AccessUseCase $access
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* @throws \Source\Exceptions\AuthenticationException * @throws \Source\Exceptions\AuthenticationException
* @throws \Source\Exceptions\AuthorizationException * @throws \Source\Exceptions\AuthorizationException
* @throws \Illuminate\Validation\ValidationException
*/ */
public function access(string $doorcode, AccessUseCase $access): JsonResponse public function access(string $doorcode, AccessUseCase $access): JsonResponse
{ {
...@@ -65,7 +94,20 @@ class DoorController extends ApiController ...@@ -65,7 +94,20 @@ class DoorController extends ApiController
} }
/** /**
* Door Open Times
*
* This route returns the times when the authenticated door client is supposed to go into open mode accepting any
* key press as a valid door unlock. Retrieves the open mode times for the next interval. Includes open mode
* schedules as well as overrides. The door to get the times for is based off of the authenticated door.
*
* @authenticated
* @queryParam Number of minutes ahead of now to get the open mode times for
*
* @response 422 {"message":"The given data was invalid.","errors":{"foresight":["The foresight must be an
* integer."]}}
*
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException
*/ */
public function status(): JsonResponse public function status(): JsonResponse
{ {
...@@ -73,6 +115,15 @@ class DoorController extends ApiController ...@@ -73,6 +115,15 @@ class DoorController extends ApiController
} }
/** /**
* Door Update
*
* This route returns the newest binary that the door controllers should be running based upon the authenticated
* door. If there are no binaries on record, a 404 response is returned.
*
* @authenticated
*
* @response 404 {"status":"error","code":404,"message":"Entity not found"}
*
* @param \Source\UseCases\Door\UpdateBinary\UpdateBinaryUseCase $updateBinaryUseCase * @param \Source\UseCases\Door\UpdateBinary\UpdateBinaryUseCase $updateBinaryUseCase
* @param \App\Guards\DoorGuard $doorGuard * @param \App\Guards\DoorGuard $doorGuard
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
......
...@@ -9,9 +9,7 @@ use Source\UseCases\Schedules\SchedulesGet\SchedulesGetUseCase; ...@@ -9,9 +9,7 @@ use Source\UseCases\Schedules\SchedulesGet\SchedulesGetUseCase;
use Source\UseCases\Schedules\APIPresenter as ScheduleApiPresenter; use Source\UseCases\Schedules\APIPresenter as ScheduleApiPresenter;
use Source\UseCases\Schedules\ScheduleCreate\ScheduleCreateUseCase; use Source\UseCases\Schedules\ScheduleCreate\ScheduleCreateUseCase;
use Source\UseCases\Schedules\ScheduleUpdate\ScheduleUpdateUseCase; use Source\UseCases\Schedules\ScheduleUpdate\ScheduleUpdateUseCase;
use Source\UseCases\GroupSchedule\APIPresenter as SchedulesForGroupApiPresenter;
use Source\UseCases\Schedules\SchedulesGet\APIPresenter as SchedulesGetApiPresenter; use Source\UseCases\Schedules\SchedulesGet\APIPresenter as SchedulesGetApiPresenter;
use Source\UseCases\GroupSchedule\ActiveSchedulesForGroup\ActiveSchedulesForGroupUseCase;
class SchedulesController extends ApiController class SchedulesController extends ApiController
{ {
...@@ -126,27 +124,4 @@ class SchedulesController extends ApiController ...@@ -126,27 +124,4 @@ class SchedulesController extends ApiController
return $this->respondWithData($presenter->getViewModel()); return $this->respondWithData($presenter->getViewModel());
} }
/**
* @param string $groupId
* @param \Source\UseCases\GroupSchedule\ActiveSchedulesForGroup\ActiveSchedulesForGroupUseCase $activeForGroup
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function activeForGroup(string $groupId, ActiveSchedulesForGroupUseCase $activeForGroup): JsonResponse
{
$this->authorizer->protect(Permissions::MANAGE_DOORS);
$this->validate($this->request, [
'date' => 'date',
]);
$presenter = new SchedulesForGroupApiPresenter();
$activeForGroup->activeForGroup($groupId, $this->request->input('date'), $presenter);
return $this->respondWithData($presenter->getViewModel());
}
} }
...@@ -63,7 +63,6 @@ use Source\UseCases\Schedules\ScheduleUpdate\ScheduleUpdateUseCaseServiceProvide ...@@ -63,7 +63,6 @@ use Source\UseCases\Schedules\ScheduleUpdate\ScheduleUpdateUseCaseServiceProvide
use Source\UseCases\Doors\GenerateDoorToken\GenerateDoorTokenUseCaseServiceProvider; use Source\UseCases\Doors\GenerateDoorToken\GenerateDoorTokenUseCaseServiceProvider;
use Source\UseCases\DoorGroup\RemoveDoorFromGroup\RemoveDoorFromGroupUseCaseServiceProvider; use Source\UseCases\DoorGroup\RemoveDoorFromGroup\RemoveDoorFromGroupUseCaseServiceProvider;
use Source\UseCases\GroupUser\RemoveUserFromGroup\RemoveUserFromGroupUseCaseServiceProvider; use Source\UseCases\GroupUser\RemoveUserFromGroup\RemoveUserFromGroupUseCaseServiceProvider;
use Source\UseCases\GroupSchedule\ActiveSchedulesForGroup\ActiveSchedulesForGroupUseCaseServiceProvider;
use Source\UseCases\Door\Authenticate\AuthenticateUseCaseServiceProvider as DoorAuthenticateUseCaseServiceProvider; use Source\UseCases\Door\Authenticate\AuthenticateUseCaseServiceProvider as DoorAuthenticateUseCaseServiceProvider;
use Source\UseCases\Users\Authenticate\AuthenticateUseCaseServiceProvider as UserAuthenticateUseCaseServiceProvider; use Source\UseCases\Users\Authenticate\AuthenticateUseCaseServiceProvider as UserAuthenticateUseCaseServiceProvider;
use Source\UseCases\Token\Authenticate\AuthenticateUseCaseServiceProvider as TokenAuthenticateUseCaseServiceProvider; use Source\UseCases\Token\Authenticate\AuthenticateUseCaseServiceProvider as TokenAuthenticateUseCaseServiceProvider;
...@@ -159,7 +158,6 @@ class AppServiceProvider extends ServiceProvider ...@@ -159,7 +158,6 @@ class AppServiceProvider extends ServiceProvider
SchedulesGetUseCaseServiceProvider::class, SchedulesGetUseCaseServiceProvider::class,
ScheduleCreateUseCaseServiceProvider::class, ScheduleCreateUseCaseServiceProvider::class,
ScheduleUpdateUseCaseServiceProvider::class, ScheduleUpdateUseCaseServiceProvider::class,
ActiveSchedulesForGroupUseCaseServiceProvider::class,
// Overrides // Overrides
OverrideGetUseCaseServiceProvider::class, OverrideGetUseCaseServiceProvider::class,
......
...@@ -12,7 +12,7 @@ return [ ...@@ -12,7 +12,7 @@ return [
/* /*
* Static output folder: HTML documentation and assets will be generated in this folder. * Static output folder: HTML documentation and assets will be generated in this folder.
*/ */
'output_folder' => 'public/docs', 'output_folder' => 'public/docs',
/* /*
* Settings for `laravel` type output. * Settings for `laravel` type output.
...@@ -184,6 +184,97 @@ return [ ...@@ -184,6 +184,97 @@ return [
], ],
], ],
/*
*
*
*
* DOOR ROUTES
*
*
*
*
*/
[
/*
* Specify conditions to determine what routes will be parsed in this group.
* A route must fulfill ALL conditions to pass.
*/
'match' => [
/*
* Match only routes whose domains match this pattern (use * as a wildcard to match any characters).
*/
'domains' => [
'*',
// 'domain1.*',
],
/*
* Match only routes whose paths match this pattern (use * as a wildcard to match any characters).
*/
'prefixes' => [
],
],
/*
* Include these routes when generating documentation,
* even if they did not match the rules above.
* Note that the route must be referenced by name here (wildcards are supported).
*/
'include' => [
'door.*',
],
/*
* Exclude these routes when generating documentation,
* even if they matched the rules above.
* Note that the route must be referenced by name here (wildcards are supported).
*/
'exclude' => [
// 'users.create', 'admin.*'
],
/*
* Specify rules to be applied to all the routes in this group when generating documentation
*/
'apply' => [
/*
* Specify headers to be added to the example requests
*/
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => 'Bearer door_1_api_token',
// 'Api-Version' => 'v2',
],
/*
* If no @response or @transformer declarations are found for the route,
* we'll try to get a sample response by attempting an API call.
* Configure the settings for the API call here.
*/
'response_calls' => [
/*
* API calls will be made only for routes in this group matching these HTTP methods (GET, POST, etc).
* List the methods here or use '*' to mean all methods. Leave empty to disable API calls.
*/
'methods' => ['*'],
/*
* Laravel config variables which should be set for the API call.
* This is a good place to ensure that notifications, emails
* and other external services are not triggered
* during the documentation API calls
*/
'config' => [
'app.env' => 'memory',
'app.debug' => false,
// 'service.key' => 'value',
],
],
],
],
/* /*
* *
* *
...@@ -234,7 +325,8 @@ return [ ...@@ -234,7 +325,8 @@ return [
* Note that the route must be referenced by name here (wildcards are supported). * Note that the route must be referenced by name here (wildcards are supported).
*/ */
'exclude' => [ 'exclude' => [
'me.*' 'me.*',
'door.*',
// 'users.create', 'admin.*' // 'users.create', 'admin.*'
], ],
......
...@@ -22,50 +22,51 @@ use App\Http\Controllers\SchedulesController; ...@@ -22,50 +22,51 @@ use App\Http\Controllers\SchedulesController;
| is assigned the "api" middleware group. Enjoy building your API! | is assigned the "api" middleware group. Enjoy building your API!
| |
*/ */
Route::post('login', [AuthController::class, 'login']); Route::post('login', [AuthController::class, 'login'])->name('login');
Route::post('logout', [AuthController::class, 'samlLogout']); Route::post('logout', [AuthController::class, 'samlLogout'])->name('logout');
Route::group(['middleware' => 'auth:api'], static function () { Route::group(['middleware' => 'auth:api'], static function () {
Route::group([ Route::group([
'prefix' => 'users', 'prefix' => 'users',
], static function () { ], static function () {
Route::get('/', [UsersController::class, 'index']); Route::get('/', [UsersController::class, 'index'])->name('users.index');
Route::post('/', [UsersController::class, 'store']); Route::post('/', [UsersController::class, 'store'])->name('users.create');
Route::get('{userId}', [UsersController::class, 'get']); Route::get('{userId}', [UsersController::class, 'get'])->name('users.get');
Route::patch('{userId}', [UsersController::class, 'update']); Route::patch('{userId}', [UsersController::class, 'update'])->name('users.update');
Route::delete('{userId}', [UsersController::class, 'delete']); Route::delete('{userId}', [UsersController::class, 'delete'])->name('users.delete');
Route::get('{userId}/groups', [UsersController::class, 'getGroupsForUser']); Route::get('{userId}/groups', [UsersController::class, 'getGroupsForUser']);
Route::post('{userId}/group/{groupId}', [UsersController::class, 'addUserToGroup']); Route::post('{userId}/group/{groupId}', [UsersController::class, 'addUserToGroup'])
Route::delete('{userId}/group/{groupId}', [UsersController::class, 'removeUserFromGroup']); ->name('users.group.add');
Route::get('{userId}/doors', [UsersController::class, 'doors']); Route::delete('{userId}/group/{groupId}', [UsersController::class, 'removeUserFromGroup'])
->name('users.groups.delete');
Route::get('{userId}/doors', [UsersController::class, 'doors'])->name('users.doors');
}); });
Route::group([ Route::group([
'prefix' => 'groups', 'prefix' => 'groups',
], static function () { ], static function () {
Route::get('/', [GroupsController::class, 'index']); Route::get('/', [GroupsController::class, 'index'])->name('groups.index');
Route::post('/', [GroupsController::class, 'store']); Route::post('/', [GroupsController::class, 'store'])->name('groups.create');
Route::get('{groupId}', [GroupsController::class, 'get']); Route::get('{groupId}', [GroupsController::class, 'get'])->name('groups.get');
Route::put('{groupId}', [GroupsController::class, 'update']); Route::put('{groupId}', [GroupsController::class, 'update'])->name('groups.update');
Route::delete('{groupId}', [GroupsController::class, 'delete']); Route::delete('{groupId}', [GroupsController::class, 'delete'])->name('groups.delete');
Route::get('{groupId}/users', [GroupsController::class, 'getUsersForGroup']); Route::get('{groupId}/users', [GroupsController::class, 'getUsersForGroup']);
Route::get('{groupId}/doors', [GroupsController::class, 'getDoorsForGroup']); Route::get('{groupId}/doors', [GroupsController::class, 'getDoorsForGroup']);
Route::get('{groupId}/active', [SchedulesController::class, 'activeForGroup']);
}); });
Route::group([ Route::group([
'prefix' => 'doors', 'prefix' => 'doors',
], static function () { ], static function () {
Route::get('/', [DoorsController::class, 'index']); Route::get('/', [DoorsController::class, 'index'])->name('doors.index');
Route::post('/', [DoorsController::class, 'store']); Route::post('/', [DoorsController::class, 'store'])->name('doors.create');
Route::get('{doorId}', [DoorsController::class, 'get']); Route::get('{doorId}', [DoorsController::class, 'get'])->name('doors.get');
Route::patch('{doorId}', [DoorsController::class, 'update']); Route::patch('{doorId}', [DoorsController::class, 'update'])->name('doors.update');
Route::post('/{doorId}/regenerate-token', [DoorsController::class, 'regenerateToken']); Route::post('/{doorId}/regenerate-token', [DoorsController::class, 'regenerateToken'])
Route::delete('{doorId}', [DoorsController::class, 'delete']); ->name('doors.regenerate');
Route::delete('{doorId}', [DoorsController::class, 'delete'])->name('doors.delete');
Route::get('{doorId}/groups', [DoorsController::class, 'getGroupsForDoor']); Route::get('{doorId}/groups', [DoorsController::class, 'getGroupsForDoor']);
Route::post('{doorId}/group/{groupId}', [DoorsController::class, 'addDoorToGroup']); Route::post('{doorId}/group/{groupId}', [DoorsController::class, 'addDoorToGroup']);
...@@ -75,41 +76,41 @@ Route::group(['middleware' => 'auth:api'], static function () { ...@@ -75,41 +76,41 @@ Route::group(['middleware' => 'auth:api'], static function () {
Route::group([ Route::group([
'prefix' => 'schedules', 'prefix' => 'schedules',
], static function () { ], static function () {
Route::get('/', [SchedulesController::class, 'index']); Route::get('/', [SchedulesController::class, 'index'])->name('schedules.index');
Route::post('/', [SchedulesController::class, 'create']); Route::post('/', [SchedulesController::class, 'create'])->name('schedules.create');
Route::get('{scheduleId}', [SchedulesController::class, 'get']); Route::get('{scheduleId}', [SchedulesController::class, 'get'])->name('schedules.get');
Route::patch('{scheduleId}', [SchedulesController::class, 'update']); Route::patch('{scheduleId}', [SchedulesController::class, 'update'])->name('schedules.update');
}); });
Route::group([ Route::group([
'prefix' => 'tokens', 'prefix' => 'tokens',
], static function () { ], static function () {
Route::get('/', [TokensController::class, 'index']); Route::get('/', [TokensController::class, 'index'])->name('tokens.index');
Route::post('/', [TokensController::class, 'store']); Route::post('/', [TokensController::class, 'store'])->name('tokens.create');
Route::get('{tokenId}', [TokensController::class, 'get']); Route::get('{tokenId}', [TokensController::class, 'get'])->name('tokens.get');
Route::patch('{tokenId}', [TokensController::class, 'update']); Route::patch('{tokenId}', [TokensController::class, 'update'])->name('tokens.update');
Route::post('{tokenId}/expire', [TokensController::class, 'expire']); Route::post('{tokenId}/expire', [TokensController::class, 'expire'])->name('tokens.expire');
}); });
Route::group([ Route::group([
'prefix' => 'entries', 'prefix' => 'entries',
], static function () { ], static function () {
Route::get('/', [EntriesController::class, 'index']); Route::get('/', [EntriesController::class, 'index'])->name('entries.index');
}); });
Route::group([ Route::group([
'prefix' => 'attempts', 'prefix' => 'attempts',
], static function () { ], static function () {
Route::get('/', [AttemptsController::class, 'index']); Route::get('/', [AttemptsController::class, 'index'])->name('attempts.index');
}); });
Route::group([ Route::group([
'prefix' => 'overrides', 'prefix' => 'overrides',
], static function () { ], static function () {
Route::get('/', [OverridesController::class, 'index']); Route::get('/', [OverridesController::class, 'index'])->name('overrides.index');
Route::post('/', [OverridesController::class, 'create']); Route::post('/', [OverridesController::class, 'create'])->name('overrides.create');
Route::get('{overrideId}', [OverridesController::class, 'get']); Route::get('{overrideId}', [OverridesController::class, 'get'])->name('overrides.get');
Route::patch('{overrideId}', [OverridesController::class, 'update']); Route::patch('{overrideId}', [OverridesController::class, 'update'])->name('overrides.update');
}); });
Route::group([ Route::group([
......
...@@ -15,12 +15,8 @@ use App\Http\Controllers\DoorController; ...@@ -15,12 +15,8 @@ use App\Http\Controllers\DoorController;
| |
*/ */
Route::get('ping', static function () { Route::get('access/{doorcode}', [DoorController::class, 'access'])->name('door.access');
return 'pong';
});
Route::get('access/{doorcode}', [DoorController::class, 'access']); Route::get('status', [DoorController::class, 'status'])->name('door.status');
Route::get('status', [DoorController::class, 'status']); Route::get('update', [DoorController::class, 'getUpdateBinary'])->name('door.update');
Route::get('update', [DoorController::class, 'getUpdateBinary']);
...@@ -171,7 +171,7 @@ class Override ...@@ -171,7 +171,7 @@ class Override
return $date->isBetween($this->getStart(), $this->getEnd()); return $date->isBetween($this->getStart(), $this->getEnd());
} }
return $date->isAfter($this->getStart()); return $date->greaterThanOrEqualTo($this->getStart());
} }
/** /**
......
...@@ -42,13 +42,15 @@ class InMemoryOverridesRepository implements OverridesRepository ...@@ -42,13 +42,15 @@ class InMemoryOverridesRepository implements OverridesRepository
*/ */
public function activeOverrideForDoorBetween(string $doorId, Carbon $begin, Carbon $end): ?Override public function activeOverrideForDoorBetween(string $doorId, Carbon $begin, Carbon $end): ?Override
{ {
$overrides = array_filter($this->overrides, static function (Override $override) use ($doorId, $begin, $end) { $overrides = collect(array_filter($this->overrides, static function (Override $override) use ($doorId, $begin, $end) {
return $override->hasDoorIdOf($doorId) && return $override->hasDoorIdOf($doorId) &&
($override->isActiveForDate($begin) || $override->isActiveForDate($end) || ($override->isActiveForDate($begin) || $override->isActiveForDate($end) ||
($begin->isBefore($override->getStart()) && $end->isAfter($override->getEnd()))); ($begin->isBefore($override->getStart()) && $end->isAfter($override->getEnd())));
}); }));
return array_shift($overrides); return $overrides
->sortBy(fn (Override $item) => $item->getCreatedAt(), SORT_DESC, true)
->first();
} }
/** /**
...@@ -56,6 +58,18 @@ class InMemoryOverridesRepository implements OverridesRepository ...@@ -56,6 +58,18 @@ class InMemoryOverridesRepository implements OverridesRepository
*/ */
public function addOverride(Override $override): ?Override public function addOverride(Override $override): ?Override
{ {
$override = new Override(
$override->getId(),
$override->getReason(),
$override->getUserId(),
$override->getDoorId(),
$override->getType(),
$override->getStart(),