I am using API-Platform with JWTs and have created a decorator to allow Swagger to display a /authentication_token endpoint as described by https://api-platform.com/docs/core/jwt/ and to a lesser degree https://api-platform.com/docs/core/openapi/. In addition to providing an email and password, I also need to provide a third parameter which is a UUID which identifies the account the user belongs to. I was able to do so by including the UUID in the body, but I would rather have it included as a header such as X-Account-UUID.
How can this be modified so that Swagger-UI includes this uuid property as a header and not in the body's JSON?
<?php
// api/src/OpenApi/JwtDecorator.php
declare(strict_types=1);
namespace App\OpenApi;
use ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\Core\OpenApi\OpenApi;
use ApiPlatform\Core\OpenApi\Model;
final class JwtDecorator implements OpenApiFactoryInterface
{
private $decorated;
public function __construct(OpenApiFactoryInterface $decorated)
{
$this->decorated = $decorated;
}
public function __invoke(array $context = []): OpenApi
{
$openApi = ($this->decorated)($context);
$schemas = $openApi->getComponents()->getSchemas();
/*
echo(json_encode(['class'=>get_class($this), 'methods'=>get_class_methods($this)]));
echo(json_encode(['class'=>get_class($this->decorated), 'methods'=>get_class_methods($this->decorated)]));
echo(json_encode(['class'=>get_class($openApi), 'methods'=>get_class_methods($openApi)]));
echo(json_encode(['class'=>get_class($openApi->getComponents()), 'methods'=>get_class_methods($openApi->getComponents())]));
echo(json_encode(['class'=>get_class($schemas), 'properties'=>$schemas]));
$securitySchemas = $openApi->getComponents()->getSecuritySchemes();
echo(json_encode(['class'=>get_class($securitySchemas), 'properties'=>$securitySchemas]));
$headers = $openApi->getComponents()->getHeaders();
exit(json_encode(['class'=>get_class($headers), 'properties'=>$headers]));
*/
$schemas['Token'] = new \ArrayObject([
'type' => 'object',
'properties' => [
'token' => [
'type' => 'string',
'readOnly' => true,
],
],
]);
$schemas['Credentials'] = new \ArrayObject([
'type' => 'object',
'properties' => [
'email' => [
'type' => 'string',
'example' => 'john.doe#bla.com',
],
'password' => [
'type' => 'string',
'example' => 'secret',
],
'uuid' => [
'type' => 'string',
'in' => 'header',
'example' => '1234abcd-abcd-1234-abcd-123412341234',
],
],
'headers' => [
'uuid' => [
'type' => 'string',
'in' => 'header',
'example' => '1234abcd-abcd-1234-abcd-123412341234',
],
],
]);
$responses = [
'200' => [
'description' => 'Get JWT token',
'content' => [
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/Token',
],
],
]
]
];
$content = new \ArrayObject([
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/Credentials',
],
],
]);
$requestBody = new Model\RequestBody('Generate new JWT Token', $content);
$post = new Model\Operation('postCredentialsItem', [], $responses, 'Get JWT token to login.', '', new Model\ExternalDocumentation, [], $requestBody);
$pathItem = new Model\PathItem('JWT Token', null, null, null, null, $post);
$openApi->getPaths()->addPath('/authentication_token', $pathItem);
return $openApi;
}
}
Related
I have created a custom endpoint using API Platform:
#[ApiResource(
collectionOperations: [
'get' => [
'normalization_context' => ['groups' => ['product:list']]
],
'post' => [
'denormalization_context' => ['groups' => ['product:list']]
]
],
itemOperations: [
'get_by_user' => [
'method' => 'GET',
'path' => '/products/user/{id}',
'read' => 'false',
'controller' => ProductController::class,
'normalization_context' => ['groups' => ['product:list']],
'openapi_context' => [
'summary' => 'Retrieves the Product collection for a specific user ID',
'description' => 'Retrieves the Product collection for a specific user ID'
]
],
], )]
and the controller method:
public function __invoke(int $id): array
{
$prod = $this->productRepository
->findBy(
['user' => $id],
);
return $prod;
}
And the error when I try to call this endpoint is:
Not Found - api/vendor/api-platform/core/src/EventListener/ReadListener.php
Has anyone any ideas what might be the issue?
I want to add Response code(400,404, 500) for each route in swagger UI using , so that user know in advance what will be the response for these error code.
I am following following link but not sure how can I modify the Response object and can add the response for 400, 404, 401 so on.
https://api-platform.com/docs/core/openapi/#overriding-the-openapi-specification
I am using symfony 5.2 and apiplatfrom 2.6 while writing this question.
When you are using decorators, you are essentially extending existing service code.
First you are getting path and then set your custom operation in swagger context with the given code.
If you notice the code block:
$openApi->getPaths()->addPath('/api/grumpy_pizzas/{id}', $pathItem->withGet(
$operation->withParameters(array_merge(
$operation->getParameters(),
[new Model\Parameter('fields', 'query', 'Fields to remove of the output')]
))
));
The path item take Operation object as an argument which has withResponses method that can be used to modify any responses that your path generates.
Take this as per-cursor and you can check whole source and see what fits your requirements best. Check source : PathItem.php
You can build response according to Response object and add it to Operation and to path.
Tip: Any editor with good linter would make your coding easier as it will be able to show you available methods.
Its late to post but I got the solutions for adding response code in my Swagger Ui, it can help some looking for solution.
private function setErrorResponseDescriptions(OpenApi $openApi): OpenApi
{
$schemas = $openApi->getComponents()->getSchemas();
$schemas['Error'] = [
'type' => 'object',
'properties' => [
'type' => [
'type' => 'string',
'readOnly' => true,
],
'title' => [
'type' => 'string',
'readOnly' => true,
],
'detail' => [
'type' => 'string',
'readOnly' => true,
],
],
];
$schemas['ValidationError'] = [
'type' => 'object',
'properties' => [
'type' => [
'type' => 'string',
'readOnly' => true,
],
'title' => [
'type' => 'string',
'readOnly' => true,
],
'detail' => [
'type' => 'string',
'readOnly' => true,
],
'validations' => [
'type' => 'array',
'readOnly' => true,
'items' => [
'type' => 'object',
'properties' => [
'propertyPath' => [
'type' => 'string',
'readOnly' => true,
],
'message' => [
'type' => 'string',
'readOnly' => true,
],
]
]
]
],
];
foreach ($openApi->getPaths()->getPaths() as $path => $pathItem) {
foreach (['PUT', 'POST', 'PATCH', 'GET'] as $method) {
$item = $pathItem->{'get' . ucfirst($method)}();
if ($item) {
$item->addResponse(
new Model\Response(
description: 'Unauthorized',
content: new \ArrayObject([
'application/problem+json' => [
'schema' => [
'$ref' => '#/components/schemas/Error',
],
],
])
),
'401'
);
if ('GET' !== $method) {
$item->addResponse(
new Model\Response(
description: 'Bad request',
content: new \ArrayObject([
'application/problem+json' => [
'schema' => [
'$ref' => '#/components/schemas/ValidationError',
],
],
])
),
'400'
);
} else {
$item->addResponse(
new Model\Response(
description: 'Not Found',
content: new \ArrayObject([
'application/problem+json' => [
'schema' => [
'$ref' => '#/components/schemas/Error',
],
],
])
),
'404'
);
}
}
}
}
return $openApi;
}
In the docs there is this example, but it shows only how to add one GET operation.
I would like to know how can I add a custom POST route to the documentation.
I am having trouble to show the example body request, with the expected values to be sent (username and email, in this example)
My attempt
<?php
// api/src/Swagger/SwaggerDecorator.php
namespace App\Swagger;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class SwaggerDecorator implements NormalizerInterface
{
private $decorated;
public function __construct(NormalizerInterface $decorated)
{
$this->decorated = $decorated;
}
public function normalize($object, $format = null, array $context = [])
{
$docs = $this->decorated->normalize($object, $format, $context);
$customDefinition = [
'tags' => [
'default'
],
'name' => 'fields',
'description' => 'Testing decorator',
'default' => 'id',
'in' => 'query',
'requestBody' =>
[
'content' => [
'application/json' => [
'schema' => [
'description' => 'abcd',
'required' => [
'username', 'email'
],
'properties' => [
'username', 'email'
],
]
]
],
'description' => 'testing'
],
];
$docs['paths']['/testing']['post']['parameters'][] = $customDefinition;
return $docs;
}
public function supportsNormalization($data, $format = null)
{
return $this->decorated->supportsNormalization($data, $format);
}
}
But it doesn't work.
you should not put the whole route declaration inside the parameters array, you should create smth like this:
$docs['paths']['/testing']['post'] = $customDefinition;
Symfony \ Component \ Debug \ Exception \ FatalThrowableError (E_RECOVERABLE_ERROR)
Argument 2 passed to Illuminate\Auth\SessionGuard::__construct() must implement interface Illuminate\Contracts\Auth\UserProvider, null given, called in C:\laragon\www\hotel\vendor\laravel\framework\src\Illuminate\Auth\AuthManager.php on line 123
<?php
namespace App\Http\Controllers\Auth;
use App\User;use App\Admin;use App\GestionnaireReceptionniste;use App\GestionnaireResto;use App\Http\Controllers\Controller;use Illuminate\Support\Facades\Hash;use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;use Illuminate\Http\Request;
class RegisterController extends Controller
{
use RegistersUsers;
protected $redirectTo = '/home';
public function __construct()
{
$this->middleware('guest');
$this->middleware('guest:admin');
$this->middleware('guest:gestionnairereceptionniste');
$this->middleware('guest:gestionnaireresto');
}
public function showAdminRegisterForm()
{
return view('auth.register', ['url' => 'admin']);
}
public function showGestionnaireReceptionnisteRegisterForm()
{
return view('auth.register', ['url' => 'gestionnairereceptionniste']);
}
public function showGestionnaireRestoRegisterForm()
{
return view('auth.register', ['url' => 'gestionnairesto']);
}
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:6', 'confirmed'],
]);
}
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
protected function createAdmin(Request $request)
{
$this->validator($request->all())->validate();
$admin = Admin::create([
'name' => $request['name'],
'email' => $request['email'],
'password' => Hash::make($request['password']),
]);
return redirect()->intended('login/admin');
}
protected function createGestionnaireReceptionniste(Request $request)
{
$this->validator($request->all())->validate();
$gestionnairereceptionniste = GestionnaireReceptionniste::create([
'name' => $request['name'],
'email' => $request['email'],
'password' => Hash::make($request['password']),
]);
return redirect()->intended('login/gestionnairereceptionniste');
}
protected function createGestionnaireResto(Request $request)
{
$this->validator($request->all())->validate();
$gestionnaireresto =CreateGestionnaireResto::create([
'name' => $request['name'],
'email' => $request['email'],
'password' => Hash::make($request['password']),
]);
return redirect()->intended('login/admin');
}
}
models:
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Admin extends Authenticatable
{
use Notifiable;
protected $guard = 'admin';
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
}
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class GestionnaireReceptionniste extends Authenticatable
{
use Notifiable;
protected $guard = 'gestionnairereceptionniste';
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
}
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class GestionnaireResto extends Authenticatable
{
use Notifiable;
protected $guard = 'gestionnaireresto';
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
}
guard:
<?php
return [
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
'gestionnairereceptionniste' => [
'driver' => 'session',
'provider' => 'gestionnaireceptionnistes',
],
'gestionnaireresto' => [
'driver' => 'session',
'provider' => 'gestionnairerestos',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
],
'gestionnairereceptionnistes' => [
'driver' => 'eloquent',
'model' => App\GestionnaireReceptionniste::class,
],
'gestionnairerestos' => [
'driver' => 'eloquent',
'model' => App\GestionnaireResto::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
],
],
];
ReditectIfAuthenticated:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
public function handle($request, Closure $next, $guard = null)
{
if ($guard == "admin" && Auth::guard($guard)->check()) {
return redirect('/admin');
}
if ($guard == "gestionnairereceptionniste" && Auth::guard($guard)->check()) {
return redirect('/gestionnairereceptionnister');
}
if ($guard == "gestionnairesto" && Auth::guard($guard)->check()) {
return redirect('/gestionnairesto');
}
if (Auth::guard($guard)->check()) {
return redirect('/home');
}
return $next($request);
}
}
There is a typo in your config/auth.php file.
This:
'gestionnairereceptionniste' => [
'driver' => 'session',
'provider' => 'gestionnaireceptionnistes', // <-- typo
],
should be:
'gestionnairereceptionniste' => [
'driver' => 'session',
'provider' => 'gestionnairereceptionnistes',
],
In my cakephp (3.3) project, How to save multiple tables in a single save. My association is as follows, also I am giving my code here.
Table1 : users
In class UsersTable extends Table
$this->hasOne('ProfileDetails', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
Table2 : profile_details
In class ProfileDetailsTable extends Table
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
What should be the data format to save multiple tables?
In my template I have added the following , for now I have used only few fields
$this->Form->input('User.username',['type' => 'text','class'=>'form-control']) ?>
$this->Form->input('User.password',['type' => 'password','class'=>'form-control']) ?>
$this->Form->input('User.email',['type' => 'text','class'=>'form-control']) ?>
$this->Form->input('ProfileDetail.first_name',['type' => 'text','class'=>'form-control']) ?>
In the controller before saving i have debuged which gives result as follows
debug($this->request->data);
$user = $this->Users->patchEntity($user, $this->request->data);
debug($user);
exit;
$this->Users->save($user)
Result of debug
\src\Controller\UsersController.php (line 39)
[
'User' => [
'username' => 'Tester',
'password' => '43434324',
'email' => 'test#gmail.com',
'role' => 'admin'
],
'ProfileDetail' => [
'first_name' => 'Tester'
]
]
\src\Controller\UsersController.php (line 41)
object(App\Model\Entity\User) {
'User' => [
'username' => 'Tester',
'password' => '43434324',
'email' => 'test#gmail.com',
'role' => 'admin'
],
'ProfileDetail' => [
'first_name' => 'Tester'
],
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'User' => true,
'ProfileDetail' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [
'email' => [
'_required' => 'This field is required'
]
],
'[invalid]' => [],
'[repository]' => 'Users'
}
Could you please suggest me how to save this data in multiple tables with single save with validation?
Looking at your debug, there is an error saying the email field is required. Maybe that is your problem..
If still not working , try this:
replace User by users
and ProjectDetail by project_details
In your form. And also remove joinType from following:
$this->hasOne('ProfileDetails', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);