Commit 2c5920b1 authored by Jacob Priddy's avatar Jacob Priddy 👌

Authorizer done. Fix generate stubs with current code styles

Create GroupUser table Gateway
Create Tests for the Authorizer
parent 49db0cbe
......@@ -3,5 +3,6 @@
namespace DummyNamespace;
class DatabaseDummyClassRepository implements DummyClassRepository {
class DatabaseDummyClassRepository implements DummyClassRepository
{
}
......@@ -3,5 +3,6 @@
namespace DummyNamespace;
class InMemoryDummyClassRepository implements DummyClassRepository {
class InMemoryDummyClassRepository implements DummyClassRepository
{
}
......@@ -3,5 +3,6 @@
namespace DummyNamespace;
class LocalDummyClassRepository extends InMemoryDummyClassRepository {
class LocalDummyClassRepository extends InMemoryDummyClassRepository
{
}
......@@ -10,13 +10,15 @@ use Illuminate\Contracts\Support\DeferrableProvider;
/**
* Service provider must be registered in AppServiceProvider
*/
class DummyClassRepositoryServiceProvider extends ServiceProvider implements DeferrableProvider {
class DummyClassRepositoryServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register() {
public function register()
{
$this->app->singleton(DummyClassRepository::class, static function (Application $app) {
if (env('APP_ENV') === 'memory') {
return new LocalDummyClassRepository();
......@@ -35,13 +37,15 @@ class DummyClassRepositoryServiceProvider extends ServiceProvider implements Def
*
* @return void
*/
public function boot(): void {
public function boot(): void
{
}
/**
* @return array
*/
public function provides() {
public function provides()
{
return [DummyClassRepository::class];
}
}
......@@ -3,5 +3,6 @@
namespace DummyNamespace;
interface DummyClassRepository {
interface DummyClassRepository
{
}
......@@ -3,5 +3,6 @@
namespace DummyNamespace;
interface DummyClassUseCase {
interface DummyClassUseCase
{
}
......@@ -4,15 +4,18 @@ namespace DummyNamespace;
use Source\UseCases\BasePresenter;
class DummyClass extends BasePresenter implements Presenter {
class DummyClass extends BasePresenter implements Presenter
{
protected array $viewModel = [];
/** @inheritDoc */
public function present(ResponseModel $responseModel): void {
public function present(ResponseModel $responseModel): void
{
}
/** @inheritDoc */
public function getViewModel(): array {
public function getViewModel(): array
{
return $this->viewModel;
}
}
......@@ -10,13 +10,15 @@ use Illuminate\Contracts\Support\DeferrableProvider;
/**
* Service provider must be registered in AppServiceProvider
*/
class DummyClassUseCaseServiceProvider extends ServiceProvider implements DeferrableProvider {
class DummyClassUseCaseServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register() {
public function register()
{
$this->app->bind(DummyClassUseCase::class, static function (Application $app) {
return new DummyClass();
});
......@@ -27,13 +29,15 @@ class DummyClassUseCaseServiceProvider extends ServiceProvider implements Deferr
*
* @return void
*/
public function boot(): void {
public function boot(): void
{
}
/**
* @return array
*/
public function provides() {
public function provides()
{
return [DummyClassUseCase::class];
}
}
......@@ -2,5 +2,6 @@
namespace DummyNamespace;
class ResponseModel {
class ResponseModel
{
}
......@@ -2,7 +2,9 @@
namespace DummyNamespace;
class DummyClass implements DummyClassUseCase {
public function __construct() {
class DummyClass implements DummyClassUseCase
{
public function __construct()
{
}
}
......@@ -2,7 +2,8 @@
namespace DummyNamespace;
interface Presenter {
interface Presenter
{
/**
* @param ResponseModel $responseModel
* @return void
......
......@@ -2,10 +2,13 @@
namespace App;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Door extends Authenticatable
{
use SoftDeletes;
protected $fillable = [
'*'
];
......
......@@ -4,15 +4,27 @@
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Group extends Model
{
use SoftDeletes;
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function groups(): BelongsToMany
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
public static function boot()
{
parent::boot();
static::deleting(static function (Group $group) {
$group->users()->detach();
});
}
}
......@@ -3,11 +3,13 @@
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Source\Authorization\AuthorizerServiceProvider;
use Source\Gateways\Saml\SamlRepositoryServiceProvider;
use Source\Gateways\Doors\DoorsRepositoryServiceProvider;
use Source\Gateways\Users\UsersRepositoryServiceProvider;
use Source\Gateways\Tokens\TokensRepositoryServiceProvider;
use Source\UseCases\Users\GetUser\GetUserUseCaseServiceProvider;
use Source\Gateways\GroupUser\GroupUserRepositoryServiceProvider;
use Source\UseCases\Users\CreateUser\CreateUserUseCaseServiceProvider;
use Source\UseCases\Users\DeleteUser\DeleteUserUseCaseServiceProvider;
use Source\UseCases\Users\UpdateUser\UpdateUserUseCaseServiceProvider;
......@@ -23,10 +25,12 @@ class AppServiceProvider extends ServiceProvider
* @var string[]
*/
protected array $gatewayProviders = [
AuthorizerServiceProvider::class,
SamlRepositoryServiceProvider::class,
UsersRepositoryServiceProvider::class,
DoorsRepositoryServiceProvider::class,
TokensRepositoryServiceProvider::class,
GroupUserRepositoryServiceProvider::class,
];
/**
......@@ -52,6 +56,8 @@ class AppServiceProvider extends ServiceProvider
public function register()
{
// We register gateways first, as use cases will generally use gateways
// Not that it actually matters because they are all deferred anyway
// and will all be registered before any need to be created.
foreach ($this->gatewayProviders as $gatewayProvider) {
$this->app->registerDeferredProvider($gatewayProvider);
}
......
......@@ -39,4 +39,20 @@ class User extends Authenticatable
{
return $this->belongsToMany(Group::class);
}
public static function boot()
{
parent::boot();
static::deleting(static function (User $user) {
// Detach all groups
$user->groups()->detach();
// Delete all tokens
/** @var \App\Token $token */
foreach ($user->tokens() as $token) {
$token->delete();
}
});
}
}
......@@ -5,37 +5,113 @@ namespace Source\Authorization;
use Illuminate\Contracts\Auth\Guard;
use Source\Exceptions\AuthorizationException;
use Source\Gateways\GroupUser\GroupUserRepository;
class ApiAuthorizer implements Authorizer
{
/**
* @var \Illuminate\Contracts\Auth\Guard
*/
protected Guard $guard;
public function __construct(Guard $guard)
/**
* @var \Source\Gateways\GroupUser\GroupUserRepository
*/
protected GroupUserRepository $groupUserRepository;
public function __construct(Guard $guard, GroupUserRepository $groupUserRepository)
{
$this->guard = $guard;
$this->groupUserRepository = $groupUserRepository;
}
/**
* @inheritDoc
* @param \Source\Entities\Group[] $groups
* @return string[]
*/
public function protect(array $permissions): void
protected static function groupNames(array $groups): array
{
if (!$this->allows($permissions)) {
throw new AuthorizationException();
$groupNames = [];
foreach ($groups as $group) {
$groupNames[] = $group->getTitle();
}
return $groupNames;
}
/**
* @inheritDoc
* @return string[]
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function allows(array $permissions): bool
protected function getGroupsForCurrentUser(): array
{
$user = $this->guard->user();
$user = $this->guard->id();
if (!$user) {
return false;
return [];
}
return static::groupNames($this->groupUserRepository->getGroupsForUser($user));
}
/**
* @inheritDoc
*/
public function allowsAll(array $permissions): bool
{
$groups = $this->getGroupsForCurrentUser();
if (in_array(Permissions::ADMIN, $groups, true)) {
return true;
}
foreach ($permissions as $permission) {
if (!in_array($permission, $groups, true)) {
return false;
}
}
return true;
}
/**
* @inheritDoc
*/
public function allowsOne(array $permissions): bool
{
$groups = $this->getGroupsForCurrentUser();
if (in_array(Permissions::ADMIN, $groups, true)) {
return true;
}
foreach ($groups as $group) {
if (in_array($group, $permissions, true)) {
return true;
}
}
return false;
}
/**
* @inheritDoc
*/
public function protectAll(array $permissions): void
{
if (!$this->allowsAll($permissions)) {
throw new AuthorizationException();
}
}
/**
* @inheritDoc
*/
public function protectOne(array $permissions): void
{
if (!$this->allowsOne($permissions)) {
throw new AuthorizationException();
}
}
}
......@@ -6,14 +6,40 @@ namespace Source\Authorization;
interface Authorizer
{
/**
* @param array $permissions
* The user needs ALL of the permissions.
*
* @param string[] $permissions
* @return bool
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function allows(array $permissions): bool;
public function allowsAll(array $permissions): bool;
/**
* @param array $permissions
* The user needs only ONE of the permissions
*
* @param string[] $permissions
* @return bool
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function allowsOne(array $permissions): bool;
/**
* Permissions to check against from \Source\Authorization\Permissions
* Te user needs ALL of the permissions
*
* @param string[] $permissions
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function protectAll(array $permissions): void;
/**
* Permissions to check against from \Source\Authorization\Permissions
* The user only needs ONE of the permissions
*
* @param string[] $permissions
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function protect(array $permissions): void;
public function protectOne(array $permissions): void;
}
<?php
namespace Source\Authorization;
use Carbon\Laravel\ServiceProvider;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Foundation\Application;
use Source\Gateways\GroupUser\GroupUserRepository;
use Illuminate\Contracts\Support\DeferrableProvider;
/**
* Service provider must be registered in AppServiceProvider
*/
class AuthorizerServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind(Authorizer::class, static function (Application $app) {
return new ApiAuthorizer($app->make(Guard::class), $app->make(GroupUserRepository::class));
});
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot(): void {
}
/**
* @return array
*/
public function provides()
{
return [Authorizer::class];
}
}
<?php
namespace Source\Gateways\GroupUser;
use App\User;
use App\Group;
use Source\Exceptions\EntityNotFoundException;
use Source\Gateways\Users\DatabaseUsersRepository;
use Source\Gateways\Groups\DatabaseGroupsRepository;
// TODO: Write tests for this
class DatabaseGroupUserRepository implements GroupUserRepository
{
/**
* @inheritDoc
*/
public function getGroupsForUser(string $userId): array
{
/** @var User|null $user */
$user = User::find($userId);
if (!$user) {
throw new EntityNotFoundException();
}
return array_map(static function (Group $group) {
return DatabaseGroupsRepository::makeGroupFromDbGroup($group);
}, $user->groups()->get()->values()->all());
}
/**
* @inheritDoc
*/
public function getUsersForGroup(string $groupId): array
{
/** @var Group|null $group */
$group = Group::find($groupId);
if (!$group) {
throw new EntityNotFoundException();
}
return array_map(static function (User $user) {
return DatabaseUsersRepository::makeUserFromDbUser($user);
}, $group->users()->get()->values()->all());
}
/**
* @inheritDoc
*/
public function addUserToGroup(string $userId, string $groupId): void
{
/** @var User|null $user */
$user = User::find($userId);
/** @var Group|null $group */
$group = Group::find($groupId);
if (!$user || !$group) {
throw new EntityNotFoundException();
}
$user->groups()->attach($groupId);
}
/**
* @inheritDoc
*/
public function removeUserFromGroup(string $userId, string $groupId): void
{
/** @var User|null $user */
$user = User::find($userId);
/** @var Group|null $group */
$group = Group::find($groupId);
if (!$user || !$group) {
throw new EntityNotFoundException();
}
$user->groups()->detach($groupId);
}
}
<?php
namespace Source\Gateways\GroupUser;
interface GroupUserRepository
{
/**
* @param string $userId
* @return \Source\Entities\Group[]
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function getGroupsForUser(string $userId): array;
/**
* @param string $groupId
* @return \Source\Entities\User[]
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function getUsersForGroup(string $groupId): array;
/**
* @param string $userId
* @param string $groupId
* @throws \Source\Exceptions\EntityNotFoundException
*/