diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/README.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/README.mustache index c0df2c5baa2..dd5fb253184 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/README.mustache +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/README.mustache @@ -107,7 +107,8 @@ $ composer phplint ## Show errors -Switch your app environment to development in `public/.htaccess` file: +Switch your app environment to development +- When using with some webserver => in `public/.htaccess` file: ```ini ## .htaccess @@ -115,6 +116,15 @@ Switch your app environment to development in `public/.htaccess` file: ``` +- Or when using whatever else, set `APP_ENV` environment variable like this: +```bash +export APP_ENV=development +``` +or simply +```bash +export APP_ENV=dev +``` + ## Mock Server Since this feature should be used for development only, change environment to `development` and send additional HTTP header `X-{{invokerPackage}}-Mock: ping` with any request to get mocked response. CURL example: @@ -214,6 +224,11 @@ Scope list: {{/scopes}} {{/isOAuth}} +{{#isBasicBearer}} +### Security schema `{{name}}` +> Important! To make Bearer authentication work you need to extend [\{{authPackage}}\{{abstractNamePrefix}}Authenticator{{abstractNameSuffix}}]({{#lambda.forwardslash}}{{authSrcPath}}{{/lambda.forwardslash}}/{{abstractNamePrefix}}Authenticator{{abstractNameSuffix}}.php) class by [\{{authPackage}}\BearerAuthenticator](./src/Auth/BearerAuthenticator.php) class. + +{{/isBasicBearer}} {{/authMethods}} ### Advanced middleware configuration Ref to used Slim Token Middleware [dyorg/slim-token-authentication](https://github.com/dyorg/slim-token-authentication/tree/1.x#readme) diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/abstract_authenticator.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/abstract_authenticator.mustache index 3aef910d9f1..af20a196704 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/abstract_authenticator.mustache +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/abstract_authenticator.mustache @@ -10,7 +10,7 @@ namespace {{authPackage}}; use Psr\Http\Message\ServerRequestInterface; -use Dyorg\TokenAuthentication; +use Psr\Http\Message\ResponseInterface; use Dyorg\TokenAuthentication\TokenSearch; use Dyorg\TokenAuthentication\Exceptions\UnauthorizedExceptionInterface; @@ -39,6 +39,32 @@ abstract class {{abstractNamePrefix}}Authenticator{{abstractNameSuffix}} */ abstract protected function getUserByToken(string $token); + /** + * Handles the response for unauthorized access attempts. + * + * This method is called when an access token is either not provided, invalid, or expired. + * It constructs a response that includes an error message, the status code, and any other relevant information. + * + * @param ServerRequestInterface $request The HTTP request that led to the unauthorized access attempt. + * @param ResponseInterface $response The response object that will be modified to reflect the unauthorized status. + * @param UnauthorizedExceptionInterface $exception The exception triggered due to unauthorized access, containing details such as the error message. + * + * @return ResponseInterface The modified response object with the unauthorized access error information, including a 401 status code and a JSON body with the error message and token information. + */ + public static function handleUnauthorized(ServerRequestInterface $request, ResponseInterface $response, UnauthorizedExceptionInterface $exception) + { + $output = [ + 'message' => $exception->getMessage(), + 'token' => $request->getAttribute('authorization_token'), + 'success' => false + ]; + + $response->getBody()->write(json_encode($output)); + return $response + ->withHeader('Content-Type', 'application/json') + ->withStatus(401); + } + /** * Authenticator constructor * diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/index.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/index.mustache index 18f2574b24b..305ea12cf4c 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/index.mustache +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/index.mustache @@ -25,7 +25,7 @@ $builder = new ContainerBuilder(); // consider prod by default $env; -switch (strtolower($_SERVER['APP_ENV'] ?? 'prod')) { +switch (strtolower($_SERVER['APP_ENV'] ?? getenv('APP_ENV') ?? 'prod')) { case 'development': case 'dev': $env = 'dev'; diff --git a/modules/openapi-generator/src/main/resources/php-slim4-server/register_routes.mustache b/modules/openapi-generator/src/main/resources/php-slim4-server/register_routes.mustache index 8b1cbf98ada..abaf5d09f4b 100644 --- a/modules/openapi-generator/src/main/resources/php-slim4-server/register_routes.mustache +++ b/modules/openapi-generator/src/main/resources/php-slim4-server/register_routes.mustache @@ -14,6 +14,23 @@ namespace {{appPackage}}; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Slim\Exception\HttpNotImplementedException; +{{#hasAuthMethods}} +use Dyorg\TokenAuthentication; + {{#authMethods}} + {{#isBasicBasic}} +use {{authPackage}}\BasicAuthenticator; + {{/isBasicBasic}} + {{#isApiKey}} +use {{authPackage}}\ApiKeyAuthenticator; + {{/isApiKey}} + {{#isOAuth}} +use {{authPackage}}\OAuthAuthenticator; + {{/isOAuth}} + {{#isBasicBearer}} +use {{authPackage}}\BearerAuthenticator; + {{/isBasicBearer}} + {{/authMethods}} +{{/hasAuthMethods}} /** * RegisterRoutes Class Doc Comment @@ -60,7 +77,9 @@ class RegisterRoutes {{#isBasicBearer}} [ 'type' => '{{type}}', - 'isBasic' => true, + 'scheme' => '{{scheme}}', + 'bearerFormat' => '{{bearerFormat}}', + 'isBasic' => false, 'isBearer' => true, 'isApiKey' => false, 'isOAuth' => false, @@ -152,6 +171,93 @@ class RegisterRoutes $callback )->setName($operation['operationId']); + {{#hasAuthMethods}} + // Add authentication middleware based on the operation's authMethods + if ($operation['authMethods']) { + foreach ($operation['authMethods'] as $authMethod) { + {{#authMethods}} + {{#isBasicBasic}} + if ($authMethod['isBasic']) { + $route->add(new TokenAuthentication([ + 'path' => '/', + 'authenticator' => new BasicAuthenticator, + 'regex' => '/Basic\s+(.*)$/i', + 'header' => 'Authorization', + 'parameter' => null, + 'cookie' => null, + 'argument' => null, + 'attribute' => 'authorization_token', + 'error' => ['{{authPackage}}\BasicAuthenticator', 'handleUnauthorized'], + ])); + } + {{/isBasicBasic}} + {{#isApiKey}} + if ($authMethod['isApiKey']) { + $authenticatorConfig = [ + 'path' => '/', + 'authenticator' => new ApiKeyAuthenticator, + 'regex' => '/\s+(.*)$/i', + 'argument' => null, + 'attribute' => 'authorization_token', + 'error' => ['{{authPackage}}\ApiKeyAuthenticator', 'handleUnauthorized'], + ]; + if ($authMethod['isKeyInHeader']) { + $authenticatorConfig = [ + 'header' => $authMethod['keyParamName'], + 'parameter' => null, + 'cookie' => null, + ] + } else if ($authMethod['isKeyInQuery']) { + $authenticatorConfig = [ + 'header' => null, + 'parameter' => $authMethod['keyParamName'], + 'cookie' => null, + ] + } else if ($authMethod['isKeyInCookie']) { + $authenticatorConfig = [ + 'header' => null, + 'parameter' => null, + 'cookie' => $authMethod['keyParamName'], + ] + } + $route->add(new TokenAuthentication($authenticatorConfig)); + } + {{/isApiKey}} + {{#isBasicBearer}} + if ($authMethod['isBearer']) { + $route->add(new TokenAuthentication([ + 'path' => '/', + 'authenticator' => new BearerAuthenticator, + 'regex' => '/Bearer\s+(.*)$/i', + 'header' => 'Authorization', + 'parameter' => null, + 'cookie' => null, + 'argument' => null, + 'attribute' => 'authorization_token', + 'error' => ['{{authPackage}}\BearerAuthenticator', 'handleUnauthorized'], + ])); + } + {{/isBasicBearer}} + {{#isOAuth}} + if ($authMethod['isOAuth']) { + $route->add(new TokenAuthentication([ + 'path' => '/', + 'authenticator' => new OAuthAuthenticator($authMethod['scopes']), + 'regex' => '/Bearer\s+(.*)$/i', + 'header' => 'Authorization', + 'parameter' => null, + 'cookie' => null, + 'argument' => null, + 'attribute' => 'authorization_token', + 'error' => ['{{authPackage}}\OAuthAuthenticator', 'handleUnauthorized'], + ])); + } + {{/isOAuth}} + {{/authMethods}} + } + } + {{/hasAuthMethods}} + foreach ($middlewares as $middleware) { $route->add($middleware); } diff --git a/samples/server/petstore/php-slim4/README.md b/samples/server/petstore/php-slim4/README.md index e5dc584f80c..f1d568cc757 100644 --- a/samples/server/petstore/php-slim4/README.md +++ b/samples/server/petstore/php-slim4/README.md @@ -90,7 +90,8 @@ $ composer phplint ## Show errors -Switch your app environment to development in `public/.htaccess` file: +Switch your app environment to development +- When using with some webserver => in `public/.htaccess` file: ```ini ## .htaccess @@ -98,6 +99,15 @@ Switch your app environment to development in `public/.htaccess` file: ``` +- Or when using whatever else, set `APP_ENV` environment variable like this: +```bash +export APP_ENV=development +``` +or simply +```bash +export APP_ENV=dev +``` + ## Mock Server Since this feature should be used for development only, change environment to `development` and send additional HTTP header `X-OpenAPIServer-Mock: ping` with any request to get mocked response. CURL example: diff --git a/samples/server/petstore/php-slim4/lib/App/RegisterRoutes.php b/samples/server/petstore/php-slim4/lib/App/RegisterRoutes.php index 0c719abdfb9..fae7317d455 100644 --- a/samples/server/petstore/php-slim4/lib/App/RegisterRoutes.php +++ b/samples/server/petstore/php-slim4/lib/App/RegisterRoutes.php @@ -27,6 +27,9 @@ namespace OpenAPIServer\App; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Slim\Exception\HttpNotImplementedException; +use Dyorg\TokenAuthentication; +use OpenAPIServer\Auth\OAuthAuthenticator; +use OpenAPIServer\Auth\ApiKeyAuthenticator; /** * RegisterRoutes Class Doc Comment @@ -881,6 +884,55 @@ class RegisterRoutes $callback )->setName($operation['operationId']); + // Add authentication middleware based on the operation's authMethods + if ($operation['authMethods']) { + foreach ($operation['authMethods'] as $authMethod) { + if ($authMethod['isOAuth']) { + $route->add(new TokenAuthentication([ + 'path' => '/', + 'authenticator' => new OAuthAuthenticator($authMethod['scopes']), + 'regex' => '/Bearer\s+(.*)$/i', + 'header' => 'Authorization', + 'parameter' => null, + 'cookie' => null, + 'argument' => null, + 'attribute' => 'authorization_token', + 'error' => ['OpenAPIServer\Auth\OAuthAuthenticator', 'handleUnauthorized'], + ])); + } + if ($authMethod['isApiKey']) { + $authenticatorConfig = [ + 'path' => '/', + 'authenticator' => new ApiKeyAuthenticator, + 'regex' => '/\s+(.*)$/i', + 'argument' => null, + 'attribute' => 'authorization_token', + 'error' => ['OpenAPIServer\Auth\ApiKeyAuthenticator', 'handleUnauthorized'], + ]; + if ($authMethod['isKeyInHeader']) { + $authenticatorConfig = [ + 'header' => $authMethod['keyParamName'], + 'parameter' => null, + 'cookie' => null, + ] + } else if ($authMethod['isKeyInQuery']) { + $authenticatorConfig = [ + 'header' => null, + 'parameter' => $authMethod['keyParamName'], + 'cookie' => null, + ] + } else if ($authMethod['isKeyInCookie']) { + $authenticatorConfig = [ + 'header' => null, + 'parameter' => null, + 'cookie' => $authMethod['keyParamName'], + ] + } + $route->add(new TokenAuthentication($authenticatorConfig)); + } + } + } + foreach ($middlewares as $middleware) { $route->add($middleware); } diff --git a/samples/server/petstore/php-slim4/lib/Auth/AbstractAuthenticator.php b/samples/server/petstore/php-slim4/lib/Auth/AbstractAuthenticator.php index 699fd1fdbc7..b253a0f84bb 100644 --- a/samples/server/petstore/php-slim4/lib/Auth/AbstractAuthenticator.php +++ b/samples/server/petstore/php-slim4/lib/Auth/AbstractAuthenticator.php @@ -23,7 +23,7 @@ namespace OpenAPIServer\Auth; use Psr\Http\Message\ServerRequestInterface; -use Dyorg\TokenAuthentication; +use Psr\Http\Message\ResponseInterface; use Dyorg\TokenAuthentication\TokenSearch; use Dyorg\TokenAuthentication\Exceptions\UnauthorizedExceptionInterface; @@ -52,6 +52,32 @@ abstract class AbstractAuthenticator */ abstract protected function getUserByToken(string $token); + /** + * Handles the response for unauthorized access attempts. + * + * This method is called when an access token is either not provided, invalid, or expired. + * It constructs a response that includes an error message, the status code, and any other relevant information. + * + * @param ServerRequestInterface $request The HTTP request that led to the unauthorized access attempt. + * @param ResponseInterface $response The response object that will be modified to reflect the unauthorized status. + * @param UnauthorizedExceptionInterface $exception The exception triggered due to unauthorized access, containing details such as the error message. + * + * @return ResponseInterface The modified response object with the unauthorized access error information, including a 401 status code and a JSON body with the error message and token information. + */ + public static function handleUnauthorized(ServerRequestInterface $request, ResponseInterface $response, UnauthorizedExceptionInterface $exception) + { + $output = [ + 'message' => $exception->getMessage(), + 'token' => $request->getAttribute('authorization_token'), + 'success' => false + ]; + + $response->getBody()->write(json_encode($output)); + return $response + ->withHeader('Content-Type', 'application/json') + ->withStatus(401); + } + /** * Authenticator constructor * diff --git a/samples/server/petstore/php-slim4/public/index.php b/samples/server/petstore/php-slim4/public/index.php index 795b85efa0f..1868cac9576 100644 --- a/samples/server/petstore/php-slim4/public/index.php +++ b/samples/server/petstore/php-slim4/public/index.php @@ -37,7 +37,7 @@ $builder = new ContainerBuilder(); // consider prod by default $env; -switch (strtolower($_SERVER['APP_ENV'] ?? 'prod')) { +switch (strtolower($_SERVER['APP_ENV'] ?? getenv('APP_ENV') ?? 'prod')) { case 'development': case 'dev': $env = 'dev';