set up doc for API
This commit is contained in:
parent
c0a8a9ab82
commit
b2bb9fc78b
|
|
@ -0,0 +1,194 @@
|
||||||
|
# Intro
|
||||||
|
|
||||||
|
The Api made some changes to the current structure of the project. These changes include the following:
|
||||||
|
- **Security** : the security.yaml file has been updated both on the server and the client side
|
||||||
|
- **Controllers**: Controllers need to be updated on the client side
|
||||||
|
- **Services**: Services and token management need updates on the client side
|
||||||
|
- **Health**: I now want to commit a Talon E over a wall IRL
|
||||||
|
- **Entities**: Entities need to be updated on the client side to include a new field (sso_id).
|
||||||
|
- This field will be used a lot, so add it to an entity if you are going to work with the API and the SSO. PLEASE.
|
||||||
|
- **Roles**: A new role was added because we are doing M2M. Only God know how that work now, but it works, so I might start praying from now on.
|
||||||
|
|
||||||
|
|
||||||
|
# Security
|
||||||
|
new firewall was added. Keep the same structure for future firewalls.
|
||||||
|
### Client side
|
||||||
|
```yaml
|
||||||
|
api_project:
|
||||||
|
pattern: ^/api/v1/project #ofc, this is an example, please THINK and change the name
|
||||||
|
stateless: true
|
||||||
|
access_token:
|
||||||
|
token_handler: App\Security\SsoTokenHandler
|
||||||
|
```
|
||||||
|
Same thing, new firewall was added.
|
||||||
|
### Server side
|
||||||
|
```yaml
|
||||||
|
api_token_validation:
|
||||||
|
pattern: ^/api/validate-token #this is NOT an example. DON'T change or it will all go to sh.t
|
||||||
|
stateless: true
|
||||||
|
oauth2: true
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
# A rajouter dans l'access_control aussi !!! IMPORTANT !!!
|
||||||
|
- { path: ^/api/validate-token, roles: PUBLIC_ACCESS }
|
||||||
|
```
|
||||||
|
|
||||||
|
# Controllers
|
||||||
|
On the client side, create a new controller for the API. This controller need will work in a REST manner.
|
||||||
|
The route should be as follows:
|
||||||
|
```php
|
||||||
|
#[Route('/api/v1/project', name: 'api_project')] //ofc, this is an example, please THINK and change the name
|
||||||
|
```
|
||||||
|
Here is a full example of a controller with the create method.
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
#[Route('/api/v1/project', name: 'api_project_')]
|
||||||
|
#[IsGranted('ROLE_API_INTERNAL')]
|
||||||
|
class ProjectApi extends AbstractController{
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $entityManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/create', name: 'create', methods: ['POST'])]
|
||||||
|
public function createProject(Request $request): JSONResponse
|
||||||
|
{
|
||||||
|
$data = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
$projet = new Projet();
|
||||||
|
|
||||||
|
$entity = $this->entityManager->getRepository(Entity::class)->find($data['entity_id']);
|
||||||
|
$precision= $data['timestamp'];
|
||||||
|
$validPrecisions = array_map(fn($case) => $case->value, TimestampPrecision::cases());
|
||||||
|
if (!in_array($precision, $validPrecisions, true)) {
|
||||||
|
return $this->json(['success' => false, 'message' => 'Précision d\'horodatage invalide.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$timestampPrecision = TimestampPrecision::from($precision);
|
||||||
|
$projet->setTimestampPrecision($timestampPrecision);
|
||||||
|
$projet->setProjet($data['projet']);
|
||||||
|
$projet->setEntityId($entity);
|
||||||
|
$projet->setBdd($data['bdd']);
|
||||||
|
$projet->setIsactive($data['isactive']);
|
||||||
|
$projet->setLogo($data['logo']);
|
||||||
|
$projet->setDeletionAllowed($data['deletion']);
|
||||||
|
$projet->setSsoId($data['id']); // c'est l'id du projet dans le portail, pas la bdd local
|
||||||
|
|
||||||
|
$this->entityManager->persist($projet);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
return new JsonResponse(['message' => 'Project created successfully', 'project_id' => $projet->getId()], 201);
|
||||||
|
}catch ( \Exception $e){
|
||||||
|
return new JsonResponse(['error' => 'Failed to create project: ' . $e->getMessage()], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Services
|
||||||
|
So, now we are getting into the thick of it. We are just COOK 🍗.
|
||||||
|
We implement a new pretty service called SsoTokenHandler. This is used to get the token received from the portal request, and validate it.
|
||||||
|
It is validaded by doing a call back to the SSO and asking if the token is valid. ( we create a new token for the SSO so it handles M2M)
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// src/Security/SsoTokenHandler.php
|
||||||
|
namespace App\Security;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
|
||||||
|
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||||
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
|
use Symfony\Component\Security\Core\User\InMemoryUser;
|
||||||
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
|
||||||
|
class SsoTokenHandler implements AccessTokenHandlerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private HttpClientInterface $httpClient,
|
||||||
|
private string $ssoUrl
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getUserBadgeFrom(string $accessToken): UserBadge
|
||||||
|
{
|
||||||
|
// 1. Call the SSO to validate the token
|
||||||
|
// Note: You need an endpoint on your SSO that returns 200 for M2M tokens
|
||||||
|
try {
|
||||||
|
$response = $this->httpClient->request('GET', $this->ssoUrl . '/api/validate-token', [
|
||||||
|
'headers' => ['Authorization' => 'Bearer ' . $accessToken]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// If the SSO redirects, HttpClient might follow it to the login page.
|
||||||
|
// Let's see the first response code.
|
||||||
|
if ($response->getStatusCode() !== 200) {
|
||||||
|
// Log the content to see the HTML "Login" page that is causing the JSON error
|
||||||
|
// dump($response->getContent(false));
|
||||||
|
throw new BadCredentialsException();
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// This will show you if you are hitting a 302 Redirect
|
||||||
|
throw new BadCredentialsException('SSO returned invalid response: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (200 !== $response->getStatusCode()) {
|
||||||
|
throw new BadCredentialsException('Invalid SSO Token');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $response->toArray();
|
||||||
|
|
||||||
|
// 2. Identify if it's a User or a Machine
|
||||||
|
$identifier = $data['email'] ?? 'SYSTEM_SSO_SERVER';
|
||||||
|
|
||||||
|
// 3. Return the badge with a "loader" closure
|
||||||
|
return new UserBadge($identifier, function($userIdentifier) use ($data) {
|
||||||
|
// If it's the SSO server calling, give it a specific role
|
||||||
|
if ($userIdentifier === 'SYSTEM_SSO_SERVER') {
|
||||||
|
return new InMemoryUser($userIdentifier, null, ['ROLE_API_INTERNAL']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, let the normal user provider handle it (for standard users)
|
||||||
|
// You might need to inject your actual UserProvider here if needed
|
||||||
|
return new InMemoryUser($userIdentifier, null, ['ROLE_USER']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Important note 1
|
||||||
|
we need to add the portal url to the .env and declare it as a parameter in services.yaml
|
||||||
|
```dotenv
|
||||||
|
SSO_URL='http://portail.solutions-easy.moi'
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
parameters:
|
||||||
|
sso_url: '%env(SSO_URL)%'
|
||||||
|
|
||||||
|
App\Security\SsoTokenHandler:
|
||||||
|
arguments:
|
||||||
|
$ssoUrl: '%sso_url%'
|
||||||
|
```
|
||||||
|
# Server side
|
||||||
|
On the server side, we need to create a new client, which will be himself, same as earlier, create it, and boom we are good.
|
||||||
|
The validate route is already created, so dw abt it.
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
php bin/console league:oauth2-server:create-client sso_internal_service --grant-type "client_credentials"
|
||||||
|
```
|
||||||
|
|
||||||
|
now, copy the identifier, and paste it in the .env file
|
||||||
|
```dotenv
|
||||||
|
OAUTH_SSO_IDENTIFIER='sso-own-identifier'
|
||||||
|
```
|
||||||
|
and we are smart so what do we do? we add it to the services.yaml
|
||||||
|
```yaml
|
||||||
|
parameters:
|
||||||
|
oauth_sso_identifier: '%env(OAUTH_SSO_IDENTIFIER)%'
|
||||||
|
|
||||||
|
App\Service\SSO\ProjectService:
|
||||||
|
arguments:
|
||||||
|
$appUrl: '%app_url%'
|
||||||
|
$clientIdentifier: '%oauth_sso_identifier%'
|
||||||
|
```
|
||||||
|
|
||||||
|
We should be good now ( I hope ). Open the portal, try your call and check if it works, if it doesn't, check the logs and debug, you are a dev for a reason, so use your brain and debug.
|
||||||
|
If it still doesn't work, start praying, because I have no idea what to do anymore, but it works on my side, so it should work on yours, if not, well, I don't know what to say. Good luck.
|
||||||
|
Jokes aside, bugs often come from security problem, if the client returns a 401 error, it can be for multiple reasons and not necessarily because of the token but maybe because of the token validation.
|
||||||
|
Another commun bug is mismatching of the data you send, so double check. GLHF ( you won't have fun, but good luck anyway )
|
||||||
|
|
@ -320,8 +320,10 @@ If there is a scope or grand error, delete the client do the following first
|
||||||
php bin/console league:oauth2-server:delete-client <identifier>
|
php bin/console league:oauth2-server:delete-client <identifier>
|
||||||
```
|
```
|
||||||
Identifier can be found in the database oauth2_client table
|
Identifier can be found in the database oauth2_client table
|
||||||
The recreate the client and enter the scopes and grant types after creating the client directly in the db
|
To recreate the client and enter the scopes and grant types after creating the client directly in the db
|
||||||
```text
|
```text
|
||||||
scopes = email profile openid
|
scopes = email profile openid
|
||||||
grants = authorization_code
|
grants = authorization_code
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue