Commit 93a25fed authored by Jacob Priddy's avatar Jacob Priddy 👌
Browse files

Merge branch '36-implement-door-authorization' into 'master'

Resolve "Implement Door Authorization"

Closes #36

See merge request kretschmar/doorcode!33
parents 89fd44ef cc3a00cb
Pipeline #5594 passed with stages
in 3 minutes and 58 seconds
......@@ -9,7 +9,8 @@ APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
DB_CONNECTION=pgsql
DB_CONNECTION=doorcode
DB_DRIVER=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=doorcode
......
......@@ -4,7 +4,8 @@ APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
DB_CONNECTION=pgsql
DB_CONNECTION=doorcode
DB_DRIVER=pgsql
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=doorcode
......
......@@ -2,7 +2,6 @@
namespace App\Exceptions;
use Exception;
use Throwable;
use Illuminate\Http\JsonResponse;
use Illuminate\Auth\AuthenticationException;
......@@ -10,7 +9,6 @@ use Source\Exceptions\EntityExistsException;
use Source\Exceptions\AuthorizationException;
use Illuminate\Validation\ValidationException;
use Source\Exceptions\EntityNotFoundException;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Source\Exceptions\AuthenticationException as SourceAuthenticationException;
......
......@@ -53,7 +53,7 @@ class DoorGuard implements Guard
/**
* Get the currently authenticated user.
*
* @return Authenticatable|null
* @return Authenticatable|\Source\Entities\Door|null
*/
public function user()
{
......
......@@ -38,6 +38,17 @@ abstract class ApiController extends Controller
$this->status = $code;
}
/**
* @return \Illuminate\Http\JsonResponse
*/
public function respondSuccess(): JsonResponse
{
return new JsonResponse([
'status' => 'success',
'code' => $this->status,
], $this->status);
}
/**
* @param array $data
* @return JsonResponse
......
<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Source\UseCases\Door\Access\AccessUseCase;
class DoorController extends ApiController
{
/**
* @param \Source\UseCases\Door\Access\AccessUseCase $access
* @param string $doorcode
* @return \Illuminate\Http\JsonResponse
* @throws \Source\Exceptions\AuthenticationException
* @throws \Source\Exceptions\AuthorizationException
*/
public function access(AccessUseCase $access, string $doorcode): JsonResponse
{
$salt = config('app.key');
$access->protectUserDoorAccess($salt, $doorcode);
return $this->respondSuccess();
}
}
......@@ -9,6 +9,8 @@ use Source\Gateways\Doors\DoorsRepositoryServiceProvider;
use Source\Gateways\Users\UsersRepositoryServiceProvider;
use Source\Gateways\Groups\GroupsRepositoryServiceProvider;
use Source\Gateways\Tokens\TokensRepositoryServiceProvider;
use Source\UseCases\Door\Access\AccessUseCaseServiceProvider;
use Source\Gateways\DoorUser\DoorUserRepositoryServiceProvider;
use Source\UseCases\Doors\GetDoor\GetDoorUseCaseServiceProvider;
use Source\UseCases\Users\GetUser\GetUserUseCaseServiceProvider;
use Source\Gateways\DoorGroup\DoorGroupRepositoryServiceProvider;
......@@ -51,6 +53,7 @@ class AppServiceProvider extends ServiceProvider
DoorsRepositoryServiceProvider::class,
TokensRepositoryServiceProvider::class,
GroupsRepositoryServiceProvider::class,
DoorUserRepositoryServiceProvider::class,
DoorGroupRepositoryServiceProvider::class,
GroupUserRepositoryServiceProvider::class,
];
......@@ -85,6 +88,9 @@ class AppServiceProvider extends ServiceProvider
AddDoorToGroupUseCaseServiceProvider::class,
RemoveDoorFromGroupUseCaseServiceProvider::class,
// Door
AccessUseCaseServiceProvider::class,
// Doors
GetDoorUseCaseServiceProvider::class,
CreateDoorUseCaseServiceProvider::class,
......
......@@ -7,6 +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;
class AuthServiceProvider extends ServiceProvider
{
......@@ -40,7 +41,7 @@ class AuthServiceProvider extends ServiceProvider
Auth::extend(
'door',
static function ($app, $name, array $config) {
return new DoorGuard($app->make(\Source\UseCases\Doors\Authenticate\AuthenticateUseCase::class), $app['request']);
return new DoorGuard($app->make(DoorAuthenticateUseCase::class), $app['request']);
}
);
}
......
......@@ -83,6 +83,7 @@ class RouteServiceProvider extends ServiceProvider
protected function mapDoorRoutes(): void
{
Route::middleware('door')
->prefix('door')
->namespace($this->namespace)
->group(base_path('routes/door.php'));
}
......
......@@ -34,6 +34,20 @@ return [
*/
'connections' => [
'doorcode' => [
'driver' => env('DB_DRIVER', 'pgsql'),
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', 'postgres'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'doorcode'),
'username' => env('DB_USERNAME', 'web'),
'password' => env('DB_PASSWORD', 'secret'),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'schema' => 'public',
'sslmode' => 'prefer',
],
'sqlite' => [
'driver' => 'sqlite',
......
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\DoorController;
/*
|--------------------------------------------------------------------------
......@@ -9,13 +10,9 @@ use Illuminate\Support\Facades\Route;
|
| Here is where you can register Door routes. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "door" middleware group.
| is assigned the "door" middleware group. All routes are automatically prefixed with '/door/'
| All incoming requests will be authenticated doors.
|
*/
Route::get(
'door/',
static function () {
return ['yeet'];
}
);
Route::get('access/{doorcode}', [DoorController::class, 'access']);
......@@ -3,7 +3,6 @@
namespace Source\Authorization;
use Illuminate\Contracts\Auth\Guard;
use Source\Gateways\Users\UsersRepository;
use Source\Gateways\Groups\GroupsRepository;
use Source\Exceptions\AuthorizationException;
......@@ -12,11 +11,6 @@ use Source\Gateways\GroupUser\GroupUserRepository;
class ApiAuthorizer implements Authorizer
{
/**
* @var \Illuminate\Contracts\Auth\Guard
*/
protected Guard $guard;
/**
* @var \Source\Gateways\GroupUser\GroupUserRepository
*/
......@@ -32,18 +26,28 @@ class ApiAuthorizer implements Authorizer
*/
protected UsersRepository $users;
/**
* @var string|null
*/
protected ?string $currentUserId;
public function __construct(
Guard $guard,
?string $currentUserId,
UsersRepository $users,
GroupsRepository $groups,
GroupUserRepository $groupUserRepository
) {
$this->users = $users;
$this->guard = $guard;
$this->groups = $groups;
$this->currentUserId = $currentUserId;
$this->groupUserRepository = $groupUserRepository;
}
public function setCurrentUserId(?string $id): void
{
$this->currentUserId = $id;
}
/**
* @inheritDoc
*/
......@@ -80,13 +84,11 @@ class ApiAuthorizer implements Authorizer
*/
protected function getGroupsForCurrentUser(): array
{
$user = $this->guard->id();
if (!$user) {
if (!$this->currentUserId) {
return [];
}
return $this->getGroupsForUser($user);
return $this->getGroupsForUser($this->currentUserId);
}
/**
......
......@@ -25,7 +25,7 @@ class AuthorizerServiceProvider extends ServiceProvider implements DeferrablePro
{
$this->app->singleton(Authorizer::class, static function (Application $app) {
return new ApiAuthorizer(
$app->make(ApiGuard::class),
$app->make(ApiGuard::class)->id(),
$app->make(UsersRepository::class),
$app->make(GroupsRepository::class),
$app->make(GroupUserRepository::class)
......
......@@ -7,16 +7,34 @@ use Carbon\Carbon;
class Door
{
/**
* @var int
*/
protected int $id;
/**
* @var string
*/
protected string $location;
/**
* @var string
*/
protected string $name;
/**
* @var \Source\Entities\HashedSearchable
*/
protected HashedSearchable $token;
/**
* @var \Carbon\Carbon|null
*/
protected ?Carbon $createdAt;
/**
* @var \Carbon\Carbon|null
*/
protected ?Carbon $updatedAt;
/**
......@@ -83,6 +101,10 @@ class Door
return $this->updatedAt;
}
/**
* @param \Source\Entities\HashedSearchable|null $token
* @return bool
*/
public function hasTokenOf(?HashedSearchable $token): bool
{
if (!$token) {
......@@ -100,6 +122,10 @@ class Door
return $this->token;
}
/**
* @param string|null $name
* @return bool
*/
public function hasNameOf(?string $name): bool
{
if (!$name) {
......@@ -125,7 +151,7 @@ class Door
/**
* @param string $id
*/
public function setId(string $id): void
public function setId(int $id): void
{
$this->id = $id;
}
......
<?php
namespace Source\Gateways\DoorUser;
use Source\Entities\Group;
use Source\Sanitize\CastsTo;
use Illuminate\Database\ConnectionInterface;
class DatabaseDoorUserRepository implements DoorUserRepository
{
use CastsTo;
/**
* @var \Illuminate\Database\ConnectionInterface
*/
protected ConnectionInterface $db;
public function __construct(ConnectionInterface $db)
{
$this->db = $db;
}
/**
* @inheritDoc
*/
public function getDoorUserGroupIntersection(string $doorId, string $userId): array
{
$query = <<<QUERY
SELECT A.id, A.title, A.description, A.created_at, A.updated_at
FROM groups AS A
INNER JOIN door_group AS B ON B.group_id = A.id AND B.door_id = :door_id
INNER JOIN group_user AS C ON C.group_id = A.id AND C.user_id = :user_id
QUERY;
$commonGroups = $this->db->select($query, [
$doorId,
$userId
]);
return array_map(function ($group) {
return new Group(
$group->id,
$group->title,
$group->description,
$this->castToDate($group->created_at),
$this->castToDate($group->updated_at)
);
}, $commonGroups);
}
}
<?php
namespace Source\Gateways\DoorUser;
interface DoorUserRepository
{
/**
* Get groups that a given user and door have in common.
*
* @param string $doorId
* @param string $userId
* @return \Source\Entities\Group[]
*/
public function getDoorUserGroupIntersection(string $doorId, string $userId): array;
}
<?php
namespace Source\Gateways\DoorUser;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\DatabaseManager;
use Source\Gateways\Doors\DoorsRepository;
use Source\Gateways\Users\UsersRepository;
use Illuminate\Contracts\Foundation\Application;
use Source\Gateways\DoorGroup\DoorGroupRepository;
use Source\Gateways\GroupUser\GroupUserRepository;
use Illuminate\Contracts\Support\DeferrableProvider;
/**
* Service provider must be registered in AppServiceProvider
*/
class DoorUserRepositoryServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->singleton(DoorUserRepository::class, static function (Application $app) {
if (env('APP_ENV') === 'memory') {
return new LocalDoorUserRepository(
$app->make(UsersRepository::class),
$app->make(DoorsRepository::class),
$app->make(GroupUserRepository::class),
$app->make(DoorGroupRepository::class)
);
}
if (env('APP_ENV') === 'testing') {
return new InMemoryDoorUserRepository();
}
/** @var DatabaseManager $manager */
$manager = $app->make(DatabaseManager::class);
return new DatabaseDoorUserRepository($manager->connection('doorcode'));
});
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot(): void
{
}
/**
* @return array
*/
public function provides()
{
return [DoorUserRepository::class];
}
}
<?php
namespace Source\Gateways\DoorUser;
class InMemoryDoorUserRepository implements DoorUserRepository
{
/**
* @var array
*/
protected array $doorGroupMap = [];
/**
* @var array
*/
protected array $userGroupMap = [];
/**
* @param string $doorId
* @param string $groupId
*/
public function addGroupToDoor(string $doorId, string $groupId): void
{
if (isset($this->doorGroupMap[$doorId])) {
$this->doorGroupMap[$doorId][] = $groupId;
} else {
$this->doorGroupMap[$doorId] = [$groupId];
}
}
/**
* @param string $userId
* @param string $groupId
*/
public function addGroupToUser(string $userId, string $groupId): void
{
if (isset($this->userGroupMap[$userId])) {
$this->userGroupMap[$userId][] = $groupId;
} else {
$this->userGroupMap[$userId] = [$groupId];
}
}
/**
* @inheritDoc
*/
public function getDoorUserGroupIntersection(string $doorId, string $userId): array
{
if (!isset($this->doorGroupMap[$doorId], $this->userGroupMap[$userId])) {
return [];
}
$doorGroups = $this->doorGroupMap[$doorId];
$userGroups = $this->userGroupMap[$userId];
return array_intersect($doorGroups, $userGroups);
}
}
<?php
namespace Source\Gateways\DoorUser;
use Source\Gateways\Doors\DoorsRepository;
use Source\Gateways\Users\UsersRepository;
use Source\Exceptions\EntityNotFoundException;
use Source\Gateways\DoorGroup\DoorGroupRepository;
use Source\Gateways\GroupUser\GroupUserRepository;
class LocalDoorUserRepository extends InMemoryDoorUserRepository
{
public function __construct(
UsersRepository $users,
DoorsRepository $doors,
GroupUserRepository $groupUser,
DoorGroupRepository $doorGroup
) {
foreach ($users->all() as $user) {
try {
foreach ($groupUser->getGroupsForUser($user->getId()) as $group) {
$this->addGroupToUser($user->getId(), $group->getId());
}
} catch (EntityNotFoundException $e) {
// nothing
}
}
foreach ($doors->all() as $door) {
try {
foreach ($doorGroup->getGroupsForDoor($door->getId()) as $group) {
$this->addGroupToDoor($door->getId(), $group->getId());
}
} catch (EntityNotFoundException $e) {
// nothing
}
}
}
}
<?php
namespace Source\Sanitize;
use Carbon\Carbon;
trait CastsTo
{
/**
* @param $date
* @param string $format
*
* @return \Carbon\Carbon|null
*/
protected function castToDate($date, $format = 'Y-m-d H:i:s'): ?Carbon
{
if (empty($date)) {
return null;
}
return Carbon::createFromFormat($format, $date);
}
}
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