#18031 Fix: added middleware adding for authorization methods [php-slim4] (#18103)

* fix: added middleware adding for authorization methods, added unauthorized handler to authorization abstract, fixes #18031

* feat: updated generated sample for php-slim4

* fix: small fix for running error handlers and environment setting fix

* feat: removed unused dependency in abstract authenticator, php-slim4
This commit is contained in:
Maroš Varchola 2024-03-23 14:41:16 +01:00 committed by GitHub
parent 5e9546451c
commit f258ce2cf5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 242 additions and 7 deletions

View File

@ -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
<IfModule mod_env.c>
@ -115,6 +116,15 @@ Switch your app environment to development in `public/.htaccess` file:
</IfModule>
```
- 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)

View File

@ -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
*

View File

@ -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';

View File

@ -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);
}

View File

@ -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
<IfModule mod_env.c>
@ -98,6 +99,15 @@ Switch your app environment to development in `public/.htaccess` file:
</IfModule>
```
- 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:

View File

@ -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);
}

View File

@ -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
*

View File

@ -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';