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

overhaul some permissions stuff

parent 62dc4fe8
Pipeline #2922 passed with stages
in 2 minutes and 1 second
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}
<?php
/*
|--------------------------------------------------------------------------
| Broadcast Channels
|--------------------------------------------------------------------------
|
| Here you may register all of the event broadcasting channels that your
| application supports. The given channel authorization callbacks are
| used to check if an authenticated user can listen to the channel.
|
*/
Broadcast::channel('App.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
......@@ -5,6 +5,7 @@ namespace Source\Authorization;
use Illuminate\Contracts\Auth\Guard;
use Source\Exceptions\AuthorizationException;
use Source\Exceptions\EntityNotFoundException;
use Source\Gateways\GroupUser\GroupUserRepository;
class ApiAuthorizer implements Authorizer
......@@ -67,7 +68,17 @@ class ApiAuthorizer implements Authorizer
return [];
}
return static::groupNames($this->groupUserRepository->getGroupsForUser($user));
return $this->getGroupsForUser($user);
}
/**
* @param string $userId
* @return string[]
* @throws \Source\Exceptions\EntityNotFoundException
*/
protected function getGroupsForUser(string $userId): array
{
return static::groupNames($this->groupUserRepository->getGroupsForUser($userId));
}
/**
......@@ -114,4 +125,46 @@ class ApiAuthorizer implements Authorizer
return false;
}
/**
* @inheritDoc
*/
public function protect(string $permission): void
{
$this->protectAll([$permission]);
}
/**
* @inheritDoc
*/
public function protectAdminRights(string $userId, ?string $groupId = null): void
{
$user = $this->groupUserRepository->getUser($userId);
if (!$user) {
throw new EntityNotFoundException('User not found.');
}
$groups = $this->getGroupsForUser($userId);
// Only admins have access to other admins
if (in_array(Permissions::ADMIN, $groups, true)) {
$this->protect(Permissions::ADMIN);
}
if ($groupId) {
$group = $this->groupUserRepository->getGroup($groupId);
if (!$group) {
throw new EntityNotFoundException('Group not found.');
}
// Only admins can create or remove other admins
if ($group->hasTitleOf(Permissions::ADMIN)) {
$this->protect(Permissions::ADMIN);
}
}
if ($user->hasFirstNameOf('admin')) {
throw new AuthorizationException('You cannot modify the admin user.');
}
}
}
......@@ -42,4 +42,24 @@ interface Authorizer
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function protectOne(array $permissions): void;
/**
* Permissions to check against from \Source\Authorization\Permissions
* The user needs the permission
*
* @param string $permission
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function protect(string $permission): void;
/**
* Protects the admin user from being modified.
*
* @param string $userId
* @param string|null $groupId
* @throws \Source\Exceptions\AuthorizationException
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function protectAdminRights(string $userId, ?string $groupId = null): void;
}
......@@ -21,7 +21,7 @@ class AuthorizerServiceProvider extends ServiceProvider implements DeferrablePro
*/
public function register()
{
$this->app->bind(Authorizer::class, static function (Application $app) {
$this->app->singleton(Authorizer::class, static function (Application $app) {
return new ApiAuthorizer($app->make(ApiGuard::class), $app->make(GroupUserRepository::class));
});
}
......
......@@ -10,4 +10,5 @@ class Permissions
public const MANAGE_DOORS = 'manage-doors';
public const MANAGE_GROUPS = 'manage-groups';
public const CODE_QUERY = 'code-query';
public const CURRENT_USER = 'current-user';
}
......@@ -236,4 +236,22 @@ class User
{
return $this->getEmail() === strtolower($email);
}
public function hasFirstNameOf(?string $name): bool
{
if (!$name) {
return false;
}
return $this->getFirstName() === $name;
}
public function is(?User $user): bool
{
if (!$user) {
return null;
}
return $this->hasEmailOf($user);
}
}
......@@ -55,8 +55,12 @@ class DatabaseGroupUserRepository implements GroupUserRepository
/** @var Group|null $group */
$group = Group::find((int)$groupId);
if (!$user || !$group) {
throw new EntityNotFoundException();
if (!$user) {
throw new EntityNotFoundException('User not found.');
}
if (!$group) {
throw new EntityNotFoundException('Group not found.');
}
$user->groups()->attach($groupId);
......@@ -72,10 +76,38 @@ class DatabaseGroupUserRepository implements GroupUserRepository
/** @var Group|null $group */
$group = Group::find((int)$groupId);
if (!$user || !$group) {
throw new EntityNotFoundException();
if (!$user) {
throw new EntityNotFoundException('User not found.');
}
if (!$group) {
throw new EntityNotFoundException('Group not found.');
}
$user->groups()->detach($groupId);
}
/**
* @inheritDoc
*/
public function getGroup(string $groupId): ?\Source\Entities\Group
{
if ($g = Group::find((int)$groupId)) {
return DatabaseGroupsRepository::makeGroupFromDbGroup($g);
}
return null;
}
/**
* @inheritDoc
*/
public function getUser(string $userId): ?\Source\Entities\User
{
if ($user = User::find((int)$userId)) {
return DatabaseUsersRepository::makeUserFromDbUser($user);
}
return null;
}
}
......@@ -3,6 +3,9 @@
namespace Source\Gateways\GroupUser;
use Source\Entities\User;
use Source\Entities\Group;
interface GroupUserRepository
{
/**
......@@ -32,4 +35,16 @@ interface GroupUserRepository
* @throws \Source\Exceptions\EntityNotFoundException
*/
public function removeUserFromGroup(string $userId, string $groupId): void;
/**
* @param string $groupId
* @return \Source\Entities\Group|null
*/
public function getGroup(string $groupId): ?Group;
/**
* @param string $userId
* @return \Source\Entities\User|null
*/
public function getUser(string $userId): ?User;
}
......@@ -3,6 +3,8 @@
namespace Source\Gateways\GroupUser;
use Source\Entities\User;
use Source\Entities\Group;
use Source\Gateways\Users\UsersRepository;
use Source\Gateways\Groups\GroupsRepository;
use Source\Exceptions\EntityNotFoundException;
......@@ -111,4 +113,20 @@ class InMemoryGroupUserRepository implements GroupUserRepository
);
}
}
/**
* @inheritDoc
*/
public function getGroup(string $groupId): ?Group
{
return $this->groups->get($groupId);
}
/**
* @inheritDoc
*/
public function getUser(string $userId): ?User
{
return $this->users->get($userId);
}
}
......@@ -122,14 +122,6 @@ class InMemoryUsersRepository implements UsersRepository
return null;
}
/**
* Clears the repository
*/
public function clear(): void
{
$this->users = [];
}
/**
* @inheritDoc
*/
......
......@@ -2,6 +2,7 @@
namespace Source\UseCases\GroupUser\AddUserToGroup;
use Source\Authorization\Authorizer;
use Source\Gateways\GroupUser\GroupUserRepository;
class AddUserToGroup implements AddUserToGroupUseCase
......@@ -11,9 +12,19 @@ class AddUserToGroup implements AddUserToGroupUseCase
*/
protected GroupUserRepository $repository;
public function __construct(GroupUserRepository $repository)
/**
* @var \Source\Authorization\Authorizer
*/
protected Authorizer $authorizer;
/**
* @param \Source\Authorization\Authorizer $authorizer
* @param \Source\Gateways\GroupUser\GroupUserRepository $repository
*/
public function __construct(Authorizer $authorizer, GroupUserRepository $repository)
{
$this->repository = $repository;
$this->authorizer = $authorizer;
}
/**
......@@ -21,6 +32,8 @@ class AddUserToGroup implements AddUserToGroupUseCase
*/
public function addUserToGroup(string $userId, string $groupId, Presenter $presenter): void
{
$this->authorizer->protectAdminRights($userId, $groupId);
$this->repository->addUserToGroup($userId, $groupId);
$responseModel = new ResponseModel('Success');
......
......@@ -10,6 +10,7 @@ interface AddUserToGroupUseCase
* @param string $groupId
* @param \Source\UseCases\GroupUser\AddUserToGroup\Presenter $presenter
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
*/
public function addUserToGroup(string $userId, string $groupId, Presenter $presenter): void;
}
......@@ -3,6 +3,7 @@
namespace Source\UseCases\GroupUser\AddUserToGroup;
use Source\Authorization\Authorizer;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Foundation\Application;
use Source\Gateways\GroupUser\GroupUserRepository;
......@@ -21,7 +22,7 @@ class AddUserToGroupUseCaseServiceProvider extends ServiceProvider implements De
public function register()
{
$this->app->bind(AddUserToGroupUseCase::class, static function (Application $app) {
return new AddUserToGroup($app->make(GroupUserRepository::class));
return new AddUserToGroup($app->make(Authorizer::class), $app->make(GroupUserRepository::class));
});
}
......
......@@ -2,6 +2,7 @@
namespace Source\UseCases\GroupUser\RemoveUserFromGroup;
use Source\Authorization\Authorizer;
use Source\Gateways\GroupUser\GroupUserRepository;
class RemoveUserFromGroup implements RemoveUserFromGroupUseCase
......@@ -11,9 +12,15 @@ class RemoveUserFromGroup implements RemoveUserFromGroupUseCase
*/
protected GroupUserRepository $repository;
public function __construct(GroupUserRepository $repository)
/**
* @var \Source\Authorization\Authorizer
*/
protected Authorizer $authorizer;
public function __construct(Authorizer $authorizer, GroupUserRepository $repository)
{
$this->repository = $repository;
$this->authorizer = $authorizer;
}
/**
......@@ -21,6 +28,8 @@ class RemoveUserFromGroup implements RemoveUserFromGroupUseCase
*/
public function removeUserFromGroup(string $userId, string $groupId, Presenter $presenter): void
{
$this->authorizer->protectAdminRights($userId, $groupId);
$this->repository->removeUserFromGroup($userId, $groupId);
$responseModel = new ResponseModel('Success');
......
......@@ -6,10 +6,13 @@ namespace Source\UseCases\GroupUser\RemoveUserFromGroup;
interface RemoveUserFromGroupUseCase
{
/**
* Only Admins can add or remove other admins, but the overall admin user's groups cannot be modified
*
* @param string $userId
* @param string $groupId
* @param \Source\UseCases\GroupUser\RemoveUserFromGroup\Presenter $presenter
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
*/
public function removeUserFromGroup(string $userId, string $groupId, Presenter $presenter): void;
}
......@@ -3,6 +3,7 @@
namespace Source\UseCases\GroupUser\RemoveUserFromGroup;
use Source\Authorization\Authorizer;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Foundation\Application;
use Source\Gateways\GroupUser\GroupUserRepository;
......@@ -21,7 +22,7 @@ class RemoveUserFromGroupUseCaseServiceProvider extends ServiceProvider implemen
public function register()
{
$this->app->bind(RemoveUserFromGroupUseCase::class, static function (Application $app) {
return new RemoveUserFromGroup($app->make(GroupUserRepository::class));
return new RemoveUserFromGroup($app->make(Authorizer::class), $app->make(GroupUserRepository::class));
});
}
......
......@@ -3,6 +3,7 @@
namespace Source\UseCases\Groups\UpdateGroup;
use Source\Entities\Group;
use Source\Authorization\Permissions;
use Source\Gateways\Groups\GroupsRepository;
use Source\Exceptions\EntityNotFoundException;
......@@ -32,9 +33,19 @@ class UpdateGroup implements UpdateGroupUseCase
throw new EntityNotFoundException();
}
$reflection = new \ReflectionClass(Permissions::class);
if (in_array($group->getTitle(), $reflection->getConstants(), true)) {
// Cannot modify default permission group title
$groupTitle = $group->getTitle();
} else {
$groupTitle = $attributes['title'];
}
$newGroup = new Group(
$group->getId(),
$attributes['title'],
$groupTitle,
$attributes['description']
);
......
......@@ -2,6 +2,7 @@
namespace Source\UseCases\Users\DeleteUser;
use Source\Authorization\Authorizer;
use Source\Gateways\Users\UsersRepository;
use Source\Exceptions\DeleteFailedException;
use Source\Exceptions\EntityNotFoundException;
......@@ -13,8 +14,14 @@ class DeleteUser implements DeleteUserUseCase
*/
protected UsersRepository $usersRepository;
public function __construct(UsersRepository $usersRepository)
/**
* @var \Source\Authorization\Authorizer
*/
protected Authorizer $authorizer;
public function __construct(Authorizer $authorizer, UsersRepository $usersRepository)
{
$this->authorizer = $authorizer;
$this->usersRepository = $usersRepository;
}
......@@ -23,16 +30,14 @@ class DeleteUser implements DeleteUserUseCase
*/
public function delete(string $userId, Presenter $presenter): void
{
$this->authorizer->protectAdminRights($userId);
$user = $this->usersRepository->get($userId);
if (!$user) {
throw new EntityNotFoundException();
}
if ($user->getFirstName() === 'admin') {
throw new DeleteFailedException('Cannot delete super admin user.');
}
if (!$this->usersRepository->delete($userId)) {
throw new DeleteFailedException('Unable to delete user.');
}
......
......@@ -10,6 +10,7 @@ interface DeleteUserUseCase
* @param \Source\UseCases\Users\DeleteUser\Presenter $presenter
* @throws \Source\Exceptions\DeleteFailedException
* @throws \Source\Exceptions\EntityNotFoundException
* @throws \Source\Exceptions\AuthorizationException
*/
public function delete(string $userId, Presenter $presenter): void;
}
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