Browse Source

add HttpBasicAuthHandler and typed auth middlewares

michelphp 1 tháng trước cách đây
mục cha
commit
75514e030d

+ 2 - 2
src/Handler/FormAuthHandler.php

@@ -72,7 +72,6 @@ class FormAuthHandler implements AuthHandlerInterface, StatefulAuthHandlerInterf
     public function authenticate(ServerRequestInterface $request): ?AuthIdentity
     {
         $path = $request->getUri()->getPath();
-        $method = $request->getMethod();
 
         if ($path === $this->logoutPath) {
             $this->sessionStorage->remove('user_identifier');
@@ -83,10 +82,11 @@ class FormAuthHandler implements AuthHandlerInterface, StatefulAuthHandlerInterf
             $identifier = $this->sessionStorage->get('user_identifier');
             $user = $this->userProvider->findByIdentifier($identifier);
             if ($user instanceof UserInterface) {
-                return new AuthIdentity($user,  $path === $this->loginPath);
+                return new AuthIdentity($user,  false);
             }
         }
 
+        $method = $request->getMethod();
         if ($path === $this->loginPath && $method === 'GET') {
             return null;
         }

+ 82 - 0
src/Handler/HttpBasicAuthHandler.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace Michel\Auth\Handler;
+
+use Michel\Auth\AuthIdentity;
+use Michel\Auth\Exception\AuthenticationException;
+use Michel\Auth\Exception\InvalidCredentialsException;
+use Michel\Auth\Exception\UserNotFoundException;
+use Michel\Auth\PasswordAuthenticatedUserInterface;
+use Michel\Auth\UserInterface;
+use Michel\Auth\UserProviderInterface;
+use Psr\Http\Message\ResponseFactoryInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+
+class HttpBasicAuthHandler implements AuthHandlerInterface
+{
+    private UserProviderInterface $userProvider;
+
+    private string $realm;
+    /**
+     * @var callable|null
+     */
+    private $onFailure;
+    public function __construct(
+        UserProviderInterface $userProvider,
+         string $realm = "Restricted Area",
+        callable              $onFailure = null
+    )
+    {
+        $this->userProvider = $userProvider;
+        $this->onFailure = $onFailure;
+        $this->realm = $realm;
+    }
+
+
+    /**
+     * @throws AuthenticationException
+     * @throws UserNotFoundException
+     * @throws InvalidCredentialsException
+     */
+    public function authenticate(ServerRequestInterface $request): ?AuthIdentity
+    {
+        $authHeader = $request->getHeaderLine('Authorization');
+        if (0 !== strpos(strtolower($authHeader), 'basic ')) {
+            return null;
+        }
+
+        $base64Credentials = substr($authHeader, 6);
+        $credentials = base64_decode($base64Credentials);
+        if (false === $credentials || false === strpos($credentials, ':')) {
+            throw new InvalidCredentialsException("Invalid Basic Auth format.");
+        }
+
+        [$login, $password] = explode(':', $credentials, 2);
+        $login = trim($login);
+        /**
+         * @var PasswordAuthenticatedUserInterface|UserInterface $user
+         */
+        $user = $this->userProvider->findByIdentifier($login);
+        if (!$user instanceof UserInterface) {
+            throw new UserNotFoundException("User not found.");
+        }
+
+        if (!$user instanceof PasswordAuthenticatedUserInterface || !$this->userProvider->isPasswordValid($user, $password)) {
+            throw new InvalidCredentialsException("Invalid username or password.");
+        }
+
+        return new AuthIdentity($user, false);
+    }
+
+    public function onFailure(ServerRequestInterface $request, ResponseFactoryInterface $responseFactory, ?AuthenticationException $exception = null): ResponseInterface
+    {
+        if (is_callable($this->onFailure)) {
+            return ($this->onFailure)($request, $responseFactory, $exception);
+        }
+
+        return $responseFactory->createResponse(401)
+            ->withHeader('WWW-Authenticate', sprintf('Basic realm="%s"', $this->realm))
+            ->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
+    }
+}

+ 1 - 0
src/Handler/TokenAuthHandler.php

@@ -45,6 +45,7 @@ class TokenAuthHandler implements AuthHandlerInterface
         if (empty($token)) {
             throw new AuthenticationException("Token is required.");
         }
+
         $user = $this->userProvider->findByToken($token);
         if (!$user instanceof UserInterface) {
             throw new InvalidCredentialsException("The provided API key is invalid.");

+ 51 - 11
src/MichelPackage/MichelAuthPackage.php

@@ -4,7 +4,11 @@ namespace Michel\Auth\MichelPackage;
 
 use Michel\Auth\Command\AuthPasswordHashCommand;
 use Michel\Auth\Handler\FormAuthHandler;
-use Michel\Auth\Middlewares\AuthMiddleware;
+use Michel\Auth\Handler\HttpBasicAuthHandler;
+use Michel\Auth\Handler\TokenAuthHandler;
+use Michel\Auth\Middlewares\FormAuthMiddleware;
+use Michel\Auth\Middlewares\HttpBasicAuthMiddleware;
+use Michel\Auth\Middlewares\TokenAuthMiddleware;
 use Michel\Auth\UserProviderInterface;
 use Michel\Package\PackageInterface;
 use Michel\Session\Storage\SessionStorageInterface;
@@ -18,35 +22,71 @@ class MichelAuthPackage implements PackageInterface
     public function getDefinitions(): array
     {
         return [
-            AuthMiddleware::class => static function (ContainerInterface $container) {
-                return new AuthMiddleware(
+            FormAuthMiddleware::class => static function (ContainerInterface $container) {
+                return new FormAuthMiddleware(
                     $container->get(FormAuthHandler::class),
                     $container->get(ResponseFactoryInterface::class),
                     $container->get(LoggerInterface::class)
                 );
             },
+            TokenAuthMiddleware::class => static function (ContainerInterface $container) {
+                return new TokenAuthMiddleware(
+                    $container->get(TokenAuthHandler::class),
+                    $container->get(ResponseFactoryInterface::class),
+                    $container->get(LoggerInterface::class)
+                );
+            },
+            HttpBasicAuthMiddleware::class => static function (ContainerInterface $container) {
+                return new HttpBasicAuthMiddleware(
+                    $container->get(HttpBasicAuthHandler::class),
+                    $container->get(ResponseFactoryInterface::class),
+                    $container->get(LoggerInterface::class)
+                );
+            },
             FormAuthHandler::class => static function (ContainerInterface $container) {
                 return new FormAuthHandler(
                     $container->get(UserProviderInterface::class),
                     $container->get(SessionStorageInterface::class),
                     [
-                        'login_path' => $container->get('auth.form_login_path'),
-                        'login_key' => $container->get('auth.form_login_key'),
-                        'password_key' => $container->get('auth.form_password_key'),
-                        'on_failure' => $container->get('auth.form_on_failure')
+                        'login_path' => $container->get('auth.form.login_path'),
+                        'logout_path' => $container->get('auth.form.logout_path'),
+                        'login_key' => $container->get('auth.form.login_key'),
+                        'password_key' => $container->get('auth.form.password_key'),
+                        'on_failure' => $container->get('auth.form.on_failure')
                     ]
                 );
             },
+            TokenAuthHandler::class => static function (ContainerInterface $container) {
+                return new TokenAuthHandler(
+                    $container->get(UserProviderInterface::class),
+                    $container->get('auth.token.header_name'),
+                    $container->get('auth.token.on_failure')
+                );
+            },
+            HttpBasicAuthHandler::class => static function (ContainerInterface $container) {
+                return new HttpBasicAuthHandler(
+                    $container->get(UserProviderInterface::class),
+                    $container->get('auth.http.basic.realm'),
+                    $container->get('auth.http.basic.on_failure')
+                );
+            }
         ];
     }
 
     public function getParameters(): array
     {
         return [
-            'auth.form_login_path' => '/login',
-            'auth.form_login_key' => '_username',
-            'auth.form_password_key' => '_password',
-            'auth.form_on_failure' => null
+            'auth.form.login_path' => '/login',
+            'auth.form.logout_path' => '/logout',
+            'auth.form.login_key' => '_username',
+            'auth.form.password_key' => '_password',
+            'auth.form.on_failure' => null,
+
+            'auth.token.header_name' => 'X-Api-Key',
+            'auth.token.on_failure' => null,
+
+            'auth.http.basic.realm' => 'Restricted Area',
+            'auth.http_basic.on_failure' => null
         ];
     }
 

+ 1 - 1
src/Middlewares/AuthMiddleware.php

@@ -14,7 +14,7 @@ use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
 use Psr\Log\LoggerInterface;
 
-final class AuthMiddleware implements MiddlewareInterface
+abstract class AuthMiddleware implements MiddlewareInterface
 {
     private AuthHandlerInterface $authHandler;
     private ResponseFactoryInterface $responseFactory;

+ 7 - 0
src/Middlewares/FormAuthMiddleware.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Michel\Auth\Middlewares;
+
+final class FormAuthMiddleware extends AuthMiddleware
+{
+}

+ 7 - 0
src/Middlewares/HttpBasicAuthMiddleware.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Michel\Auth\Middlewares;
+
+final class HttpBasicAuthMiddleware extends AuthMiddleware
+{
+}

+ 7 - 0
src/Middlewares/TokenAuthMiddleware.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Michel\Auth\Middlewares;
+
+final class TokenAuthMiddleware extends AuthMiddleware
+{
+}