- WIP on authentication
This commit is contained in:
parent
9ae3e2d6f9
commit
0f2886b266
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\User\Authorize\Header;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Ulmus\User\Entity\UserInterface;
|
||||
use Ulmus\User\Lib\Authorize;
|
||||
|
||||
class BasicMethod
|
||||
{
|
||||
public function __construct(
|
||||
protected UserInterface $user,
|
||||
protected string|array $arguments
|
||||
) {}
|
||||
|
||||
public function execute(ServerRequestInterface $request) : bool
|
||||
{
|
||||
if ( false === $decoded = base64_decode($this->arguments) ) {
|
||||
throw new \RuntimeException("Base64 decoding of given username:password failed");
|
||||
}
|
||||
|
||||
list($userName, $password) = explode(':', $decoded) + [ null, null ];
|
||||
|
||||
if ( empty($userName) ) {
|
||||
throw new \RuntimeException("A username must be provided");
|
||||
}
|
||||
elseif ( empty($password) ) {
|
||||
throw new \RuntimeException("A password must be provided");
|
||||
}
|
||||
|
||||
( new Authorize($this->user) )->authenticate([ 'email' => $userName, 'username' => $userName ], $password);
|
||||
|
||||
return $this->user->loggedIn();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\User\Authorize\Header;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Ulmus\User\Entity\UserInterface;
|
||||
|
||||
class BearerMethod
|
||||
{
|
||||
public function __construct(
|
||||
protected UserInterface $user,
|
||||
protected string $token
|
||||
) {}
|
||||
|
||||
public function execute(ServerRequestInterface $request) : bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\User\Authorize\Header;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Ulmus\User\Entity\DigestAuthUserInterface;
|
||||
|
||||
class DigestMethod
|
||||
{
|
||||
public function __construct(
|
||||
protected DigestAuthUserInterface $user,
|
||||
protected string|array $arguments
|
||||
) {}
|
||||
|
||||
public function execute(ServerRequestInterface $request) : bool
|
||||
{
|
||||
$arguments = $this->parseDigestArguments($this->arguments);
|
||||
|
||||
if (empty($arguments['username'])) {
|
||||
throw new \InvalidArgumentException("A 'username' key is required to authenticate using Digest");
|
||||
}
|
||||
|
||||
$isSess = stripos($arguments['algorithm'] ?? "", "-SESS") !== false;
|
||||
|
||||
$hashMethod = $this->getDigestAlgorithmHash($arguments);
|
||||
|
||||
$arguments['nc'] = str_pad($arguments['nc'] ?? "1", 8, '0', STR_PAD_LEFT);
|
||||
|
||||
$ha1 = $this->getSecretHash();
|
||||
|
||||
if ($isSess) {
|
||||
$ha1 = hash($hashMethod, implode(':', [
|
||||
$ha1, $arguments['nonce'] , $arguments['cnonce']
|
||||
]));
|
||||
}
|
||||
|
||||
switch($arguments['qop'] ?? 'auth') {
|
||||
case 'auth-int':
|
||||
$ha2 = hash($hashMethod, implode(':', [
|
||||
strtoupper($request->getMethod()), $arguments['uri'], hash($hashMethod, $body ?? "")
|
||||
]));
|
||||
break;
|
||||
|
||||
case 'auth':
|
||||
default:
|
||||
$ha2 = hash($hashMethod, implode(':', [
|
||||
strtoupper($request->getMethod()), $arguments['uri']
|
||||
]));
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($arguments['qop'])) {
|
||||
$response = hash($hashMethod, implode(':', [
|
||||
$ha1, $arguments['nonce'], $arguments['nc'], $arguments['cnonce'], $arguments['qop'], $ha2
|
||||
]));
|
||||
}
|
||||
else {
|
||||
$response = hash($hashMethod, implode(':', [
|
||||
$ha1, $arguments['nonce'], $ha2
|
||||
]));
|
||||
}
|
||||
|
||||
return $response === $arguments['response'];
|
||||
}
|
||||
|
||||
|
||||
protected function parseDigestArguments(string|array $arguments) : array
|
||||
{
|
||||
if (is_string($arguments)) {
|
||||
$keys = [ 'nonce', 'nc', 'cnonce', 'qop', 'username', 'uri', 'realm', 'response', 'opaque', 'algorithm' ];
|
||||
|
||||
# From https://www.php.net/manual/en/features.http-auth.php
|
||||
preg_match_all('@(' . implode('|', $keys) . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $arguments, $matches, PREG_SET_ORDER);
|
||||
|
||||
$arguments = [];
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$arguments[$match[1]] = $match[3] ?: $match[4];
|
||||
}
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
protected function getDigestAlgorithmHash(array $arguments) : string
|
||||
{
|
||||
switch(strtoupper($arguments['algorithm'] ?? "")) {
|
||||
case "SHA-512-256":
|
||||
case "SHA-512-256-SESS":
|
||||
return "sha512/256";
|
||||
|
||||
case "SHA-256":
|
||||
case "SHA-256-SESS":
|
||||
return "sha256";
|
||||
|
||||
default:
|
||||
case 'MD5-SESS':
|
||||
case 'MD5':
|
||||
return "md5";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -4,20 +4,28 @@ namespace Ulmus\User\Authorize;
|
|||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Ulmus\User\Common\AuthorizeContentTypeEnum;
|
||||
use Ulmus\User\Entity\UserInterface;
|
||||
use Ulmus\User\Lib\Authorize;
|
||||
use Ulmus\User\Entity\{ DigestAuthUserInterface, UserInterface };
|
||||
|
||||
class HeaderAuthentication implements AuthorizeMethodInterface
|
||||
{
|
||||
public function connect(ServerRequestInterface $request, UserInterface $user): bool
|
||||
{
|
||||
if ( null !== ( $auth = $request->getHeaderLine('Authorization') ) ) {
|
||||
if (null !== ( $auth = $request->getHeaderLine('Authorization') )) {
|
||||
list($method, $value) = explode(' ', $auth, 2) + [ null, null ];
|
||||
|
||||
list($method, $userPass) = explode(' ', $auth, 2) + [ null, null ];
|
||||
|
||||
switch(strtolower(strtolower($method))) {
|
||||
switch(strtolower($method)) {
|
||||
case "basic":
|
||||
return $this->basicMethod($user, $userPass);
|
||||
return (new Header\BasicMethod($user, $value))->execute($request);
|
||||
|
||||
case "digest":
|
||||
if (! $user instanceof DigestAuthUserInterface) {
|
||||
throw new \RuntimeException("Your user entity must provide a valid hash of `user:realm:password` ");
|
||||
}
|
||||
|
||||
return (new Header\DigestMethod($user, $value))->execute($request);
|
||||
|
||||
case "bearer":
|
||||
return (new Header\BearerMethod($user, $value))->execute($request);
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException("An authentication method must be provided");
|
||||
|
@ -27,25 +35,6 @@ class HeaderAuthentication implements AuthorizeMethodInterface
|
|||
return false;
|
||||
}
|
||||
|
||||
protected function basicMethod(UserInterface $user, string $userPassword) : bool
|
||||
{
|
||||
if ( false === $decoded = base64_decode($userPassword) ) {
|
||||
throw new \RuntimeException("Base64 decoding of given username:password failed");
|
||||
}
|
||||
|
||||
list($userName, $password) = explode(':', $decoded) + [ null, null ];
|
||||
|
||||
if ( empty($userName) ) {
|
||||
throw new \RuntimeException("A username must be provided");
|
||||
}
|
||||
elseif ( empty($password) ) {
|
||||
throw new \RuntimeException("A password must be provided");
|
||||
}
|
||||
|
||||
( new Authorize($user) )->authenticate([ 'email' => $userName, 'username' => $userName ], $password);
|
||||
|
||||
return $user->isLoaded();
|
||||
}
|
||||
|
||||
public function catchRequest(ServerRequestInterface $request) : bool
|
||||
{
|
||||
|
@ -59,5 +48,4 @@ class HeaderAuthentication implements AuthorizeMethodInterface
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\User\Entity;
|
||||
|
||||
interface DigestAuthUserInterface
|
||||
{
|
||||
# Represent a hash (md5, sha256, ...) of username:realm:password
|
||||
public function getSecretHash() : string;
|
||||
}
|
|
@ -25,11 +25,16 @@ class HeaderAuthenticationMiddleware implements MiddlewareInterface
|
|||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
if ( $this->authenticator->catchRequest($request) ) {
|
||||
if ( ! $this->authenticator->connect($request, $this->entity) ) {
|
||||
return call_user_func($this->loginFailedResponse, [ 'api.process' => "Auth failed" ]);
|
||||
try {
|
||||
if ( $this->authenticator->catchRequest($request) ) {
|
||||
if ( ! $this->authenticator->connect($request, $this->entity) ) {
|
||||
return call_user_func($this->loginFailedResponse, [ 'api.process' => "Auth failed" ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
return call_user_func($this->loginFailedResponse, [ 'api.error_message' => $e->getMessage() ]);
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue