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

Merge branch '37-token-crud' into 'master'

Resolve "Token CRUD"

Closes #37

See merge request kretschmar/doorcode!34
parents 93a25fed 96ad1bd2
Pipeline #5597 passed with stages
in 3 minutes and 51 seconds
......@@ -7,8 +7,8 @@ use Illuminate\Http\Request;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\Authenticatable;
use Source\UseCases\Doors\Authenticate\AuthenticateUseCase;
use Source\UseCases\Doors\Authenticate\TranslationPresenter;
use Source\UseCases\Door\Authenticate\AuthenticateUseCase;
use Source\UseCases\Door\Authenticate\TranslationPresenter;
class DoorGuard implements Guard
{
......
......@@ -11,9 +11,11 @@ use Source\Exceptions\AuthenticationException;
use Source\UseCases\Users\GetUser\GetUserUseCase;
use Source\UseCases\Users\UpdateUser\UpdateUserUseCase;
use Source\UseCases\GroupUser\GetUserGroups\GetUserGroupsUseCase;
use Source\UseCases\TokenUser\GetUserTokens\GetUserTokensUseCase;
use Source\UseCases\Users\GetUser\APIPresenter as GetUserAPIPresenter;
use Source\UseCases\Users\UpdateUser\APIPresenter as UpdateUserAPIPresenter;
use Source\UseCases\GroupUser\GetUserGroups\APIPresenter as GetUserGroupsAPIPresenter;
use Source\UseCases\TokenUser\GetUserTokens\APIPresenter as GetUserTokensAPIPresenter;
class MeController extends ApiController
{
......@@ -98,4 +100,18 @@ class MeController extends ApiController
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param \Source\UseCases\TokenUser\GetUserTokens\GetUserTokensUseCase $userTokens
* @return \Illuminate\Http\JsonResponse
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function tokens(GetUserTokensUseCase $userTokens): JsonResponse
{
$presenter = new GetUserTokensAPIPresenter();
$userTokens->getTokensForUser($this->userId, $presenter);
return $this->respondWithData($presenter->getViewModel());
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Source\Authorization\Permissions;
use Source\UseCases\Tokens\GetToken\GetTokenUseCase;
use Source\UseCases\Tokens\CreateToken\CreateTokenUseCase;
use Source\UseCases\Tokens\ExpireToken\ExpireTokenUseCase;
use Source\UseCases\Tokens\UpdateToken\UpdateTokenUseCase;
use Source\UseCases\Tokens\GetAllTokens\GetAllTokensUseCase;
use Source\UseCases\Tokens\GetToken\APIPresenter as GetTokenAPIPresenter;
use Source\UseCases\Tokens\GetAllTokens\APIPresenter as AllTokensAPIPresenter;
use Source\UseCases\Tokens\CreateToken\APIPresenter as CreateTokenAPIPresenter;
use Source\UseCases\Tokens\ExpireToken\APIPresenter as ExpireTokenAPIPresenter;
use Source\UseCases\Tokens\UpdateToken\APIPresenter as UpdateTokenAPIPresenter;
class TokensController extends ApiController
{
/**
* @param \Source\UseCases\Tokens\GetAllTokens\GetAllTokensUseCase $allTokens
* @return \Illuminate\Http\JsonResponse
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function index(GetAllTokensUseCase $allTokens): JsonResponse
{
$this->authorizer->protect(Permissions::MANAGE_USERS);
$presenter = new AllTokensAPIPresenter();
$allTokens->all($presenter);
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param \Source\UseCases\Tokens\GetToken\GetTokenUseCase $token
* @param string $tokenId
* @return \Illuminate\Http\JsonResponse
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function get(GetTokenUseCase $token, string $tokenId): JsonResponse
{
$this->authorizer->protect(Permissions::MANAGE_USERS);
$presenter = new GetTokenAPIPresenter();
$token->getToken($tokenId, $presenter);
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param \Source\UseCases\Tokens\CreateToken\CreateTokenUseCase $createToken
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function store(CreateTokenUseCase $createToken): JsonResponse
{
$this->authorizer->protect(Permissions::ADMIN);
$this->validate($this->request, [
'name' => 'required|string|max:255',
'user_id' => 'required|numeric',
'expires_at' => 'date',
]);
$attributes = $this->request->all();
$attributes['salt'] = config('app.key');
$presenter = new CreateTokenAPIPresenter();
$createToken->create($attributes, $presenter);
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param \Source\UseCases\Tokens\UpdateToken\UpdateTokenUseCase $updateToken
* @param string $tokenId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function update(UpdateTokenUseCase $updateToken, string $tokenId): JsonResponse
{
$this->authorizer->protect(Permissions::ADMIN);
$this->validate($this->request, [
'name' => 'string|max:255',
'expires_at' => 'date',
]);
$attributes = $this->request->all();
$attributes['salt'] = config('app.key');
$presenter = new UpdateTokenAPIPresenter();
$updateToken->update($tokenId, $attributes, $presenter);
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param \Source\UseCases\Tokens\ExpireToken\ExpireTokenUseCase $expireToken
* @param string $tokenId
* @return \Illuminate\Http\JsonResponse
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function expire(ExpireTokenUseCase $expireToken, string $tokenId): JsonResponse
{
$this->authorizer->protect(Permissions::ADMIN);
$presenter = new ExpireTokenAPIPresenter();
$expireToken->expire($tokenId, $presenter);
return $this->respondWithData($presenter->getViewModel());
}
}
......@@ -12,6 +12,7 @@ use Source\UseCases\Users\DeleteUser\DeleteUserUseCase;
use Source\UseCases\Users\UpdateUser\UpdateUserUseCase;
use Source\UseCases\Users\GetAllUsers\GetAllUsersUseCase;
use Source\UseCases\GroupUser\GetUserGroups\GetUserGroupsUseCase;
use Source\UseCases\TokenUser\GetUserTokens\GetUserTokensUseCase;
use Source\UseCases\GroupUser\AddUserToGroup\AddUserToGroupUseCase;
use Source\UseCases\Users\GetUser\APIPresenter as GetUserAPIPresenter;
use Source\UseCases\Users\GetAllUsers\APIPresenter as AllUsersAPIPresenter;
......@@ -20,6 +21,7 @@ use Source\UseCases\Users\DeleteUser\APIPresenter as DeleteUserAPIPresenter;
use Source\UseCases\Users\UpdateUser\APIPresenter as UpdateUserAPIPresenter;
use Source\UseCases\GroupUser\RemoveUserFromGroup\RemoveUserFromGroupUseCase;
use Source\UseCases\GroupUser\GetUserGroups\APIPresenter as GetUserGroupsAPIPresenter;
use Source\UseCases\TokenUser\GetUserTokens\APIPresenter as GetUserTokensAPIPresenter;
use Source\UseCases\GroupUser\AddUserToGroup\APIPresenter as AddUserToGroupAPIPresenter;
use Source\UseCases\GroupUser\RemoveUserFromGroup\APIPresenter as RemoveUserFromGroupAPIPresenter;
......@@ -210,4 +212,22 @@ class UsersController extends ApiController
return $this->respondWithData($presenter->getViewModel());
}
/**
* @param \Source\UseCases\TokenUser\GetUserTokens\GetUserTokensUseCase $userTokens
* @param string $userId
* @return \Illuminate\Http\JsonResponse
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function tokens(GetUserTokensUseCase $userTokens, string $userId): JsonResponse
{
$this->authorizer->protect(Permissions::MANAGE_USERS);
$presenter = new GetUserTokensAPIPresenter();
$userTokens->getTokensForUser($userId, $presenter);
return $this->respondWithData($presenter->getViewModel());
}
}
......@@ -16,6 +16,7 @@ use Source\UseCases\Users\GetUser\GetUserUseCaseServiceProvider;
use Source\Gateways\DoorGroup\DoorGroupRepositoryServiceProvider;
use Source\Gateways\GroupUser\GroupUserRepositoryServiceProvider;
use Source\UseCases\Groups\GetGroup\GetGroupUseCaseServiceProvider;
use Source\UseCases\Tokens\GetToken\GetTokenUseCaseServiceProvider;
use Source\UseCases\Doors\CreateDoor\CreateDoorUseCaseServiceProvider;
use Source\UseCases\Doors\DeleteDoor\DeleteDoorUseCaseServiceProvider;
use Source\UseCases\Doors\UpdateDoor\UpdateDoorUseCaseServiceProvider;
......@@ -27,18 +28,23 @@ use Source\UseCases\Users\GetAllUsers\GetAllUsersUseCaseServiceProvider;
use Source\UseCases\Groups\CreateGroup\CreateGroupUseCaseServiceProvider;
use Source\UseCases\Groups\DeleteGroup\DeleteGroupUseCaseServiceProvider;
use Source\UseCases\Groups\UpdateGroup\UpdateGroupUseCaseServiceProvider;
use Source\UseCases\Tokens\CreateToken\CreateTokenUseCaseServiceProvider;
use Source\UseCases\Tokens\ExpireToken\ExpireTokenUseCaseServiceProvider;
use Source\UseCases\Tokens\UpdateToken\UpdateTokenUseCaseServiceProvider;
use Source\UseCases\Token\Authenticate\AuthenticateUseCaseServiceProvider;
use Source\UseCases\Groups\GetAllGroups\GetAllGroupsUseCaseServiceProvider;
use Source\UseCases\Tokens\GetAllTokens\GetAllTokensUseCaseServiceProvider;
use Source\UseCases\DoorGroup\GetDoorGroups\GetDoorGroupsUseCaseServiceProvider;
use Source\UseCases\DoorGroup\GetGroupDoors\GetGroupDoorsUseCaseServiceProvider;
use Source\UseCases\GroupUser\GetGroupUsers\GetGroupUsersUseCaseServiceProvider;
use Source\UseCases\GroupUser\GetUserGroups\GetUserGroupsUseCaseServiceProvider;
use Source\UseCases\TokenUser\GetUserTokens\GetUserTokensUseCaseServiceProvider;
use Source\UseCases\DoorGroup\AddDoorToGroup\AddDoorToGroupUseCaseServiceProvider;
use Source\UseCases\GroupUser\AddUserToGroup\AddUserToGroupUseCaseServiceProvider;
use Source\UseCases\Doors\GenerateDoorToken\GenerateDoorTokenUseCaseServiceProvider;
use Source\UseCases\DoorGroup\RemoveDoorFromGroup\RemoveDoorFromGroupUseCaseServiceProvider;
use Source\UseCases\GroupUser\RemoveUserFromGroup\RemoveUserFromGroupUseCaseServiceProvider;
use Source\UseCases\Doors\Authenticate\AuthenticateUseCaseServiceProvider as DoorAuthenticateUseCaseServiceProvider;
use Source\UseCases\Door\Authenticate\AuthenticateUseCaseServiceProvider as DoorAuthenticateUseCaseServiceProvider;
use Source\UseCases\Users\Authenticate\AuthenticateUseCaseServiceProvider as UserAuthenticateUseCaseServiceProvider;
class AppServiceProvider extends ServiceProvider
......@@ -101,6 +107,16 @@ class AppServiceProvider extends ServiceProvider
DoorAuthenticateUseCaseServiceProvider::class,
UserAuthenticateUseCaseServiceProvider::class,
GenerateDoorTokenUseCaseServiceProvider::class,
// Tokens
GetTokenUseCaseServiceProvider::class,
CreateTokenUseCaseServiceProvider::class,
ExpireTokenUseCaseServiceProvider::class,
UpdateTokenUseCaseServiceProvider::class,
GetAllTokensUseCaseServiceProvider::class,
// TokenUser
GetUserTokensUseCaseServiceProvider::class,
];
/**
......
......@@ -7,7 +7,7 @@ use App\Guards\DoorGuard;
use Illuminate\Support\Facades\Auth;
use Source\UseCases\Token\Authenticate\AuthenticateUseCase;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Source\UseCases\Doors\Authenticate\AuthenticateUseCase as DoorAuthenticateUseCase;
use Source\UseCases\Door\Authenticate\AuthenticateUseCase as DoorAuthenticateUseCase;
class AuthServiceProvider extends ServiceProvider
{
......
......@@ -6,6 +6,7 @@ use App\Http\Controllers\AuthController;
use App\Http\Controllers\DoorsController;
use App\Http\Controllers\UsersController;
use App\Http\Controllers\GroupsController;
use App\Http\Controllers\TokensController;
/*
|--------------------------------------------------------------------------
......@@ -34,6 +35,7 @@ Route::group(['middleware' => 'auth:api'], static function () {
Route::get('{userId}/groups', [UsersController::class, 'getGroupsForUser']);
Route::post('{userId}/group/{groupId}', [UsersController::class, 'addUserToGroup']);
Route::delete('{userId}/group/{groupId}', [UsersController::class, 'removeUserFromGroup']);
Route::get('{userId}/tokens', [UsersController::class, 'tokens']);
});
Route::group([
......@@ -64,11 +66,22 @@ Route::group(['middleware' => 'auth:api'], static function () {
Route::delete('{doorId}/group/{groupId}', [DoorsController::class, 'removeDoorFromGroup']);
});
Route::group([
'prefix' => 'tokens',
], static function () {
Route::get('/', [TokensController::class, 'index']);
Route::get('{tokenId}', [TokensController::class, 'get']);
Route::post('/', [TokensController::class, 'store']);
Route::put('{tokenId}', [TokensController::class, 'update']);
Route::post('{tokenId}/expire', [TokensController::class, 'expire']);
});
Route::group([
'prefix' => 'me',
], static function () {
Route::get('/', [MeController::class, 'index']);
Route::get('/groups', [MeController::class, 'groups']);
Route::get('groups', [MeController::class, 'groups']);
Route::get('tokens', [MeController::class, 'tokens']);
Route::patch('/', [MeController::class, 'update']);
});
});
......@@ -83,6 +83,19 @@ class Token
return $this->tokenString === $token;
}
/**
* @param string|null $id
* @return bool
*/
public function hasIdOf(?string $id): bool
{
if (!$id) {
return false;
}
return (int)$id === $this->id;
}
/**
* @return int
*/
......@@ -179,4 +192,25 @@ class Token
{
return !$this->isValid();
}
/**
* @param string|null $id
* @return bool
*/
public function hasUserIdOf(?string $id): bool
{
if (!$id) {
return false;
}
return $this->userId === (int)$id;
}
/**
* @return bool
*/
public function hasName(): bool
{
return (bool)$this->name;
}
}
......@@ -5,19 +5,22 @@ namespace Source\Gateways\DoorGroup;
use App\Door;
use App\Group;
use Source\Sanitize\CastsTo;
use Source\Exceptions\EntityNotFoundException;
use Source\Gateways\Doors\DatabaseDoorsRepository;
use Source\Gateways\Groups\DatabaseGroupsRepository;
class DatabaseDoorGroupRepository implements DoorGroupRepository
{
use CastsTo;
/**
* @inheritDoc
*/
public function getGroupsForDoor(string $doorId): array
{
/** @var Door|null $door */
$door = Door::find((int)$doorId);
$door = Door::find(($this->castToInt($doorId)));
if (!$door) {
throw new EntityNotFoundException();
......@@ -34,7 +37,7 @@ class DatabaseDoorGroupRepository implements DoorGroupRepository
public function getDoorsForGroup(string $groupId): array
{
/** @var Group|null $group */
$group = Group::find((int)$groupId);
$group = Group::find($this->castToInt($groupId));
if (!$group) {
throw new EntityNotFoundException();
......@@ -51,9 +54,9 @@ class DatabaseDoorGroupRepository implements DoorGroupRepository
public function addDoorToGroup(string $doorId, string $groupId): void
{
/** @var Door|null $door */
$door = Door::find((int)$doorId);
$door = Door::find($this->castToInt($doorId));
/** @var Group|null $group */
$group = Group::find((int)$groupId);
$group = Group::find($this->castToInt($groupId));
if (!$door) {
throw new EntityNotFoundException('Door not found.');
......@@ -72,9 +75,9 @@ class DatabaseDoorGroupRepository implements DoorGroupRepository
public function removeDoorFromGroup(string $doorId, string $groupId): void
{
/** @var Door|null $door */
$door = Door::find((int)$doorId);
$door = Door::find($this->castToInt($doorId));
/** @var Group|null $group */
$group = Group::find((int)$groupId);
$group = Group::find($this->castToInt($groupId));
if (!$door) {
throw new EntityNotFoundException('Door not found.');
......
......@@ -101,12 +101,12 @@ class InMemoryDoorGroupRepository implements DoorGroupRepository
public function removeDoorFromGroup(string $doorId, string $groupId): void
{
if (isset($this->doorMap[$doorId])) {
$this->doorMap[$doorId] = array_filter(
$this->doorMap[$doorId] = array_values(array_filter(
$this->doorMap[$doorId],
static function (string $searchGroup) use ($groupId) {
return $searchGroup !== $groupId;
}
);
));
}
}
}
......@@ -4,11 +4,14 @@
namespace Source\Gateways\Doors;
use Source\Entities\Door;
use Source\Sanitize\CastsTo;
use Source\Entities\HashedSearchable;
use Source\Exceptions\EntityExistsException;
class DatabaseDoorsRepository implements DoorsRepository
{
use CastsTo;
/**
* @inheritDoc
*/
......@@ -48,6 +51,10 @@ class DatabaseDoorsRepository implements DoorsRepository
*/
public function getByToken(?HashedSearchable $token): ?Door
{
if (!$token) {
return null;
}
$dbDoor = \App\Door::where('api_token', $token->getHash())->first();
if (!$dbDoor) {
......@@ -62,7 +69,7 @@ class DatabaseDoorsRepository implements DoorsRepository
*/
public function get(string $doorId): ?Door
{
if (!($door = \App\Door::find((int)$doorId))) {
if (!($door = \App\Door::find($this->castToInt($doorId)))) {
return null;
}
......@@ -101,7 +108,7 @@ class DatabaseDoorsRepository implements DoorsRepository
*/
public function update(string $doorId, Door $door): ?Door
{
$dbDoor = \App\Door::find((int)$doorId);
$dbDoor = \App\Door::find($this->castToInt($doorId));
if (!$dbDoor) {
return null;
......@@ -121,7 +128,7 @@ class DatabaseDoorsRepository implements DoorsRepository
*/
public function delete(string $doorId): bool
{
return \App\Door::destroy((int)$doorId);
return \App\Door::destroy($this->castToInt($doorId));
}
/**
......
......@@ -99,9 +99,9 @@ class InMemoryDoorsRepository implements DoorsRepository
*/
public function delete(string $doorId): bool
{
$this->doors = array_filter($this->doors, static function (Door $door) use ($doorId) {
$this->doors = array_values(array_filter($this->doors, static function (Door $door) use ($doorId) {
return !$door->hasIdOf($doorId);
});
}));
return true;
}
......
......@@ -5,19 +5,22 @@ namespace Source\Gateways\GroupUser;
use App\User;
use App\Group;
use Source\Sanitize\CastsTo;
use Source\Exceptions\EntityNotFoundException;
use Source\Gateways\Users\DatabaseUsersRepository;
use Source\Gateways\Groups\DatabaseGroupsRepository;
class DatabaseGroupUserRepository implements GroupUserRepository
{
use CastsTo;
/**
* @inheritDoc
*/
public function getGroupsForUser(string $userId): array
{
/** @var User|null $user */
$user = User::find((int)$userId);
$user = User::find($this->castToInt($userId));
if (!$user) {
throw new EntityNotFoundException();
......@@ -34,7 +37,7 @@ class DatabaseGroupUserRepository implements GroupUserRepository
public function getUsersForGroup(string $groupId): array
{
/** @var Group|null $group */
$group = Group::find((int)$groupId);
$group = Group::find($this->castToInt($groupId));
if (!$group) {
throw new EntityNotFoundException();
......@@ -51,9 +54,9 @@ class DatabaseGroupUserRepository implements GroupUserRepository
public function addUserToGroup(string $userId, string $groupId): void
{
/** @var User|null $user */
$user = User::find((int)$userId);
$user = User::find($this->castToInt($userId));
/** @var Group|null $group */
$group = Group::find((int)$groupId);
$group = Group::find($this->castToInt($groupId));
if (!$user) {
throw new EntityNotFoundException('User not found.');
......@@ -72,9 +75,9 @@ class DatabaseGroupUserRepository implements GroupUserRepository
public function removeUserFromGroup(string $userId, string $groupId): void
{
/** @var User|null $user */
$user = User::find((int)$userId);
$user = User::find($this->castToInt($userId));
/** @var Group|null $group */
$group = Group::find((int)$groupId);
$group = Group::find($this->castToInt($groupId));
if (!$user) {
throw new EntityNotFoundException('User not found.');
......
......@@ -103,12 +103,12 @@ class InMemoryGroupUserRepository implements GroupUserRepository
public function removeUserFromGroup(string $userId, string $groupId): void
{
if (isset($this->groupMap[$userId])) {
$this->groupMap[$userId] = array_filter(
$this->groupMap[$userId] = array_values(array_filter(
$this->groupMap[$userId],
static function (string $searchGroup) use ($groupId) {
return $searchGroup !== $groupId;
}
);
));
}
}
}
......@@ -4,15 +4,18 @@
namespace Source\Gateways\Groups;
use Source\Entities\Group;
use Source\Sanitize\CastsTo;
class DatabaseGroupsRepository implements GroupsRepository
{
use CastsTo;
/**
* @inheritDoc
*/
public function get(string $groupId): ?Group
{
$group = \App\Group::find((int)$groupId);
$group = \App\Group::find($this->castToInt($groupId));
if (!$group) {
return null;
......@@ -73,7 +76,7 @@ class DatabaseGroupsRepository implements GroupsRepository
*/
public function update(string $groupId, Group $group): ?Group
{
$dbGroup = \App\Group::find((int)$groupId);
$dbGroup = \App\Group::find($this->castToInt($groupId));