Skip to content

Commit a6b653b

Browse files
committed
Added auth/rbac system.
Signed-off-by: Joshua Parker <joshua@joshuaparker.dev>
1 parent 45642b5 commit a6b653b

26 files changed

+1525
-0
lines changed

Auth/Auth.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codefy\Framework\Auth;
6+
7+
use Codefy\Framework\Auth\Rbac\Rbac;
8+
use Codefy\Framework\Auth\Repository\AuthUserRepository;
9+
use Psr\Http\Message\ResponseFactoryInterface;
10+
use Psr\Http\Message\ResponseInterface;
11+
use Psr\Http\Message\ServerRequestInterface;
12+
use Qubus\Config\ConfigContainer;
13+
use Qubus\Exception\Exception;
14+
use Qubus\Http\Session\SessionEntity;
15+
16+
class Auth implements Sentinel
17+
{
18+
public function __construct(
19+
protected AuthUserRepository $repository,
20+
protected ConfigContainer $configContainer,
21+
protected Rbac $rbac,
22+
protected ResponseFactoryInterface $responseFactory,
23+
) {
24+
}
25+
26+
/**
27+
* @throws Exception
28+
*/
29+
public function authenticate(ServerRequestInterface $request): ?SessionEntity
30+
{
31+
$params = $request->getParsedBody();
32+
$identity = $this->configContainer->getConfigKey(key: 'auth.pdo.identity', default: 'username');
33+
$password = $this->configContainer->getConfigKey(key: 'auth.pdo.password', default: 'password');
34+
35+
if (! isset($params[$identity]) || ! isset($params[$password])) {
36+
return null;
37+
}
38+
39+
$user = $this->repository->authenticate(
40+
credential: $params[$identity],
41+
password: $params[$password]
42+
);
43+
44+
if (null !== $user) {
45+
return $user;
46+
}
47+
48+
return null;
49+
}
50+
51+
/**
52+
* @throws Exception
53+
*/
54+
public function unauthorized(ServerRequestInterface $request): ResponseInterface
55+
{
56+
return $this->responseFactory
57+
->createResponse(code: 302)
58+
->withHeader(
59+
name: 'Location',
60+
value: $this->configContainer->getConfigKey(
61+
key: 'auth.http_redirect',
62+
default: $this->configContainer->getConfigKey(key: 'auth.login_url')
63+
)
64+
);
65+
}
66+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codefy\Framework\Auth\Middleware;
6+
7+
use Codefy\Framework\Auth\Sentinel;
8+
use Psr\Http\Message\ResponseInterface;
9+
use Psr\Http\Message\ServerRequestInterface;
10+
use Psr\Http\Server\MiddlewareInterface;
11+
use Psr\Http\Server\RequestHandlerInterface;
12+
use Qubus\Http\Session\SessionEntity;
13+
14+
use function Qubus\Support\Helpers\is_null__;
15+
16+
final class AuthenticationMiddleware implements MiddlewareInterface
17+
{
18+
public const AUTH_ATTRIBUTE = 'USERAUTH';
19+
20+
public function __construct(protected Sentinel $auth)
21+
{
22+
}
23+
24+
/**
25+
* {@inheritDoc}
26+
*/
27+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
28+
{
29+
$user = $this->auth->authenticate($request);
30+
if (is_null__(var: $user) || !$user instanceof SessionEntity) {
31+
return $this->auth->unauthorized($request);
32+
}
33+
return $handler->handle(request: $request->withAttribute(name: self::AUTH_ATTRIBUTE, value: $user));
34+
}
35+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codefy\Framework\Auth\Middleware;
6+
7+
use Codefy\Framework\Auth\UserSession;
8+
use Psr\Http\Message\ResponseInterface;
9+
use Psr\Http\Message\ServerRequestInterface;
10+
use Psr\Http\Server\MiddlewareInterface;
11+
use Psr\Http\Server\RequestHandlerInterface;
12+
use Qubus\Config\ConfigContainer;
13+
use Qubus\Exception\Data\TypeException;
14+
use Qubus\Exception\Exception;
15+
use Qubus\Http\Session\SessionService;
16+
17+
final class UserSessionMiddleware implements MiddlewareInterface
18+
{
19+
public const SESSION_ATTRIBUTE = 'USERSESSION';
20+
21+
public function __construct(protected ConfigContainer $configContainer, protected SessionService $sessionService)
22+
{
23+
}
24+
25+
/**
26+
* @throws TypeException
27+
* @throws Exception
28+
* @throws \Exception
29+
*/
30+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
31+
{
32+
$userDetails = $request->getAttribute(name: AuthenticationMiddleware::AUTH_ATTRIBUTE);
33+
34+
$this->sessionService::$options = [
35+
'cookie-name' => 'USERSESSID',
36+
'cookie-lifetime' => (int) $this->configContainer->getConfigKey(key: 'cookies.lifetime', default: 86400),
37+
];
38+
$session = $this->sessionService->makeSession($request);
39+
40+
/** @var UserSession $user */
41+
$user = $session->get(type: UserSession::class);
42+
$user
43+
->withToken(token: $userDetails->token)
44+
->withRole(role: $userDetails->role);
45+
46+
$request = $request->withAttribute(name: self::SESSION_ATTRIBUTE, value: $user);
47+
48+
$response = $handler->handle($request);
49+
50+
return $this->sessionService->commitSession($response, $session);
51+
}
52+
}

Auth/Rbac/Entity/AssertionRule.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codefy\Framework\Auth\Rbac\Entity;
6+
7+
interface AssertionRule
8+
{
9+
/**
10+
* @param array|null $params
11+
* @return bool
12+
*/
13+
public function execute(?array $params): bool;
14+
}

Auth/Rbac/Entity/Permission.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codefy\Framework\Auth\Rbac\Entity;
6+
7+
interface Permission
8+
{
9+
/**
10+
* @return string
11+
*/
12+
public function getName(): string;
13+
14+
/**
15+
* @return string
16+
*/
17+
public function getDescription(): string;
18+
19+
/**
20+
* @param Permission $permission
21+
*/
22+
public function addChild(Permission $permission);
23+
24+
/**
25+
* @param string $permissionName
26+
*/
27+
public function removeChild(string $permissionName);
28+
29+
/**
30+
* @return Permission[]
31+
*/
32+
public function getChildren(): array;
33+
34+
/**
35+
* @param string $ruleClass
36+
*/
37+
public function setRuleClass(string $ruleClass);
38+
39+
/**
40+
* @return string|null;
41+
*/
42+
public function getRuleClass(): ?string;
43+
44+
/**
45+
* @param array|null $params
46+
* @return bool
47+
*/
48+
public function checkAccess(array $params = null): bool;
49+
}

Auth/Rbac/Entity/RbacPermission.php

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codefy\Framework\Auth\Rbac\Entity;
6+
7+
use Codefy\Framework\Auth\Rbac\Exception\SentinelException;
8+
use Codefy\Framework\Auth\Rbac\Resource\StorageResource;
9+
use Throwable;
10+
11+
use function array_keys;
12+
use function sprintf;
13+
14+
class RbacPermission implements Permission
15+
{
16+
protected array $childrenNames = [];
17+
protected ?string $ruleClass = '';
18+
19+
/**
20+
* @param string $permissionName
21+
* @param string $description
22+
* @param StorageResource $rbacStorageCollection
23+
*/
24+
public function __construct(
25+
protected string $permissionName,
26+
protected string $description,
27+
protected StorageResource $rbacStorageCollection
28+
) {
29+
}
30+
31+
/**
32+
* @return string
33+
*/
34+
public function getName(): string
35+
{
36+
return $this->permissionName;
37+
}
38+
39+
/**
40+
* @return string
41+
*/
42+
public function getDescription(): string
43+
{
44+
return $this->description;
45+
}
46+
47+
/**
48+
* @param Permission $permission
49+
*/
50+
public function addChild(Permission $permission): void
51+
{
52+
$this->childrenNames[$permission->getName()] = true;
53+
}
54+
55+
/**
56+
* @param string $permissionName
57+
*/
58+
public function removeChild(string $permissionName): void
59+
{
60+
unset($this->childrenNames[$permissionName]);
61+
}
62+
63+
/**
64+
* @return Permission[]
65+
*/
66+
public function getChildren(): array
67+
{
68+
$result = [];
69+
$permissionNames = array_keys(array: $this->childrenNames);
70+
foreach ($permissionNames as $name) {
71+
$result[$name] = $this->rbacStorageCollection->getPermission(name: $name);
72+
}
73+
return $result;
74+
}
75+
76+
/**
77+
* @param string $ruleClass
78+
*/
79+
public function setRuleClass(string $ruleClass): void
80+
{
81+
$this->ruleClass = $ruleClass;
82+
}
83+
84+
/**
85+
* @return string|null;
86+
*/
87+
public function getRuleClass(): ?string
88+
{
89+
return $this->ruleClass;
90+
}
91+
92+
/**
93+
* @param array|null $params
94+
* @return bool
95+
* @throws SentinelException
96+
*/
97+
public function checkAccess(?array $params = null): bool
98+
{
99+
$result = true;
100+
if ($ruleClass = $this->getRuleClass()) {
101+
try {
102+
$rule = new $ruleClass();
103+
if (!$rule instanceof AssertionRule) {
104+
throw new SentinelException(
105+
sprintf(
106+
'Rule class: %s is not an instance of %s.',
107+
$rule,
108+
AssertionRule::class
109+
)
110+
);
111+
}
112+
113+
$result = $rule->execute(params: $params);
114+
} catch (Throwable $e) {
115+
throw new SentinelException(sprintf('Cannot instantiate rule class: %s.', $ruleClass));
116+
}
117+
}
118+
return $result;
119+
}
120+
}

0 commit comments

Comments
 (0)