Ajout d'une authentification multi-tenant dans Cywise¶
Nous voulons permettre à nos clients de se connecter à Cywise en utilisant leur IdP. Nous voulons utiliser le protocole SAML2.
Nous avons plusieurs clients dans l'application Cywise. Chaque client doit pouvoir avoir son IdP donc notre fonctionnalité doit être multi-tenant.
Réflexions¶
Utiliser une intégration Laravel¶
Laravel Sanctum permet l'authentification des utilisateurs.
Il est capable de générer des tokens API et aussi d'authentifier les utilisateurs pour une application SPA mais il ne permet pas d'utiliser un IdP SAML (ni un IdP OAuth).
Donc Laravel Sanctum ne nous aidera pas.
Laravel Passport permet d'authentifier les utilisateurs auprès d'un IdP OAuth2.
Mais pas d'intégration SAML possible.
Donc Laravel Passport ne nous aidera pas.
La doc explique que Socialite permet aux utilisateurs de s'authentifier auprès d'un IdP OAuth et ajoute :
Socialite currently supports authentication via Facebook, X, LinkedIn, Google, GitHub, GitLab, Bitbucket, and Slack.
Mais j'ai trouvé des add-ons pour Socialite pour beaucoup d'autres IdP. Dont un pour un IdP SAML : https://socialiteproviders.com/Saml2/
Mais la configuration de l'IdP SAML est mise dans config/services.php et je ne vois pas comment je
pourrais en avoir plusieurs pour gérer le multitenant.
Un article sur la façon de l'utiliser : https://tchury.substack.com/p/how-to-add-saml2-login-on-laravel
Librairies pour le SAML¶
Je recherche donc une librairie qui me permette d'intégrer un IdP SAML2. Avec au moins ces critères :
- une librairie maintenue
- une librairie me permettant de configurer l'IdP à la volée (pour le multitenant)
aacotroneo/laravel-saml2¶
Repo : https://github.com/aacotroneo/laravel-saml2 Licence : MIT
C'est la librairie que j'utilisais dans cf-ui mais cette repo n'est plus maintenue. Le README renvoie vers https://github.com/24Slides/laravel-saml2
Cette librairie encapsulait onelogin/php-saml version 3 pour Laravel.
24Slides/laravel-saml2¶
Repo : https://github.com/24Slides/laravel-saml2 Licence : MIT
Comme aacotroneo/laravel-saml2, cette librairie encapsule onelogin/php-saml version 3 (ou 4) pour Laravel.
Je vois que dès la version 2, le support multitenant a été ajouté :
Added
Completely changed the way of supporting multiple Identity Providers by adding Tenants Helper functions saml_url(), saml_route(), saml_tenant_uuid() Initializing SP in middleware Database migrations Console commands saml2:create-tenant, saml2:update-tenant, saml2:delete-tenant, saml2:restore-tenant, saml2:list-tenants, saml2:tenant-credentials
Voir https://github.com/24Slides/laravel-saml2/blob/master/CHANGELOG.md#added-13.
Un article sur son utilisation basique (sans multitenant) : https://joshuajordancallis.medium.com/setup-saml2-with-laravel-acting-as-the-service-provider-d97350d76b32
codegreencreative/laravel-samlidp¶
Repo : https://github.com/codegreencreative/laravel-samlidp Licence : MIT
Cette librairie permet de faire de l'application Laravel un IdP. Elle utilise la librairie de base litesaml/lightsaml version 4.
Donc ce n'est pas ce dont j'ai besoin.
OneLogin¶
Repo : https://github.com/SAML-Toolkits/php-saml (https://github.com/onelogin/php-saml) Licence : MIT
C'est une librairie de base que je devrais intégrer à Laravel si je la choisis. Je préfère utiliser une librairie déjà intégrée à Laravel si j'en trouve une qui me convient.
Actions¶
Je vais faire un POC en utilisant 24Slides/laravel-saml2.
Mes contraintes¶
La repo est publique donc je ne dois ni stocker les infos de mon SP (certificats, etc) ni les infos des IdP de nos clients.
Comme la lib est multitenant et stocke les infos d'un tenant (un IdP) dans la base, rien ne sera dans le code (ne pas faire de Seeder...).
Et les infos du SP peuvent être configurées grâce à des variables d'environnement que je pourrais mettre dans les secrets de Towerify CLI.
Préparation pour mes tests¶
Je veux pouvoir tester mon code depuis mon poste mais pour ajouter un SP à Keycloak j'ai besoin d'un domaine et du HTTPS.
Pour avoir un domaine et du HTTPS, j'utilise ngrok et la commande :
ngrok http http://127.0.0.1:8080/
Warning
Le domaine change à chaque redémarrage de ngrok
J'ai ajouté un domaine static (gratuit) dans mon compte ngrok et je peux maintenant
avoir toujours ce nom de domaine si j'utilise la commande :
ngrok http --url=weasel-exotic-llama.ngrok-free.app http://127.0.0.1:8080/
Info
Je me connecte à mon compte ngrok en utilisant mon compte Google pbrisacier@mncc.fr
Je vais créer 2 IdP dans Keycloak : cywise1 et cywise2.
Je vais décrire mes configurations pour cywise1 puis je referai la même chose pour cywise2.
J'ajoute le royaume cywise1.
J'ai accès au metadata SAML : https://auth.computablefacts.com/realms/cywise1/protocol/saml/descriptor
Je peux y lire les informations dont j'ai besoin pour créer le tenant dans l'application :
php artisan saml2:create-tenant \
--key=cywise1 \
--entityId=https://auth.computablefacts.com/realms/cywise1 \
--loginUrl=https://auth.computablefacts.com/realms/cywise1/protocol/saml \
--logoutUrl=https://auth.computablefacts.com/realms/cywise1/protocol/saml \
--x509cert="MIICnTCCAYUCBgGWIBRiSzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjeXdpc2UxMB4XDTI1MDQxMDE0MjAyOVoXDTM1MDQxMDE0MjIwOVowEjEQMA4GA1UEAwwHY3l3aXNlMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL/9UEM235k5H3ntv24kg0SBJkimjEa6LdwwJ31PM/f4KOQAwqF2MnnIessPNgqmeAVx/+VSVOGQJ3AzU6GrIYHDiCO1vfTpWKz5dnNVz9zd8B9y+urrM2vSnOX6nTve9aqWBTO6GqNPYCgsO8X7efeBd8VQQSk7GDTHSyE+2b408fTzNoO7+yVcn1VjVO+KvoT9iBG9ycJWhwnlFgR/cRpq2RzlYuwisua8htInt3nlk3718LaOC058ZOucCkvkOYYoIFxxJombuvbPthhFC55M0++Lj8OQK4NTLHMDIuS3bIbjWHSlO6YeR3IR1lHYUclXR+iH2pDL/knnSLGz3jsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEACljTuMR0PmKBAnXEMmOp6RpP86KMZ0s6mDenm5t5kDxNfCFmNKz1cY/t6FP9anHZBtyHqM0S8mZGPq75JwnsPhtf5SQvBvZSkajImPiiKupkCsTUfFPpJZVnFHKvKjJIwCkN0KNnumLZ7JS582WZeV1PF7bb4AtL8umBhsdtJKpFKUg+8obedOJPrh2GRm325vjDIJVEpGB7REHo+mb4qZvyLfqxAxXTJQU4YUqiIpYN1oAxjuXCtxRWKw2+/ulx9YirJNn1bjtqqmOVMk37h7IfJnFCY7iVLBC0Xe8c1asNhPK+Fj8RLBW6e53glDY4mnGxWJp/OFkG8wY7/u6/CQ=="
The tenant #1 (2f57bc95-d2fa-486f-8a88-5f6ded3d764f) was successfully created.
Credentials for the tenant
--------------------------
Identifier (Entity ID): http://localhost:8178/saml2/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/metadata
Reply URL (Assertion Consumer Service URL): http://localhost:8178/saml2/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/acs
Sign on URL: http://localhost:8178/saml2/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/login
Logout URL: http://localhost:8178/saml2/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/logout
Relay State: (optional)
Je change la config pour passer du préfixe /saml2 au préfixe /saml :
'routesPrefix' => '/saml',
Je change APP_URL dans mon .env local :
APP_URL=https://weasel-exotic-llama.ngrok-free.app
Ce qui met à jour les infos du tenant que je peux voir avec php artisan saml2:tenant-credentials 1.
Credentials for the tenant
--------------------------
Identifier (Entity ID): https://weasel-exotic-llama.ngrok-free.app/saml/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/metadata
Reply URL (Assertion Consumer Service URL): https://weasel-exotic-llama.ngrok-free.app/saml/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/acs
Sign on URL: https://weasel-exotic-llama.ngrok-free.app/saml/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/login
Logout URL: https://weasel-exotic-llama.ngrok-free.app/saml/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/logout
Relay State: (optional)
J'en profite pour changer les infos de l'organisation et des contacts en ajoutant ça dans mon .env :
SAML2_CONTACT_TECHNICAL_NAME="Patrick Brisacier"
SAML2_CONTACT_TECHNICAL_EMAIL=pbrisacier+saml@mncc.fr
SAML2_CONTACT_SUPPORT_NAME=Support
SAML2_CONTACT_SUPPORT_EMAIL=support@computablefacts.freshdesk.com
SAML2_ORGANIZATION_NAME=ComputableFacts
SAML2_ORGANIZATION_URL=https://cywise.io/
Je génère un certificat X509 pour mon SP avec les commandes :
openssl genrsa -out cywise-local.pem 2048
openssl req -new -key cywise-local.pem -out cywise-local.csr
openssl x509 -req -days 3650 -in cywise-local.csr -signkey cywise-local.pem -out cywise-local.crt
Puis j'ajoute le certificat (cywise-local.crt) et la clé privée (cywise-local.pem) dans mon .env :
SAML2_SP_CERT_x509="MIIDFTCC****L5rJIQ=="
SAML2_SP_CERT_PRIVATEKEY="MIIEvAIB****6B7UuA=="
Le certificat apparaît maintenant dans les metadata.
Je sauvegarde les metadata dans un fichier cywise1-metadata.xml.
Je crée un client Keycloak cywise-dev-patrick dans cywise1 en important cywise1-metadata.xml.
NOTA : j'ai dû supprimer une balise <script/> dans le fichier cywise1-metadata.xml pour que
Keycloak veuille importer le fichier.
Par défaut, Keycloak active l'option (du client) "Client Signature Required".
J'ai dû changer les 2 options ci-dessous à true pour activer la signature pour mon SP :
'authnRequestsSigned' => true,
'logoutRequestSigned' => true,
ERROR [org.keycloak.protocol.saml.SamlService] (executor-thread-12334) request validation failed: org.keycloak.common.VerificationException: SigAlg was null
En cliquant sur l'URL "Sign on URL", je peux valider un login/password (cywise1 / cywise1-patrick) puis je suis redirigé vers Cywise. Il y a des erreurs mais je n'ai rien codé pour authentifier l'utilisateur.
Modification du process de login de Laravel¶
Je veux découper la phase de login en 2 parties : - demander l'email uniquement - demander le password ensuite
Cela va me permettre, quand le formulaire de demande de l'email sera posté, de récupérer le domaine et de rediriger l'utilisateur vers l'IdP qui correspond à ce domaine s'il y en a un.
Nous utilisons Laravel UI pour gérer le login, le logout, register, etc.
J'ai refait la commande php artisan ui bootstrap --auth pour voir les fichiers mis en place.
Pour les controllers, il y a : HomeController, Auth\LoginController, Auth\RegisterController,
Auth\ForgotPasswordController, Auth\ResetPasswordController que nous avions déjà et
Auth\ConfirmPasswordController et Auth\VerificationController que nous n'avons pas.
Les routes sont mises en place grâce à Auth::routes(); qui se trouve dans le fichier
routes/web.php.
Cela appelle la méthode auth() de la classe AuthRouteMethods (vendor/laravel/ui/src/AuthRouteMethods.php).
Comme il n'y a pas d'options passées à la méthode, cela met en place les routes pour login, logout, register et reset. Comme on peut le voir avec cette commande :
$ php artisan route:list | grep Auth
GET|HEAD login ........................................ login › Auth\LoginController@showLoginForm
POST login ........................................................ Auth\LoginController@login
POST logout ............................................. logout › Auth\LoginController@logout
GET|HEAD password/confirm ...... password.confirm › Auth\ConfirmPasswordController@showConfirmForm
POST password/confirm ................................. Auth\ConfirmPasswordController@confirm
POST password/email ........ password.email › Auth\ForgotPasswordController@sendResetLinkEmail
GET|HEAD password/reset ..... password.request › Auth\ForgotPasswordController@showLinkRequestForm
POST password/reset ..................... password.update › Auth\ResetPasswordController@reset
GET|HEAD password/reset/{token} ...... password.reset › Auth\ResetPasswordController@showResetForm
GET|HEAD register ........................ register › Auth\RegisterController@showRegistrationForm
POST register ............................................... Auth\RegisterController@register
Je vais me concentrer sur le login et le logout mais je devrais gérer les autres correctement plus tard.
Fonctionnement actuel du login : - GET /login -> LoginController@showLoginForm -> view('auth.login') -> valider le formulaire appelle POST /login - POST /login -> LoginController@login -> sendLoginResponse -> redirectTo /home
Fonctionnement voulu du login : - GET /login -> LoginController@showLoginForm -> view('auth.login') -> valider le formulaire appelle POST /login/email - POST /login/email -> LoginController@loginEmail -> redirect GET /login/password - GET /login/password -> LoginController@showLoginPasswordForm -> view('auth.login_password') -> valider le formulaire appelle POST /login - POST /login -> LoginController@login -> sendLoginResponse -> redirectTo /home
Je dois donc - ajouter 2 routes : - POST /login/email -> LoginController@loginEmail - GET /login/password -> LoginController@showLoginPasswordForm - modifier la vue 'auth.login' (pour qu'elle ne demande que l'email et qu'elle le POST vers /login/email) - ajouter la vue 'auth.login_password' (qui demande le password et masque le champ email)
J'ai fait la modification et ça fonctionne.
Redirection vers l'IdP SAML en fonction du domaine de l'email¶
La librairie 24Slides/laravel-saml2 stocke les tenants SAML dans la table saml2_tenants et utilise le modèle \Slides\Saml2\Models\Tenant::class pour y accéder.
Je vais ajouter une table de correspondance entre un domaine et un tenant SAML. Ainsi, après l'envoi de l'email, à l'étape 1 du process de login, je pourrais extraire le domaine de l'email et renvoyer vers l'IdP (le tenant SAML) correspondant s'il y en a un sinon, je continue le process en demande le password à l'étape 2.
Je vais appeller la table saml2_email_domains et le modèle SamlEmailDomain (sans le 2).
J'ai modifié la méthode loginEmail pour retrouver l'IdP lié au domaine de l'email
puis rediriger l'utilisateur vers cet IdP.
Problème avec ngrok et le port¶
Après authentification dans Keycloak cywise1, j'ai ce message d'erreur :
The response was received at http://weasel-exotic-llama.ngrok-free.app:8080/saml/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/acs
instead of https://weasel-exotic-llama.ngrok-free.app/saml/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/acs
J'active l'option de la librairie prévue pour ça (This is useful if your application is running behind a load balancer which terminates SSL.) :
'proxyVars' => true,
J'ai maintenant ce message d'erreur :
The response was received at https://weasel-exotic-llama.ngrok-free.app:8080/saml/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/acs
instead of https://weasel-exotic-llama.ngrok-free.app/saml/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/acs
https au lieu de http mais il reste le port 8080 qui est
celui que j'utilise en local.
Il faudrait que ngrok passe l'entête X-Forwarded-For: 443 dans les entêtes de la requête envoyée
à mon application locale pour que ça fonctionne.
Après un peu de temps dans la doc de ngrok, je vois qu'on peut ajouter, dans le fichier de configuration de ngrok,
une section traffic_policy qui permet d'ajouter l'entête.
J'ai d'abord converti la version du fichier de config (seule la version 3 permet d'ajouter une section traffic_policy)
avec :
ngrok config upgrade
Puis j'ai modifié le fichier de config avec la commande ngrok config edit pour y mettre :
version: "3"
agent:
authtoken: '***'
connect_url: connect.eu.ngrok-agent.com:443
endpoints:
- name: cywise-saml-debug
description: Debug Cywise for SAML IdP auth
url: weasel-exotic-llama.ngrok-free.app
upstream:
url: http://127.0.0.1:8080/
traffic_policy:
on_http_request:
- actions:
- type: add-headers
config:
headers:
X-Forwarded-Port: ${conn.server_port}
Enfin, je démarre ngrok avec la commande :
ngrok start cywise-saml-debug
Modification dans la base¶
J'avais créé une table saml2_email_domains et le modèle SamlEmailDomain qui va avec
mais j'ai une relation OneToOne entre cette table et celle créée par la librairie
(saml2_tenants).
Je préfère tout mettre dans la même table (saml2_tenants) en reprenant les colonnes
des migrations de la librairie et en ajoutant mes colonnes.
Voilà ma version de la migration :
Schema::create('saml2_tenants', function (Blueprint $table) {
$table->id();
$table->timestamps();
// The associated Cywise tenant
$table->intOrBigIntBasedOnRelated('tenant_id', Schema::connection(null), 'tenants.id')->nullable();
$table->foreign('tenant_id')->references('id')->on('tenants')->cascadeOnDelete();
// The associated customer
$table->intOrBigIntBasedOnRelated('customer_id', Schema::connection(null), 'customers.id')->unsigned()->nullable();
$table->foreign('customer_id')->references('id')->on('customers')->cascadeOnDelete();
// Associated email domains
$table->string('domain')->default('');
$table->string('alt_domain1')->default('');
$table->uuid();
$table->string('key')->nullable();
$table->string('idp_entity_id');
$table->string('idp_login_url');
$table->string('idp_logout_url');
$table->text('idp_x509_cert');
$table->string('relay_state_url')->nullable();
$table->string('name_id_format')->default('persistent');
$table->json('metadata');
$table->softDeletes();
});
J'ai ajouté les colonnes tenant_id et customer_id pour les utiliser si j'ai besoin
de créer l'utilisateur (à sa première connexion).
J'ai ajouté les colonnes domain et alt_domain1 pour lier le tenant SAML aux domaines
du client (par exemple hermes.com et ext.hermes.com).
J'ai créé un modèle pour aller avec la table en héritant du modèle de la librairie :
namespace App\Models;
use Slides\Saml2\Models\Tenant;
class Saml2Tenant extends Tenant
{
public static function firstFromDomain(string $domain)
{
return self::query()
->where('domain', '=', $domain)
->orWhere('alt_domain1', '=', $domain)
->first();
}
}
Enfin, je modifie les options de la librairie prévue pour ça pour utiliser mon modèle et ne pas faire les migrations par défaut :
'tenantModel' => \App\Models\Saml2Tenant::class,
'load_migrations' => false,
Ajout de claims dans Keycloak¶
Avec le réglage par défaut de Keycloak, Cywise ne reçoit que l'attribut role et pas
l'email ni le nom de l'utilisateur.
J'ai ajouté l'email et le "common name" dans les claims (je me suis inspiré de mes réglages pour ISTA).
Authentifier l'utilisateur dans Cywise¶
Je dois maintenant authentifier l'utilisateur dans Cywise. Et, avant, créer son compte s'il n'existe pas déjà.
Pour ça, je dois faire un Listener pour l'événement SignedIn de la librairie.
Comme je vais devoir aussi faire un Listener pour l'événement SignedOut, j'ai
préféré les regrouper dans la même classe app/Listeners/SamlEventSubscriber.php
qui ressemble à :
namespace App\Listeners;
use Illuminate\Events\Dispatcher;
use Slides\Saml2\Events\SignedIn;
use Slides\Saml2\Events\SignedOut;
class SamlEventSubscriber
{
/**
* Register the listeners for the subscriber.
*
* @return array<string, string>
*/
public function subscribe(Dispatcher $events): array
{
return [
SignedIn::class => 'handleSignedIn',
SignedOut::class => 'handleSignedOut',
];
}
public function handleSignedIn(SignedIn $event): void {}
public function handleSignedOut(SignedOut $event): void {}
}
Puis, j'ai enregistré cette classe dans la méthode boot() de la classe
app/Providers/AppServiceProvider.php :
// SAML
Event::subscribe(SamlEventSubscriber::class);
Je crée l'utilisateur en passant par la classe invitation :
$invitation = Invitation::query()
->where('email', $this->saml2UserEmail)
->first();
if (!$invitation) {
$invitation = InvitationProxy::createInvitation($this->saml2UserEmail, $this->saml2UserName);
}
$user = $invitation->createUser([
'password' => Str::random(64),
'tenant_id' => $tenantId,
'customer_id' => $customerId,
]);
Au départ, j'arrivais à créer l'utilisateur mais il n'était pas connecté à Cywise.
Le problème venait des middlewares pour gérer les cookies (pour la session) qui
n'était pas actif.
J'ai résolu le problème en ajoutant un MiddlewareGroup dans app/Http/Kernel.php :
'saml' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
],
'routesMiddleware' => ['saml'],
Logout¶
Comme expliqué dans la doc officielle de la librairie, il y a 2 cas : * logout depuis l'IdP, Keycloak pour mes tests * logout depuis Cywise
Pour le logout depuis l'IdP, cela appelle l'URL /saml/{uuid}/slo qui déclenche
l'événement SignedOut.
Il a donc suffit que je déconnecte l'utilisateur de Cywise pour cet événement :
public function handleSignedOut(SignedOut $event): void
{
Auth::logout();
Session::save();
}
Pour le logout depuis Cywise, il faut que j'appelle le logout de l'IdP. Mais seulement si l'utilisateur est connecté depuis un IdP.
Heureusement, si l'utilisateur s'est connecté depuis un IdP, le helper
saml_tenant_uuid() renvoit l'UUID du tenant SAML. Et le helper renvoie
null si l'utilisateur ne vient pas d'un IdP.
J'ai donc copier la méthode logout() de xxx dans ma classe LoginController
pour ajouter ce test au début et rediriger vers le logout de l'IdP :
if ($uuid = saml_tenant_uuid()) {
return redirect(URL::route('saml.logout', ['uuid' => $uuid]));
}
Ca faisait une erreur jusqu'à ce que je change ce setting de la librairie pour mettre une route de logout par défaut à '/' :
'logoutRoute' => env('SAML2_LOGOUT_URL', '/'),
Settings JSON pour Tenant SAML¶
Je veux pouvoir changer des paramétrages pour chaque tenant SAML.
Par exemple, les noms des claims pour récupérer l'email, le nom et les roles
de l'utilisateur.
Je vais stocker ces paramétrages dans une colonne JSON dans la table qui
contient les tenants SAML (saml2_tenants).
La table par défaut de la librairie contenait déjà une colonne JSON appelée
metadata que j'ai conservée pour stocker les paramétrages (en passant, metadata
est un très mauvais nom car ça n'a rien à voir avec les metadata de l'IdP...)
Je vais faire une méthode dans ma classe Saml2Tenant qui me permettra de récupérer une des clés du JSON (un paramètre) et de renvoyer une valeur par défaut si la clé n'existe pas.
Les requêtes SQL que j'ai faites pour tester les settings actuels :
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.claims', JSON_OBJECT())
WHERE `key`='cywise1' AND JSON_EXTRACT(metadata, '$.claims') IS NULL;
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.claims.email', JSON_OBJECT())
WHERE `key`='cywise1' AND JSON_EXTRACT(metadata, '$.claims.email') IS NULL;
update saml2_tenants set metadata = JSON_SET(metadata, '$.claims.email.friendlyName', 'email', '$.claims.email.name', 'http://schemas.xmlsoap.org/claims/EmailAddress')
where `key`='cywise1';
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.claims.name', JSON_OBJECT())
WHERE `key`='cywise1' AND JSON_EXTRACT(metadata, '$.claims.name') IS NULL;
update saml2_tenants set metadata = JSON_SET(metadata, '$.claims.name.friendlyName', 'name', '$.claims.name.name', 'http://schemas.xmlsoap.org/claims/CommonName')
where `key`='cywise1';
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.claims.role', JSON_OBJECT())
WHERE `key`='cywise1' AND JSON_EXTRACT(metadata, '$.claims.role') IS NULL;
update saml2_tenants set metadata = JSON_SET(metadata, '$.claims.role.friendlyName', 'role', '$.claims.role.name', 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role')
where `key`='cywise1';
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.updateUser', JSON_OBJECT())
WHERE `key`='cywise1' AND JSON_EXTRACT(metadata, '$.updateUser') IS NULL;
update saml2_tenants set metadata = JSON_SET(metadata, '$.updateUser.putRandomPassword', true)
where `key`='cywise1';
Puis je lis ces clés avec un code de ce genre :
$roleFriendlyName = $this->saml2Tenant->config('claims.role.friendlyName', 'role');
2025-04-30 - Publication en DEV¶
J'ai fusionné ma branche avec 0.x et j'ai déployé en DEV.
Je refais une conf vers Keycloak cywise1.
Je me suis connecté à yunohost-addapps puis au container app de cywise_ui_dev avec
sudo docker exec -it e5529ce4388d /bin/bash.
Cela m'a permis de refaire la commande artisan pour créer le tenant SAML de l'IdP cywise1 avec la même commande que celle que j'avais utilisée en local :
php artisan saml2:create-tenant \
--key=cywise1 \
--entityId=https://auth.computablefacts.com/realms/cywise1 \
--loginUrl=https://auth.computablefacts.com/realms/cywise1/protocol/saml \
--logoutUrl=https://auth.computablefacts.com/realms/cywise1/protocol/saml \
--x509cert="MIICnTCCAYUCBgGWIBRiSzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjeXdpc2UxMB4XDTI1MDQxMDE0MjAyOVoXDTM1MDQxMDE0MjIwOVowEjEQMA4GA1UEAwwHY3l3aXNlMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL/9UEM235k5H3ntv24kg0SBJkimjEa6LdwwJ31PM/f4KOQAwqF2MnnIessPNgqmeAVx/+VSVOGQJ3AzU6GrIYHDiCO1vfTpWKz5dnNVz9zd8B9y+urrM2vSnOX6nTve9aqWBTO6GqNPYCgsO8X7efeBd8VQQSk7GDTHSyE+2b408fTzNoO7+yVcn1VjVO+KvoT9iBG9ycJWhwnlFgR/cRpq2RzlYuwisua8htInt3nlk3718LaOC058ZOucCkvkOYYoIFxxJombuvbPthhFC55M0++Lj8OQK4NTLHMDIuS3bIbjWHSlO6YeR3IR1lHYUclXR+iH2pDL/knnSLGz3jsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEACljTuMR0PmKBAnXEMmOp6RpP86KMZ0s6mDenm5t5kDxNfCFmNKz1cY/t6FP9anHZBtyHqM0S8mZGPq75JwnsPhtf5SQvBvZSkajImPiiKupkCsTUfFPpJZVnFHKvKjJIwCkN0KNnumLZ7JS582WZeV1PF7bb4AtL8umBhsdtJKpFKUg+8obedOJPrh2GRm325vjDIJVEpGB7REHo+mb4qZvyLfqxAxXTJQU4YUqiIpYN1oAxjuXCtxRWKw2+/ulx9YirJNn1bjtqqmOVMk37h7IfJnFCY7iVLBC0Xe8c1asNhPK+Fj8RLBW6e53glDY4mnGxWJp/OFkG8wY7/u6/CQ=="
La réponse :
The tenant #1 (565e5bfc-9804-4ed8-b174-c6138c4dec31) was successfully created.
Credentials for the tenant
--------------------------
Identifier (Entity ID): https://dev.cywise-ui.myapps.addapps.io/saml/565e5bfc-9804-4ed8-b174-c6138c4dec31/metadata
Reply URL (Assertion Consumer Service URL): https://dev.cywise-ui.myapps.addapps.io/saml/565e5bfc-9804-4ed8-b174-c6138c4dec31/acs
Sign on URL: https://dev.cywise-ui.myapps.addapps.io/saml/565e5bfc-9804-4ed8-b174-c6138c4dec31/login
Logout URL: https://dev.cywise-ui.myapps.addapps.io/saml/565e5bfc-9804-4ed8-b174-c6138c4dec31/logout
Relay State: (optional)
J'utilise phpMyAdmin pour modifier ce tenant SAML afin d'ajouter le domaine cywise1.io.
J'ai également refait la configuration d'un nouveau client dans Keycloak. Et j'ai pu me connecter avec cywise1 / cywise1-patrick / demo@cywise1.io et voir la page /home.
=> La première fois j'ai eu un timeout (504)
Mais je n'ai pas reproduit le problème les fois suivantes.
2025-04-30 - Publication en PROD¶
Je refais à nouveau le paramétrage de Keycloak cywise1 pour le domaine cywise1.io :
The tenant #1 (48696b8c-225d-4b98-8102-0f8f9e7f9460) was successfully created.
Credentials for the tenant
--------------------------
Identifier (Entity ID): https://app.cywise.io/saml/48696b8c-225d-4b98-8102-0f8f9e7f9460/metadata
Reply URL (Assertion Consumer Service URL): https://app.cywise.io/saml/48696b8c-225d-4b98-8102-0f8f9e7f9460/acs
Sign on URL: https://app.cywise.io/saml/48696b8c-225d-4b98-8102-0f8f9e7f9460/login
Logout URL: https://app.cywise.io/saml/48696b8c-225d-4b98-8102-0f8f9e7f9460/logout
Relay State: (optional)
Je génère un certificat X509 pour mon SP avec les commandes :
openssl genrsa -out cywise-prod.pem 2048
openssl req -new -key cywise-prod.pem -out cywise-prod.csr
openssl x509 -req -days 3650 -in cywise-prod.csr -signkey cywise-prod.pem -out cywise-prod.crt
Puis j'ajoute le certificat (cywise-prod.crt) et la clé privée (cywise-prod.pem) dans mon .env :
SAML2_SP_CERT_x509="MIIDFTCC****1tXxVA=="
SAML2_SP_CERT_PRIVATEKEY="MIIEvgIB****cbdUx8Gr"
NOTA: je n'ai pas fait cette étape pour Cywise DEV car, je pense, j'ai les variables
dans mon .env local et, comme c'est moi qui ai publié, mon .env se trouve
sur Cywise en plus des secrets...
Je suis allé modifier directement le fichier /home/yunohost.app/cywise-ui_prod/.env pour y ajouter les 2 clés.
Puis j'ai redémarré la stack avec sudo systemctl restart cywise-ui_prod.service pour que le
changement soit pris en compte.
J'ai ajouté le domaine cywise1 à ce tenant SAML et j'ai ajouté tenant_id=18 et customer_id=9 car
ce sont les IDs des utilisateurs d'Hermès.
customer_id a bien été mis à jour mais pas tenant_id
=> Normal, tenant_id n'était pas fillable. Cyrille a commité cette modif.
Etrange : je n'ai pas réussi à supprimer mon utilisateur. J'avais une erreur sur la contrainte avec
am_assets et sa colonne created_by pourtant, aucun asset de cette table n'avait un created_by=113
(l'ID de mon user).
J'ai dû le supprimer en décochant la vérification des contraintes...
Dans app/Listeners/UserInvitationUtilizedListener.php, les tenant_id et customer_id de l'utilisateur
créés à partir d'une invitation sont copiés depuis ceux de l'utilisateur qui a créé l'invitation (created_by).
Mais, dans mon cas, je n'ai pas de created_by donc pas de copie. La classe met également en place les
rôles par défaut et ceux de Hermès si un created_by est défini donc ne le fait pas dans mon cas.
Je pense que c'est mieux de ne pas mettre de created_by à mon invitation, comme ça je peux gérer les ID et
les rôles indépendamment des invitations "standards".
Je vais stocker dans les settings JSON de mon tenant SAML, les rôles par défaut que je dois attribuer à tous les utilisateurs et les correspondances entre "groupes IdP" et rôles à attribuer à l'utilisateur.
A partir de la chaîne 'App\Models\Role::CYBERBUDDY_ADMIN' je peux retrouver la valeur de la constante avec constant('App\Models\Role::CYBERBUDDY_ADMIN'). NOTA : il faut absoluement mettre le namespace.
Je crée le tenant SAML pour Hermès car je veux envoyer les URLs aujourd'hui. Sur le serveur, dans le container de Cywise PROD app, je fais la commande :
php artisan saml2:create-tenant \
--key=hermes \
--entityId=http://fed.hermes.com/adfs/services/trust \
--loginUrl=https://fed.hermes.com/adfs/ls/ \
--logoutUrl=https://fed.hermes.com/adfs/ls/ \
--x509cert="MIIC2DCCAcCgAwIBAgIQJh/EANNXJb5LBvCDXcXOwzANBgkqhkiG9w0BAQsFADAoMSYwJAYDVQQDEx1BREZTIFNpZ25pbmcgLSBmZWQuaGVybWVzLmNvbTAeFw0yMzA5MjcwOTM2MjBaFw0yODA5MjcwOTM2MjBaMCgxJjAkBgNVBAMTHUFERlMgU2lnbmluZyAtIGZlZC5oZXJtZXMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwZT8DsXjzQO02YpXZ99PmIXanvWp+dDsP0SQmMJMHoKgsmj5gDlYohkCXlt2K9hYVa84drBYxLZLmY3MotGVoQhwDV/vHIMJOzys2IKMBtHjsPPYVML9tFJRZPtMIxH3Dzp1sPDiDmY67IZhgcEiEzlIuAcsToTUNSZjR+1kd+4j5nuf5NBtV0dc499yOs3s8q4rk4R0fd2BwC2QawBvu2iTT8xsBX8i4N3N3kLhJeSSgHUIg7QZNPNmo+cs6HfGehGhn2jcfAotYLRZfV8V15RK5DIXgruzqGVOgE91twSQKGepFxVrEdgGwszyzEyzaqBkcU3nTl2SEQ3yX7B9uQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBPfTe/w4or4QV0ffysfXBiuisGu7ThZRU6stJ+pO1Y6aYXyM2Xe4PmTKwY0evkSJmLrNWlqRpoNEcR3H3fCDHaMKVWV+/4QJ3W5Xd1bPwX0qYYyHJxn4TkH2q0cmidjYWP1KxL/57uCbzpdwtKBrA/dOi+MlFJ9O1GhAuKk/BC22iXNoROQ6nfL8tJdvWMx2I7HI5CrDZzoYPtH+liJP05rfpSf31FnkKIedPb6FHiSgTkM44XUB9nqml0DPLWvxTXJ4XYmHGmCI9REOTY7YUy7U0wevV7w9elXcr1T3NSDj2y8/avZVhRgjsKsq2DWwewGT5PUazU7MIz6Yq806vF"
The tenant #2 (bce5732e-8860-47d0-89e4-b3adccccb750) was successfully created.
Credentials for the tenant
--------------------------
Identifier (Entity ID): https://app.cywise.io/saml/bce5732e-8860-47d0-89e4-b3adccccb750/metadata
Reply URL (Assertion Consumer Service URL): https://app.cywise.io/saml/bce5732e-8860-47d0-89e4-b3adccccb750/acs
Sign on URL: https://app.cywise.io/saml/bce5732e-8860-47d0-89e4-b3adccccb750/login
Logout URL: https://app.cywise.io/saml/bce5732e-8860-47d0-89e4-b3adccccb750/logout
Relay State: (optional)
Dans la base de données, je change : - tenant_id=18 - customer_id=9 - domain=hermes.com.test - alt_domain1=ext.hermes.com.test - uuid=0d92ec95-af6a-48c4-b890-6f701a6f33fb (je mets celui que j'ai déjà communiqué à Hermès)
Ce qui donne :
root@086bc0971261:/var/www/html# php artisan saml2:tenant-credentials 2
The tenant model
================
+-----------------+-------------------------------------------------------+
| Column | Value |
+-----------------+-------------------------------------------------------+
| ID | 2 |
| UUID | 0d92ec95-af6a-48c4-b890-6f701a6f33fb |
| Key | hermes |
| Entity ID | http://fed.hermes.com/adfs/services/trust |
| Login URL | https://fed.hermes.com/adfs/ls/ |
| Logout URL | https://fed.hermes.com/adfs/ls/ |
| Relay State URL | (empty) |
| Name ID format | persistent |
| x509 cert | MIIC2DCCAcCgAwIBAgIQJh/EANNXJb5LBvCDXcXOwzANBgkqhk... |
| Metadata | (empty) |
| Created | 2025-05-02 10:19:22 |
| Updated | 2025-05-02 10:19:22 |
| Deleted | (empty) |
+-----------------+-------------------------------------------------------+
Credentials for the tenant
--------------------------
Identifier (Entity ID): https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/metadata
Reply URL (Assertion Consumer Service URL): https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/acs
Sign on URL: https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/login
Logout URL: https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/logout
Relay State: (optional)
Je ne veux ne pas modifier le mot de passe des comptes Hermès existants, pour le moment,
durant la phase de tests avec Amine.
Comme la valeur par défaut de updateUser.putRandomPassword est false, je n'ai pas
besoin de changer les settings du Tenant SAML.
Je dois faire les réglages des claims. D'après les metadata de l'IdP d'Hermès, je vais paramétrer : - claims.email.friendlyName = email (default) - claims.email.name = http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress - claims.name.friendlyName = name (default) - claims.name.name = http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname - claims.role.friendlyName = group - claims.role.name = http://schemas.xmlsoap.org/claims/Group
J'ai donc changé les settings avec les requêtes SQL ci-dessous :
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.claims', JSON_OBJECT())
WHERE `key`='hermes' AND JSON_EXTRACT(metadata, '$.claims') IS NULL;
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.claims.email', JSON_OBJECT())
WHERE `key`='hermes' AND JSON_EXTRACT(metadata, '$.claims.email') IS NULL;
update saml2_tenants set metadata = JSON_SET(metadata, '$.claims.email.name', 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress')
where `key`='hermes';
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.claims.name', JSON_OBJECT())
WHERE `key`='hermes' AND JSON_EXTRACT(metadata, '$.claims.name') IS NULL;
update saml2_tenants set metadata = JSON_SET(metadata, '$.claims.name.name', 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname')
where `key`='hermes';
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.claims.role', JSON_OBJECT())
WHERE `key`='hermes' AND JSON_EXTRACT(metadata, '$.claims.role') IS NULL;
update saml2_tenants set metadata = JSON_SET(metadata, '$.claims.role.friendlyName', 'group', '$.claims.role.name', 'http://schemas.xmlsoap.org/claims/Group')
where `key`='hermes';
Mise à jour des rôles¶
Je veux pouvoir paramétrer les rôles mis en place quand un utilisateur se connecte en passant par un tenant SAML : - ajouter une liste de rôles à tous les utilisateurs - ajouter une liste de rôles pour un ou plusieurs rôles issus de l'IdP
J'ai fait un paramétrage de ce type :
"roles": {
"default": ["role1", "role2"],
"idp_roles": ["user", "admin"],
"user_to_cywise": ["cywise_user"],
"admin_to_cywise": ["cywise_admin", "cywise_admin2"]
}
default donne la liste des rôles Cywise à ajouter à chaque utilisateur.
La clé idp_roles donne la liste des rôles de l'IdP à prendre en compte.
Pour chaque rôle de l'IdP, la clé <idp_role>_to_cywise donne la liste des rôles
Cywise à ajouter à l'utilisateur s'il a ce rôle <idp_role>.
Le paramétrage fait sur Cywise DEV pour l'IdP cywise1 afin de fait mes tests :
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.roles', JSON_OBJECT())
WHERE `key`='cywise1' AND JSON_EXTRACT(metadata, '$.roles') IS NULL;
update saml2_tenants set metadata = JSON_SET(metadata, '$.roles.default', JSON_ARRAY('App\\Models\\Role::CYBERBUDDY_ONLY'))
where `key`='cywise1';
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.roles.idp_roles', JSON_ARRAY('manage-account'))
WHERE `key`='cywise1';
update saml2_tenants set metadata = JSON_SET(metadata, '$.roles.manage-account_to_cywise', JSON_ARRAY('App\\Models\\Role::CYBERBUDDY_ADMIN'))
where `key`='cywise1';
2025-05-05 - Publication en PROD¶
Cyrille a fait la MEP ce matin.
Je vais ajouter les paramètres pour la synchronisation des rôles avec l'IdP d'Hermès :
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.roles', JSON_OBJECT())
WHERE `key`='hermes' AND JSON_EXTRACT(metadata, '$.roles') IS NULL;
update saml2_tenants set metadata = JSON_SET(metadata, '$.roles.default', JSON_ARRAY('App\\Models\\Role::CYBERBUDDY_ONLY'))
where `key`='hermes';
UPDATE saml2_tenants SET metadata = JSON_SET(metadata, '$.roles.idp_roles', JSON_ARRAY('SG-WORLD-CU-CYBERBUDDY-ADMIN-SSO'))
WHERE `key`='hermes';
update saml2_tenants set metadata = JSON_SET(metadata, '$.roles.SG-WORLD-CU-CYBERBUDDY-ADMIN-SSO_to_cywise', JSON_ARRAY('App\\Models\\Role::CYBERBUDDY_ADMIN'))
where `key`='hermes';
Avant :
{
"claims": {
"email": {
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
},
"name": {
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
},
"role": {
"friendlyName": "group",
"name": "http://schemas.xmlsoap.org/claims/Group"
}
}
}
Après :
{
"claims": {
"email": {
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
},
"name": {
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
},
"role": {
"friendlyName": "group",
"name": "http://schemas.xmlsoap.org/claims/Group"
}
},
"roles": {
"default": ["App\\Models\\Role::CYBERBUDDY_ONLY"],
"idp_roles": ["SG-WORLD-CU-CYBERBUDDY-ADMIN-SSO"],
"SG-WORLD-CU-CYBERBUDDY-ADMIN-SSO_to_cywise": ["App\\Models\\Role::CYBERBUDDY_ADMIN"]
}
}
2025-05-07 - Debug Logout avec Hermès¶
Etat des lieux : - le login fonctionne - je récupère email et groups (pas firstName et lastName mais ce n'est qu'un problème de réglage) - le logout provoque une erreur 500. A priori un problème de signature que ma lib décode avec SHA1 alors que c'est encodé avec SHA256.
Amine a testé avec un plugin permettant de voir les messages SAML.
Lors du logout, il y a bien un paramètre SigAlg dans l'URL.
J'ai modifié le paramètre retrieveParametersFromServer à true pour demander à la lib
de lire dans la querystring.
Je l'ai fait à l'arrache dans mon container et j'ai vérifié avec :
root@dc4e2ac02573:/var/www/html# php artisan tinker
Psy Shell v0.12.7 (PHP 8.3.20 — cli) by Justin Hileman
> config('saml2.retrieveParametersFromServer')
= true
[2025-05-07 10:06:45] prod.ERROR: saml2.error_detail {"uuid":"0d92ec95-af6a-48c4-b890-6f701a6f33fb","error":"Signature validation failed. Logout Response rejected"}
Hermès a détecté un problème avec le NameID dans la requête de logout :
Cywise envoi l'EntityID à la place du NameID reçu dans la phase de login ce qui n'est pas normal
et provoque une erreur sur le serveur Windows de l'ADFS d'Hermès.
J'ai vu dans le code de ma librairie que l'EntityID est envoyée si aucun NameID n'est fourni à la classe qui construit la requête de logout.
// vendor/onelogin/php-saml/src/Saml2/LogoutRequest.php line 94
if (!empty($nameId)) {
if (empty($nameIdFormat)
&& $spData['NameIDFormat'] != Constants::NAMEID_UNSPECIFIED) {
$nameIdFormat = $spData['NameIDFormat'];
}
} else {
$nameId = $idpData['entityId'];
$nameIdFormat = Constants::NAMEID_ENTITY;
}
Fin du meeting
J'installe le plugin "SAML Tracer" sur mon Firefox i.e. le même outil que celui que Nicolas a demandé à Amine d'installer sur son Chrome pendant le meeting. J'ai demandé l'export des logs de cet outil à Amine. Comme ça, je pourrais voir le détail des requêtes et des réponses SAML de ces tests de ce matin.
Je reproduis le problème du NameID en utilisant mon IdP Cywise1 sur Keycloak. La requête de logout contient l'entityID :
<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="ONELOGIN_cc4cc12edbe8e97a440089832082f1c43965836a"
Version="2.0"
IssueInstant="2025-05-07T10:45:48Z"
Destination="https://auth.computablefacts.com/realms/cywise1/protocol/saml">
<saml:Issuer>https://app.cywise.io/saml/48696b8c-225d-4b98-8102-0f8f9e7f9460/metadata</saml:Issuer>
<saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://auth.computablefacts.com/realms/cywise1</saml:NameID>
</samlp:LogoutRequest>
<samlp:LogoutResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
Destination="https://app.cywise.io/saml/48696b8c-225d-4b98-8102-0f8f9e7f9460/sls"
ID="ID_5b565ed3-43b1-40e0-ba63-300192a37ef6"
InResponseTo="ONELOGIN_cc4cc12edbe8e97a440089832082f1c43965836a"
IssueInstant="2025-05-07T10:45:48.500Z"
Version="2.0">
<Issuer>https://auth.computablefacts.com/realms/cywise1</Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status>
</samlp:LogoutResponse>
Suite pour Hermès¶
Les problèmes à résoudre : - l'erreur de signature à la réponse du logout - le NameID qui n'est pas correcte dans la requête du logout (lié au précédent ?) - récupérer le lastname de l'utilisateur - le groupe CYBERBUDDY_ADMIN ne semble pas avoir été donné à Amine
Ma priorité est le NameID.
Logout - envoyer le NameID reçu au login¶
Je reproduis aussi le problème avec mon Cywise local et l'IdP Keycloak cywise1.
<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="ONELOGIN_a0c12251f1173a67eeee348b7770b964033a361f"
Version="2.0"
IssueInstant="2025-05-07T14:02:09Z"
Destination="https://auth.computablefacts.com/realms/cywise1/protocol/saml">
<saml:Issuer>https://weasel-exotic-llama.ngrok-free.app/saml/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/metadata</saml:Issuer>
<saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://auth.computablefacts.com/realms/cywise1</saml:NameID>
</samlp:LogoutRequest>
Pas de problème dans la réponse de Keycloak :
<samlp:LogoutResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
Destination="https://weasel-exotic-llama.ngrok-free.app/saml/2f57bc95-d2fa-486f-8a88-5f6ded3d764f/sls"
ID="ID_11bc21d8-c1e5-4352-93f4-925593310cb2"
InResponseTo="ONELOGIN_a0c12251f1173a67eeee348b7770b964033a361f"
IssueInstant="2025-05-07T14:02:09.283Z"
Version="2.0">
<Issuer>https://auth.computablefacts.com/realms/cywise1</Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status>
</samlp:LogoutResponse>
Alors que le NameID reçu au login est :
<saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">G-ba95d406-2291-4372-abc2-b6ec359375d1</saml:NameID>
persistent, j'ai le même NameID à chaque login :
<saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">G-ba95d406-2291-4372-abc2-b6ec359375d1</saml:NameID>
J'ai réolu ce problème car je peux passer un NameID à ma requête de logout mais pour ça, il faut que je stocke le NameID reçu au moment du login. J'ai choisi de le stocker dans la session PHP avec :
// Keep NameID to send it back when user logout
session([
'saml2NameId' => $this->saml2User->getNameId(),
]);
$nameId = session('saml2NameId');
return redirect(URL::route('saml.logout', ['uuid' => $uuid, 'nameId' => $nameId]));
Ca fonctionne durant mes tests et ça fonctionne durant les tests de Amine.
Mais, malgré cela, il y a toujours
Récupération du nom¶
Dans le debug SAML envoyé par Amine, je vois les attributs suivants :
<AttributeStatement>
<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
<AttributeValue>amine.benayada@ext.hermes.com</AttributeValue>
</Attribute>
<Attribute Name="firstname">
<AttributeValue>Amine</AttributeValue>
</Attribute>
<Attribute Name="lastname">
<AttributeValue>BENAYADA</AttributeValue>
</Attribute>
<Attribute Name="http://schemas.xmlsoap.org/claims/Group">
<AttributeValue>SG-WORLD-APP-CU-CYBERBUDDY-ADMIN-SSO</AttributeValue>
</Attribute>
</AttributeStatement>
lastname comme Name pour aller chercher le nom.
Et je vois aussi que le groupe SG-WORLD-APP-CU-CYBERBUDDY-ADMIN-SSO n'est pas exactement
celui que j'attendais (SG-WORLD-CU-CYBERBUDDY-ADMIN-SSO). Je vais donc modifier
mes réglages pour ça aussi.
Réglages avant :
{
"claims": {
"email": {
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
},
"name": {
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
},
"role": {
"friendlyName": "group",
"name": "http://schemas.xmlsoap.org/claims/Group"
}
},
"roles": {
"default": ["App\\Models\\Role::CYBERBUDDY_ONLY"],
"idp_roles": ["SG-WORLD-CU-CYBERBUDDY-ADMIN-SSO"],
"SG-WORLD-CU-CYBERBUDDY-ADMIN-SSO_to_cywise": ["App\\Models\\Role::CYBERBUDDY_ADMIN"]
}
}
Réglages après :
{
"claims": {
"email": {
"name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
},
"name": {
"name": "lastname"
},
"role": {
"friendlyName": "group",
"name": "http://schemas.xmlsoap.org/claims/Group"
}
},
"roles": {
"default": ["App\\Models\\Role::CYBERBUDDY_ONLY"],
"idp_roles": ["SG-WORLD-APP-CU-CYBERBUDDY-ADMIN-SSO"],
"SG-WORLD-APP-CU-CYBERBUDDY-ADMIN-SSO_to_cywise": ["App\\Models\\Role::CYBERBUDDY_ADMIN"]
}
}
Changement fait le 2025-05-07 à 18h10. (j'avais mis firstname à la place de lastname et j'ai modifié à nouveau à 18h37)
Reste le problème de la signature invalide¶
Je veux calculer moi-même la signature d'une réponse logout pour savoir si elle est vraiment invalide ou non.
Je vais prendre un des messages des logs de Amine :
GET
SAMLResponse: fZJLi9swFIX/itFeliw/EgvHUGZKCaQz0Ayz6Ga4lq46BkcyvnIf/75yQhcDJaCNxDn3HH1SR3CZZn0KP8IavyHNwRNmx8cDe6udAdUOJUdbDbySiHyPsuRgmrpWAI0tK5a94kJj8AemcsmyI9GKR08RfExHUtVcprV7KRqtpK53eVO331n2iBRHD/HqfI9xJi0EzHNu/vwaCfMxiK2YkLZVaNqag2uAV3tT8WHfSt64nSygcWXpBkETsexhK76FrovXAWgk7eGCpKPR509fTzr10+Ym0qunGc3oRrSps/9375dwYM9Pn0/PX45Pb0aZQUGhGqtq54yr0FrYuxRZFYMt3ABQNa4oWPb7MnnSV5D34+clxGDCxPruCmq5We+bgAiXDRTrN1CJk0Obv+OShLkJFwHWkUian6NBEnFZKXbiNr/vbs97jhBX+rh7CBazV5hWvJ9PV7U+ryaNT6BF34mPU8X//lD/Fw==
RelayState: https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/logout
Signature: oRoGUu2Q0vF3FIhwqxV2CmCeSpMoGmsFW/7G+actoPvn4FHyfNRUHeFFbxmxkry6/WN++rQRymsxYAQ8iyn3bRs0nwaTeS349D2apTp2nGB7atwLtx4FOvL2tHHK6rqZmYTiBXGCpYV+83xh9wmiMHQqZ/j6Qa5IrOFF2gHvZNPVHkW5lHDDCBEJLpbkhqkBeeVKqyV/r/uX+qDMwNUr0/xGe5alEIUmXfHm8XGGaPJD4vJJZwdo8S7RDMscb0Qn6HPEzPpdv5A3s9bkaZJB2MB/R0EK6kaQfbR4aixN0HeyJM4/hMhMa6B3naQcJNtTD1tH6SvSAL16cSO7K0A9+Q==
SigAlg: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
Je décode (base64) la réponse SAML, je décompresse et je calcule le hash SHA256 :
> $decoded = base64_decode('fZJLi9swFIX/itFeliw/EgvHUGZKCaQz0Ayz6Ga4lq46BkcyvnIf/75yQhcDJaCNxDn3HH1SR3CZZn0KP8IavyHNwRNmx8cDe6udAdUOJUdbDbySiHyPsuRgmrpWAI0tK5a94kJj8AemcsmyI9GKR08RfExHUtVcprV7KRqtpK53eVO331n2iBRHD/HqfI9xJi0EzHNu/vwaCfMxiK2YkLZVaNqag2uAV3tT8WHfSt64nSygcWXpBkETsexhK76FrovXAWgk7eGCpKPR509fTzr10+Ym0qunGc3oRrSps/9375dwYM9Pn0/PX45Pb0aZQUGhGqtq54yr0FrYuxRZFYMt3ABQNa4oWPb7MnnSV5D34+clxGDCxPruCmq5We+bgAiXDRTrN1CJk0Obv+OShLkJFwHWkUian6NBEnFZKXbiNr/vbs97jhBX+rh7CBazV5hWvJ9PV7U+ryaNT6BF34mPU8X//lD/Fw==')
= b"}ÆKï█0\x14à èÐ^û,?\x12\vÃPfJ\tñ3ð\f│Þf©û«:\x06G2¥r\x1F ¥rB\x17\x03%áì─9¸\x1C}RGpÖf}\n?┬\x1A┐!═┴\x13fÃÃ\x03{½Ø\x01ı\x0E%G[\r╝Æê|Å▓õ`Ü║V\0ì-+û¢ÔBc\x07ªr╔▓#ÐèGO\x11|LGRı\ªÁ{)\x1A¡ñ«wySÀ▀Y÷ê\x14G\x0F±Û|Åq&-\x04╠sn■³\x1A\t¾1ê¡ÿÉÂUh┌ÜâkÇW{S±a▀JÌ©Ø,áqeÚ\x06A\x13▒ýa+¥à«ïÎ\x01h$ÝßéñúÐþO_O:§Ëµ&ʽº\x19═ÞF┤®│ w´ùp`¤OƒO¤_ÄOoFÖAAí\x1A½jþî½ðZÏ╗\x14Y\x15â-▄\0P5«(X÷¹2yÊWɸÒþ%─`┬─·¯\nj╣Y´øÇ\x08ù\r\x14Ù7PëôCø┐ÒÆä╣\t\x17\x01ÍæH܃úA\x12qY)vÔ6┐´n¤{Ä\x10W·©{\x08\x16│WÿV╝ƒOWÁ>»&ìOáE▀ëÅS┼ ■P \x17"
> $uncompressed = gzinflate($decoded)
= "<samlp:LogoutResponse ID="_5fca29b3-ed4b-40ee-8e03-ac6552aa6d34" Version="2.0" IssueInstant="2025-05-07T16:20:57.659Z" Destination="https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/sls" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo="ONELOGIN_c2cb2a126d25ffcf4edda8ff3341bd1fbaa46f11" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://fed.hermes.com/adfs/services/trust</Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>"
> $sha256hash = hash('sha256', $uncompressed)
= "522e5fc5ef97ee093d9817c04e6bf6e5d4fdfe9c640758f36bd36f506f6ece13"
Je dois maintenant retrouver ce hash dans la signature. Je mets la signature dans un fichier signature.base64 :
oRoGUu2Q0vF3FIhwqxV2CmCeSpMoGmsFW/7G+actoPvn4FHyfNRUHeFFbxmxkry6/WN++rQRymsxYAQ8iyn3bRs0nwaTeS349D2apTp2nGB7atwLtx4FOvL2tHHK6rqZmYTiBXGCpYV+83xh9wmiMHQqZ/j6Qa5IrOFF2gHvZNPVHkW5lHDDCBEJLpbkhqkBeeVKqyV/r/uX+qDMwNUr0/xGe5alEIUmXfHm8XGGaPJD4vJJZwdo8S7RDMscb0Qn6HPEzPpdv5A3s9bkaZJB2MB/R0EK6kaQfbR4aixN0HeyJM4/hMhMa6B3naQcJNtTD1tH6SvSAL16cSO7K0A9+Q==
base64 -d signature.base64 > signature.der
openssl pkeyutl -verify -in signature.der -pubin -inkey signature_cle_publique.pem -pkeyopt digest:sha256
Bon, j'arrête là. J'ai une signature invalide mais j'ai aussi une signature invalide pour un autre message SAML qui n'a pas provoqué d'erreur dans les logs (le message d'envoi des attributs). Le plus probable est que ma méthode manuelle n'est pas correcte...
J'ai décidé de changer 2 options dans config/saml2.php :
/*
|--------------------------------------------------------------------------
| Signature validation
|--------------------------------------------------------------------------
|
| Set to true if you want to use parameters from $_SERVER to validate the signature.
|
*/
'retrieveParametersFromServer' => true,
'security' => [
/*
|--------------------------------------------------------------------------
| Lowercase Urlencoding.
|--------------------------------------------------------------------------
|
| ADFS URL-Encodes SAML data as lowercase, and the toolkit by default uses
| uppercase.
|
| Turn it True for ADFS compatibility on signature verification
|
*/
'lowercaseUrlencoding' => true,
],
Je pense que c'est la première option qui a résolu le problème car la deuxième concerne l'encodage d'un message partant de Cywise pour aller vers l'IdP.
Ajout contraintes sur le mot de passe¶
Dans la page register, ajouter les infos avec les contraintes sur le mot de passe : minimum 12 caractères, au moins un chiffre, au moins une lettre minuscule, au moins une lettre majuscule, pas de caractères autres.
J'ai créé une classe PasswordRequirements qui donne, pour chaque règle standard et chaque règle custom (nous en avons créé 5), le texte du requirement et la condition javascript qui correspond. Après modification du template Blade de la page register, cela permet d'afficher les contraintes à l'écran puis de les mettre en rouge ou en vert en fonction du respect de chaque contraintes avec le mot de passe saisi.
Il y a 2 pages où l'utilisateur peut faire une demande de changement de mot de passe :
- depuis la phase de login avec "Mot de passe oublié ?" => /password/reset
- une fois connecté avec le menu "Réinitialisation du mot de passe" => /reset-password
NOTA : pas de page "Profil" où il pourrait modifier son mot de passe
Dans les 2 cas, cela doit envoyer un mail avec un lien vers la page de reset /password/reset/{id}?email={email}.
Pour la page /password/reset, le mail est envoyé mais pas pour la page /reset-password.
J'ai compris pourquoi le mail ne partait pas pour la page /reset-password : un middleware
protège la méthode du controller et l'utilisateur ne doit pas être connecté pour que l'email
parte.
J'ai donc modifié le comportement du menu "Réinitialisation du mot de passe" : je déconnecte
l'utilisateur puis je le redirige vers la page de reset du mot de passe.
J'ajoute une invitation ce qui me permet de récupérer un lien du type :
/pub/invitation/{id}. C'est la librairie Konekt (konekt/appshell) qui gère cette fonctionnalité.
Le template Blade de la page se trouve dans resources/views/vendor/appshell/public-invitation/show.blade.php.
A mettre aussi dans la page invitation.
Logo sur la page invitation¶
C'est le logo de Towerify qui s'affiche et pas celui de Cywise. Pourquoi ? Le titre n'est pas correct dans la barre de titre. Pourquoi ?
J'ai testé mais je n'ai pas de logo et le titre prend bien en compte le réglage
de mon .env pour la clé APP_NAME.
Il est possible que le bug ait été dû à l'envoi du .env local dans l'image
Docker que j'ai corrigé récemment...
Hermès - Problème avec Microsoft Edge¶
Amine est revenu vers moi car le SSO ne fonctionne pas avec Microsoft Edge.
Logs Microsoft Edge envoyés par Amine
[
{
"guid": "659cc3a9-ac62-52b4-48e0-4d27484c742b",
"requestId": "38364",
"timestamp": "2025-05-12T14:41:31.192Z",
"url": "https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/sls",
"httpMethod": "GET",
"cookieRequest": [
"XSRF-TOKEN=eyJpdiI6ImxCZ0wzcGpEd0N1cXg1dDN5ek5SeHc9PSIsInZhbHVlIjoia2lNUHdmT2FZTGpMK3ByS3dKYnA2ZGY5MG9BNWltSFJQanJwTmhDRzhaSzlScjJ3UGZXMEpqRGRqWmMvR0VSWFFGU2NHTXN6eEtkcGk2dFkra1BjTVhoZUErK0ZaM1pHeFIwbGMwS3Znc1VRSDBYSTRzMDhwRks1MlZKSjhiZVUiLCJtYWMiOiJkNmY3NTM1MzMzNzE5MmNjZmYxOWNmZWE1YWI3MGZjMzU0MDEzMzBiMTRlMzIyYjFjMTg4MmE0YWIxYTQ5ODNkIiwidGFnIjoiIn0%3D; cywise_session=eyJpdiI6IlRxVnJ5TnNiNk5jc1AzMEdrbGZxVGc9PSIsInZhbHVlIjoiNTMyTUcyMkVMb3dkbUZzb3NiUzc2UGxVZFFoSUNwQ0dNZWNqS2RhSjRETERNa3R5eGJsZENZY3d4VUVvdDBSZ2VENjk5TWFPbGZ1OTRjZ2hxMUVZeFRRUk5taDJHMzNHM2xEK2Uyak5QQVVsM0RYZFJLVXhYOEovckpPRnNRaDQiLCJtYWMiOiJhMjQ0OTUxM2Q3N2ZhODQ1MDEwNWRkNmQ2NDIyNTQwZDJkNzdhNWJjNjZkNTdjOTliOGNkNTk2YjVmZTU2ZGM4IiwidGFnIjoiIn0%3D"
],
"cookieResponse": [
"cywise_session=eyJpdiI6ImZPcW4wczBobnhhZDRlUVBHNnJjOFE9PSIsInZhbHVlIjoiVVpBZ2JMY3hUaDNnRmlOS2lTV05obXFaZXlPU0NlQk41Nkt2RDF0bnNyS0V6MlJrVkFRRG5DRkNJbkxsVjVsQy8yd2VoTk10Y1hnck14OTdTU09VTVRpYjV2Q1A3ZitWRzJsUEovWk9XUFFCeDhwRktjbkVwVzBEMzVPbTdKWVMiLCJtYWMiOiI0Nzk2MDkxOWI1NThiMzUwZGVkYzUyMmQ0NDg1NjlkZTJkMDIyNDc3M2ViNWQ0NDE4NTI2ZGMyNTZiMmIzOGVhIiwidGFnIjoiIn0%3D; expires=Mon, 12 May 2025 16:41:31 GMT; Max-Age=7200; path=/; domain=cywise.io; httponly"
],
"protocol": "SAML",
"messageType": "Response",
"messageVerb": "logoutresponse",
"message": "<samlp:LogoutResponse ID=\"_f9fef065-19e1-44ed-b6f4-b90df6f3736e\" Version=\"2.0\" IssueInstant=\"2025-05-12T14:41:31.162Z\" Destination=\"https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/sls\" Consent=\"urn:oasis:names:tc:SAML:2.0:consent:unspecified\" InResponseTo=\"ONELOGIN_ce8a59b9b3389335b159355e372ead1bffe6416c\" xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"><Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://fed.hermes.com/adfs/services/trust</Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Requester\" /></samlp:Status></samlp:LogoutResponse>",
"state": "https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/logout",
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"signature": "LGgjfDFT9jkkTP7SApdrDsC+JniBH14V96HnaEJ2nvMDHmuuuOWZbEQ9NbeNhooJMT5O48Pe3yc6CKfb+GSBFVR/+HeWDX2ItIXQcxC0tQRlPcG+iX+K2rhENR8RSzGoRDNp0KhqJMHry6JVxN/Z3hennMRGz/WENoQOiyDHBsLgnwCR9qm7oy8SGWt0aTQbhtV5/mmU7Aq3UihWNDKdTuAdIqW+ERhxU/THh5HbUrAm/lbXuMxxDO5XHgiMVQ0ghOVEr3DJck2iPQM8Vt1+dE0kNN9oYhASjgJOPsX+ynJojEs80E9KZjHtPoeYm7j63ufDx3A3KNWHizMips2Izw=="
},
{
"guid": "86b8716a-4007-35f2-6f19-a44fa30dc6bd",
"requestId": "38364",
"timestamp": "2025-05-12T14:41:31.158Z",
"url": "https://fed.hermes.com/adfs/ls/",
"httpMethod": "GET",
"cookieRequest": [
"SamlLogout=aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZm1ldGFkYXRhP09ORUxPR0lOX2QyZjVmZTAwMGVhMjdmOWE2ZjNjZmU0ZTZkZWM2OGU5NDY2ZTNiNGE/aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZmxvZ291dD91cm4lM2FhdXRoMCUzYWN5YmVydmFkaXMtcHJvZCUzYUdTUTdNSTZRUVEyMFlTTVhaOU41JkZhbHNlJmFtaW5lLmJlbmF5YWRhJTQwZXh0Lmhlcm1lcy5jb20mJiYmJl8xYThhYmRmYy03MjU5LTRhZTYtOGNhYS0zYzU5NGFjY2RmY2EmXzc1YWUzMjBjLTM0MzYtNDI4Ny1iNjk2LTU4MTFmM2JkMTcwYSZfZDlmNzg3YmMtODEzMC00OTRlLTgwZGYtYjFkMTFlYjE4MGUwJl9mZTgwNGVlMS05MjNjLTRhMDMtOWVmNy1hNmJhNmVlYWJmNmQmX2JjOWE0YWNmLWU3NDAtNDhiMC04YThlLTYxZjhhMWU4NmJhYSZfMzM5YjAxYzUtZDliYy00NGM0LTllNGEtNTc3YjNiYTAzZGRhJl9mZjg0NGQ0OC02ZTZkLTRhZDEtYjE5MC00YmU0MmI2ZGEyYWU/XzI4MDhhODQwLTM1NTMtNGQwZS1iMTEyLTA4ZjIyMjZlN2M1OT91cm4lM2FvYXNpcyUzYW5hbWVzJTNhdGMlM2FTQU1MJTNhMi4wJTNhc3RhdHVzJTNhU3VjY2Vzcw==; MSISSignoutProtocol=U2FtbA==; MSISAuth=AAEAAGfufZvnuPj4k/3ZlXVbEOSU4pJrkOii3+sEPKbSX11ESqhvljM620lxmq+vh433bWzQVAPHVRevwWCr3oZ+oUH0fuXtUUaCSfh2KBdkFu61u2DwNpVkVJD1kKhGArNbZeEF9Mga5DTIueucGB4pxZJjk60yNuZaXrz2GUtKRP2qaeJWtqk1DuzvKYB5USGHEZxfjQXtKCeWVIjKQ7rFqiQ0KlA+W4Fsf6EjkeHN3VjOgjo82tQSSkes554gxR84OLUHh1+E/F5h6cWqmsOcig4KSA52dkiBKbBhW7nnY4pAAfesCmtfdJ4SpYtvTEui9Whc3itePc9bH6ElFX3UGpbKcgVH7AbwPjPPSbOEvJpCD29woUL/ENs+Bll6HG+/7QABAACOc5XE1qq8pA0U4yw6a6hjoQ6sEbd3VvIf/jzwGO5NUrlmeq/0uH9B8DTPUnvc9eeWHod2NRyx6L+/GXO/G9q2RKd30hmhws8ND8ujuHFrjC8U6TK+bBuTIr/e79CwGwoz6/fkcw/C/4gf3V26SWBYvLcDwBy1C2hlhzXw6H23di8Ke9WSYCLpC3Uevg83xuCl6BxKOwJ/zxOMItUN+gnZdodMykw8S9TLv9V0Et7eLoy73aHNZEps/of/qEQTo93+wV2m9tvQijIOwctpWcwIAZxDXBSBXViNB4N2ErItEOnWfcHtYYnXi4hgpJR9wN51HHbrYpSVN2QI9DkZ7dFcoAMAAPgZKLQp9Mnr8H2XmP5fBYQPEEduT1JmRpH24COGE7AgenYikQFGZi7oBCugmcfqCTzKZW6I+ZmM0Pkaebg/eRg3SdJY3E0GXtJ0LL2vpf2+Kr31bgNP2u5vCyAPvBitUTMgQa2rcAXesCvvvXvzejn0+oam5Be3/kwIwYv5B3H6wvGFBOpQbUIR8z1oTyYitCjqQgDA3d4J6vPgmj9lnK9WlQ/TbeWB650hX6oMq/ILdRscNJPJfbpakFXupdoriLHo5RIjbz8TT5+coIozEsUOEvpWtX3LDclHZMCLL1MlBPPUBi5LIUAvakXXbMXzWO0FjuaBb3xC58tAZv72E3ZmdTrcVK70/mAqL2HJVsi4Gzi35G32FLAkQuFsfvh/xNYlgDuZabwpAuHB/KevWaY76M3v7CnAuvRou9ekYBlTP6Nj5JOgg2O+U8OgBVA1ZxgExDl/v0G118k9GK2TjAWsbd9tZjP2iRcLSBWKleJdv0Q+CawWm/J61egi3d03ES2JbYu6biNu/XKxQ/rsGvx0zjZQpP9FLF+eFnAWbXXN3Icsuzjte9f37wG5Tp6hbwdZUDndkZDtXsmn+N1SZJYZOUuObb7MXgZnlEGpu8K6ruJUOGgwXdQ9EcFjuZpXzJF4AVX6l2aQumDF/nnMWZvzb66rpJ3kqOKI3CDm/YN395kLnbdPKx+755yYsp8iJirD7EPY6jvCWKTeiBIROIwZOQA1hu1ww9brk9queSsEZCPkQGjUOuJxWqDop/Stewq5R6i8Q6O6HJiAVfWAwXwcCOyYQoJT34PjNreVE3qbT0zOGTTuxRhwnjcaDCivD42fWC5CtrDbXuQeV+ex+sdFOvCqj02YNvVb7dLLrYFI+9UURyp3fpr/syxlftNL/CqOunCH0h9U3OnmVzaOzMu24F286sYaEsDv+nYXQHrRboGhoywBvnoe73xNfPjwWBMlU6NJ8Otl4qRpFxeyY4LB53Giw5j338AsvxXo7L3lbjP+YCdD9Ucv+P/IPT2wfr+e+KtAzcwKC5F89JLxqu2hc8mLz0SZZQaGwDT3afVqsE1jSDiKsb+G5J6HBNKZwDJTLk9RF6YCotBIEVaq6EL+m2v+bgXALLC3ZKWbxqE7YSkeDf5W5Frw4RtjjPo8CLzZUqOyChO7vDyp1s+DPz72A5uEYOnbhQZcns9KbkL62L4KSqh/Lm3JzDVGDD4jn6dhGlBu5/VajT+Kn43owCY=; SamlSession=aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZm1ldGFkYXRhJkZhbHNlJmFtaW5lLmJlbmF5YWRhJTQwZXh0Lmhlcm1lcy5jb20mdXJuJTNhb2FzaXMlM2FuYW1lcyUzYXRjJTNhU0FNTCUzYTIuMCUzYW5hbWVpZC1mb3JtYXQlM2FwZXJzaXN0ZW50JiYmJl8yMjM5ZDNhNi04OTY5LTQzZjYtODVjNi05NDIyMzIwZTI3N2UmXzgxM2E0MjFiLTUwNjEtNGViNy04ZjYyLTI3ZTUyZmIxMmNlZCZfZTA5MTZiMjgtNWI0NC00ZGEyLTkyNTktZTllMWQ5NWE3NmI2; MSISAuthenticated=NS8xMi8yMDI1IDI6NDE6MjIgUE0=; MSISLoopDetectionCookie=MjAyNS0wNS0xMjoxNDo0MToyMlpcMQ==; rskxRunCookie=0; rCookie=a9habht7tm711246vvsuosqm8h42wgg; lastRskxRun=1742465769640; datadome=d234~ExLTPjxG2_MIfDRxaJs~Zk~oNvIG8vK3MgnIRN4tBdFp8jj3Ov8BTHQd0s1LVwaZlphOsA~ZROskHMF3U7Q0nNww~FYpWBCdZX80x5wSSZ1MgYmtKC45hYcHGI0"
],
"cookieResponse": [
"MSISAuth=; expires=Sun, 11 May 2025 14:41:31 GMT; path=/adfs; Secure; SameSite=None",
"MSISAuthenticated=; expires=Sun, 11 May 2025 14:41:31 GMT; path=/adfs; Secure; SameSite=None",
"SamlSession=aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZm1ldGFkYXRhJkZhbHNlJmFtaW5lLmJlbmF5YWRhJTQwZXh0Lmhlcm1lcy5jb20mdXJuJTNhb2FzaXMlM2FuYW1lcyUzYXRjJTNhU0FNTCUzYTIuMCUzYW5hbWVpZC1mb3JtYXQlM2FwZXJzaXN0ZW50JiYmJl8yMjM5ZDNhNi04OTY5LTQzZjYtODVjNi05NDIyMzIwZTI3N2UmXzgxM2E0MjFiLTUwNjEtNGViNy04ZjYyLTI3ZTUyZmIxMmNlZCZfZTA5MTZiMjgtNWI0NC00ZGEyLTkyNTktZTllMWQ5NWE3NmI2; path=/adfs; HttpOnly; Secure; SameSite=None",
"SamlLogout=aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZm1ldGFkYXRhP09ORUxPR0lOX2QyZjVmZTAwMGVhMjdmOWE2ZjNjZmU0ZTZkZWM2OGU5NDY2ZTNiNGE/aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZmxvZ291dD91cm4lM2FhdXRoMCUzYWN5YmVydmFkaXMtcHJvZCUzYUdTUTdNSTZRUVEyMFlTTVhaOU41JkZhbHNlJmFtaW5lLmJlbmF5YWRhJTQwZXh0Lmhlcm1lcy5jb20mJiYmJl8xYThhYmRmYy03MjU5LTRhZTYtOGNhYS0zYzU5NGFjY2RmY2EmXzc1YWUzMjBjLTM0MzYtNDI4Ny1iNjk2LTU4MTFmM2JkMTcwYSZfZDlmNzg3YmMtODEzMC00OTRlLTgwZGYtYjFkMTFlYjE4MGUwJl9mZTgwNGVlMS05MjNjLTRhMDMtOWVmNy1hNmJhNmVlYWJmNmQmX2JjOWE0YWNmLWU3NDAtNDhiMC04YThlLTYxZjhhMWU4NmJhYSZfMzM5YjAxYzUtZDliYy00NGM0LTllNGEtNTc3YjNiYTAzZGRhJl9mZjg0NGQ0OC02ZTZkLTRhZDEtYjE5MC00YmU0MmI2ZGEyYWU/XzI4MDhhODQwLTM1NTMtNGQwZS1iMTEyLTA4ZjIyMjZlN2M1OT91cm4lM2FvYXNpcyUzYW5hbWVzJTNhdGMlM2FTQU1MJTNhMi4wJTNhc3RhdHVzJTNhU3VjY2Vzcw==; expires=Mon, 12 May 2025 14:51:31 GMT; path=/adfs; HttpOnly; Secure; SameSite=None"
],
"protocol": "SAML",
"messageType": "Request",
"messageVerb": "logoutrequest",
"message": "<samlp:LogoutRequest\n xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"\n xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\"\n ID=\"ONELOGIN_ce8a59b9b3389335b159355e372ead1bffe6416c\"\n Version=\"2.0\"\n IssueInstant=\"2025-05-12T14:41:31Z\"\n Destination=\"https://fed.hermes.com/adfs/ls/\">\n <saml:Issuer>https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/metadata</saml:Issuer>\n <saml:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">amine.benayada@ext.hermes.com</saml:NameID>\n \n</samlp:LogoutRequest>",
"state": "https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/logout",
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"signature": "AssFsKX6HQNKq1xojquFDG2r7r0sntlLXzA+fw+PWIlb7qonRxm/3PypUha1F6i28/9N3NCUHd4J4BE3x7xgVBWHebYtxum+xfs4Jt7po4Wd/LgcG5DUTU4C6BFIWUjT9z27MZMQSgOaMFrBebJ5trKXrIKMkxMsdnGhxHLX5KRNJoWRuW9D1P3+5z2RnqvhKTnPsfR1Z2VAXfDnkTjZPClRkdzAAznUWr7EQI+22xKWEMm1/SLnZJEeSmtYGTSsGRNwiUBm5FTphvpPbOHKI2RJSMd95LFqFqYbguuk1WBD+NGJPc5d89qvwB4xObaTVU3fQptrkXge3D2ihfw56w=="
},
{
"guid": "bbb9ae26-231c-b9e3-a4d3-b3e8b1bf0b58",
"requestId": "38310",
"timestamp": "2025-05-12T14:41:22.622Z",
"url": "https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/acs",
"httpMethod": "POST",
"cookieRequest": [
"XSRF-TOKEN=eyJpdiI6IkpIRDhrVWpGYTNlVlRHRWZLK3ZPSVE9PSIsInZhbHVlIjoiN3Bqb0pLZWVUb1VpTFhya3d1NEJoeitvNVVNQXZVMlIwb0tGZUNHVW11UUlmL3NpYStRWnFVV1c5eFYrbjVYdXdIam0wb0NJUHZMZ21BMDc5bGp4MDNlZ28zUVdCRkQ1RHRUUVBpbGlPV29Bc1Q3UTVmT1hSUHk3YWZWdkdTL1YiLCJtYWMiOiI5NjAxMjhhNDk0MjU2YmQ0ZDg4MWRkZjY4MTczZGFhODE5MmYxMWQ5OWU5N2UyMzUzZDAxZDg0MDNiM2ZiYWY1IiwidGFnIjoiIn0%3D; cywise_session=eyJpdiI6ImRPdjRXc2RLd2JFRm1BbGxJSWJrcGc9PSIsInZhbHVlIjoiaXFrcDNocGdUNEFYK1J3STZGZDlKNVpiQU5jWmpsWDZoLzRrS3JMWHF2MUZTa2xEeFRvNjFHYkVGd1h1bzV0YTlyVnBnZlQ1MkRXbC9Qd01Ca0VlS3poWm9hUFdjU2hsQXViSWVnSGFKTXR5bkJ3c3pJcmRLTWZlSGFDYWo4ZGwiLCJtYWMiOiIwNTBhM2JmMDg2MjQ0ODcxMTZmYzIxZmI2ZTk3MjE2YzRmYzdkNmY5YzRkZjJjN2I3ZWQ1YTc4OGUzMmRmNDg2IiwidGFnIjoiIn0%3D"
],
"cookieResponse": [
"cywise_session=eyJpdiI6ImFBTFNsalIxdHZxUXVaY29kc3QrQVE9PSIsInZhbHVlIjoiNFFwT21hUnB4UVgrYUxBSEp5MytSTFdtSVI0bHN6VU1xQlpYYkFmd1NTRG5vNnIxY3o1RmxXK3R5TU5qZ09FSEdORTloU0lJbkdINnI2NExMb0tBWjc0bzVjV2pLQUdhcm1kamxCTGtud09DbFRNZndFaUg4YUxITjJyd1RvOGYiLCJtYWMiOiJkNjE5MDhmNzE3YWQyNzZlZDBhYTg1ZWIyMTcxOGExMDc2MzEzOTc2NTZiMThiMTJiMTM2MWQ3YmY1ZTU4MTI1IiwidGFnIjoiIn0%3D; expires=Mon, 12 May 2025 16:41:22 GMT; Max-Age=7200; path=/; domain=cywise.io; httponly"
],
"protocol": "SAML",
"messageType": "Response",
"messageVerb": "response",
"message": "<samlp:Response ID=\"_f7728bbc-bc1d-4464-965f-f1505e964c2c\" Version=\"2.0\" IssueInstant=\"2025-05-12T14:41:22.569Z\" Destination=\"https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/acs\" Consent=\"urn:oasis:names:tc:SAML:2.0:consent:unspecified\" InResponseTo=\"ONELOGIN_7bc388c175dffb87f691ba83f313492e94be52f8\" xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"><Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://fed.hermes.com/adfs/services/trust</Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\" /></samlp:Status><Assertion ID=\"_e0916b28-5b44-4da2-9259-e9e1d95a76b6\" IssueInstant=\"2025-05-12T14:41:22.569Z\" Version=\"2.0\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"><Issuer>http://fed.hermes.com/adfs/services/trust</Issuer><ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\" /><ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\" /><ds:Reference URI=\"#_e0916b28-5b44-4da2-9259-e9e1d95a76b6\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\" /><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\" /></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\" /><ds:DigestValue>aVqkQJUGU1ACSVXRRYdb8j82RtIY33pnGbx3KAKZZ/0=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>ckU0c8pK+7aG7K8DDrRExCPRSQoP8Uc7Akx0ZPUogZ4USRtdCkuzmP7J6iwTPJ5bc3da4i3k1KOxX8PjVdSa/afl37jbcpCMe1OV3M706g9JDIWCeITGgBnKukCH1BjQIdcZsjnGrFqa3jyVabWYyJvgGHridIVKVkTINcJLpu8YHwmTPTF8uRqPJ0aG1dxgkvGQoShFQ9W7eHCZ4Tur0fc54O6yPfksbytORTAWBMXhYqWuK5BIczPVAiRy9Pmey3uw9/58/0m4hcCCvdQhHub+hmv36eSIHYuh8j6xpsuKYCIGxdJFbY08wnNtneH6TGQKcmg5mSl1LcvrcgcsdA==</ds:SignatureValue><KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><ds:X509Data><ds:X509Certificate>MIIC2DCCAcCgAwIBAgIQJh/EANNXJb5LBvCDXcXOwzANBgkqhkiG9w0BAQsFADAoMSYwJAYDVQQDEx1BREZTIFNpZ25pbmcgLSBmZWQuaGVybWVzLmNvbTAeFw0yMzA5MjcwOTM2MjBaFw0yODA5MjcwOTM2MjBaMCgxJjAkBgNVBAMTHUFERlMgU2lnbmluZyAtIGZlZC5oZXJtZXMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwZT8DsXjzQO02YpXZ99PmIXanvWp+dDsP0SQmMJMHoKgsmj5gDlYohkCXlt2K9hYVa84drBYxLZLmY3MotGVoQhwDV/vHIMJOzys2IKMBtHjsPPYVML9tFJRZPtMIxH3Dzp1sPDiDmY67IZhgcEiEzlIuAcsToTUNSZjR+1kd+4j5nuf5NBtV0dc499yOs3s8q4rk4R0fd2BwC2QawBvu2iTT8xsBX8i4N3N3kLhJeSSgHUIg7QZNPNmo+cs6HfGehGhn2jcfAotYLRZfV8V15RK5DIXgruzqGVOgE91twSQKGepFxVrEdgGwszyzEyzaqBkcU3nTl2SEQ3yX7B9uQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBPfTe/w4or4QV0ffysfXBiuisGu7ThZRU6stJ+pO1Y6aYXyM2Xe4PmTKwY0evkSJmLrNWlqRpoNEcR3H3fCDHaMKVWV+/4QJ3W5Xd1bPwX0qYYyHJxn4TkH2q0cmidjYWP1KxL/57uCbzpdwtKBrA/dOi+MlFJ9O1GhAuKk/BC22iXNoROQ6nfL8tJdvWMx2I7HI5CrDZzoYPtH+liJP05rfpSf31FnkKIedPb6FHiSgTkM44XUB9nqml0DPLWvxTXJ4XYmHGmCI9REOTY7YUy7U0wevV7w9elXcr1T3NSDj2y8/avZVhRgjsKsq2DWwewGT5PUazU7MIz6Yq806vF</ds:X509Certificate></ds:X509Data></KeyInfo></ds:Signature><Subject><NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">amine.benayada@ext.hermes.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"ONELOGIN_7bc388c175dffb87f691ba83f313492e94be52f8\" NotOnOrAfter=\"2025-05-12T14:46:22.569Z\" Recipient=\"https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/acs\" /></SubjectConfirmation></Subject><Conditions NotBefore=\"2025-05-12T14:41:22.553Z\" NotOnOrAfter=\"2025-05-12T15:41:22.553Z\"><AudienceRestriction><Audience>https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/metadata</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress\"><AttributeValue>amine.benayada@ext.hermes.com</AttributeValue></Attribute><Attribute Name=\"firstname\"><AttributeValue>Amine</AttributeValue></Attribute><Attribute Name=\"lastname\"><AttributeValue>BENAYADA</AttributeValue></Attribute><Attribute Name=\"http://schemas.xmlsoap.org/claims/Group\"><AttributeValue>SG-WORLD-APP-CU-CYBERBUDDY-ADMIN-SSO</AttributeValue></Attribute></AttributeStatement><AuthnStatement AuthnInstant=\"2025-05-12T14:41:22.522Z\" SessionIndex=\"_e0916b28-5b44-4da2-9259-e9e1d95a76b6\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef></AuthnContext></AuthnStatement></Assertion></samlp:Response>",
"state": "https://app.cywise.io/home",
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"signature": "ckU0c8pK+7aG7K8DDrRExCPRSQoP8Uc7Akx0ZPUogZ4USRtdCkuzmP7J6iwTPJ5bc3da4i3k1KOxX8PjVdSa/afl37jbcpCMe1OV3M706g9JDIWCeITGgBnKukCH1BjQIdcZsjnGrFqa3jyVabWYyJvgGHridIVKVkTINcJLpu8YHwmTPTF8uRqPJ0aG1dxgkvGQoShFQ9W7eHCZ4Tur0fc54O6yPfksbytORTAWBMXhYqWuK5BIczPVAiRy9Pmey3uw9/58/0m4hcCCvdQhHub+hmv36eSIHYuh8j6xpsuKYCIGxdJFbY08wnNtneH6TGQKcmg5mSl1LcvrcgcsdA=="
},
{
"guid": "92a0e45f-df2c-3bd9-98ca-d7028ca2e34f",
"requestId": "38308",
"timestamp": "2025-05-12T14:41:22.552Z",
"url": "https://fed.hermes.com/adfs/ls/",
"httpMethod": "GET",
"cookieRequest": [
"SamlLogout=aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZm1ldGFkYXRhP09ORUxPR0lOX2QyZjVmZTAwMGVhMjdmOWE2ZjNjZmU0ZTZkZWM2OGU5NDY2ZTNiNGE/aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZmxvZ291dD91cm4lM2FhdXRoMCUzYWN5YmVydmFkaXMtcHJvZCUzYUdTUTdNSTZRUVEyMFlTTVhaOU41JkZhbHNlJmFtaW5lLmJlbmF5YWRhJTQwZXh0Lmhlcm1lcy5jb20mJiYmJl8xYThhYmRmYy03MjU5LTRhZTYtOGNhYS0zYzU5NGFjY2RmY2EmXzc1YWUzMjBjLTM0MzYtNDI4Ny1iNjk2LTU4MTFmM2JkMTcwYSZfZDlmNzg3YmMtODEzMC00OTRlLTgwZGYtYjFkMTFlYjE4MGUwJl9mZTgwNGVlMS05MjNjLTRhMDMtOWVmNy1hNmJhNmVlYWJmNmQmX2JjOWE0YWNmLWU3NDAtNDhiMC04YThlLTYxZjhhMWU4NmJhYSZfMzM5YjAxYzUtZDliYy00NGM0LTllNGEtNTc3YjNiYTAzZGRhJl9mZjg0NGQ0OC02ZTZkLTRhZDEtYjE5MC00YmU0MmI2ZGEyYWU/XzI4MDhhODQwLTM1NTMtNGQwZS1iMTEyLTA4ZjIyMjZlN2M1OT91cm4lM2FvYXNpcyUzYW5hbWVzJTNhdGMlM2FTQU1MJTNhMi4wJTNhc3RhdHVzJTNhU3VjY2Vzcw==; MSISSignoutProtocol=U2FtbA==; SamlSession=aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZm1ldGFkYXRhJkZhbHNlJmFtaW5lLmJlbmF5YWRhJTQwZXh0Lmhlcm1lcy5jb20mdXJuJTNhb2FzaXMlM2FuYW1lcyUzYXRjJTNhU0FNTCUzYTIuMCUzYW5hbWVpZC1mb3JtYXQlM2FwZXJzaXN0ZW50JiYmJl8yMjM5ZDNhNi04OTY5LTQzZjYtODVjNi05NDIyMzIwZTI3N2UmXzgxM2E0MjFiLTUwNjEtNGViNy04ZjYyLTI3ZTUyZmIxMmNlZA==; MSISLoopDetectionCookie=MjAyNS0wNS0xMjoxNDozOTo0NVpcMQ==; MSISAuth=AAEAAGGMr+TFC7DD35hqDaD89eMRfd+/Ln1A43nD26Ep2Ps7U+HAdo8eBvJb7laTKc9v844+XmISZ9H8rG61cg1MP3iwqcHsSoj8C5cgWomcR3ntLNpQIBa1Bf56Ze/piMhFqEqfW7JtKdzY9tuwy4nFJtg96psu+zhoO5wQpPyOZx1dscsb12SNi23jMpGBxldubpeZVp/BA0t1easEE4xnDlWCDED20Xv1UzAPCfglLb3E6RirpXkTlVgtgaiAIJUsyuWRdlI54oW5Pi+QtkuHkh0nlUNzNP58XZICyY7HJhQ67WGkLwhKGVXHWDTmI3Y/rRXiYiDfgzejn2nithlFb/XKcgVH7AbwPjPPSbOEvJpCD29woUL/ENs+Bll6HG+/7QABAAAiktI/gi3OKh1rZUGpX4whnMl1Y6GFt+qh3iwG20a6vLVbylmk1S9t7ujcbL62FwepOMVfz7MMsDIxdLHgPG3A0BEvO/BZnjj1M12wG/po1XmAXRBdIzIpoCwv1dkJKRmqRYjn0TYy1vBeAdbKOsSWTc7pxSv1Y32iVImMvX3gXIK5CaUOP2hrXNiiFJ1BotrNMoFl9C2rFMpvih8+h/Amlw24cr3Bes28b9slKFz0foQ5Ch58KD2BHLv9o9dyWHf8MQaBzMEVu1G8GQ/alLjbaTJh/DrJf2TaSU1s29pFx5Sd0PApvH2XY5D7xW/CMF0RPArDmuB5fM24pQwAb3OqoAMAAAgjDon8s5lJVmhviQYY3bK6WtyX6eGM8puDxcwkIBNbONGRmCaw8npqPJxmstf/ue/IuD/ydFvr+4v1YNVM0NjoOq6y9mQ6B5Sf+MXt5vDA20/KB0U6UoRae4R5yrb7mkAksOEp/NpT0GhhzfEiayYYH1OfOmCBrv+kqBivD6tZz7HooqLcY3pRs3LvzSNJU64jyX8KTd4EJ2q9DhPW4tmddxptSE49BYk6UxaM3SxJ3NP8vhs3oMb/1px8/6daRWxYLpTWVPyk3n6qgdoUUEi46JY+7JVK826/ropV59xuZkDK6lm85auEvSkGsUCEorrOndbkh9NDvFc3n62T243TGl7YnENcT/93rnrd1uaVlaWhla87kN1VtWL17mq+AJU71TzxtGO2Shky7cXF6L45qPdNQahT9KxB4abc4T4Xkiin82tdMTSLu4BYA8fWKjzDsoAtKdTnycp+/qB2hSlCj6nzTjaPQYcJowtenUhzimXRCvw2NQ7pQ6RjQfoM9v5ZvdFN8kt014TIHV3bb8d7WXS8GLjbkMShT/1PFZ1r9BIS7y8J64jIng/UsN+cuDYGUvH358Mj+hr8w+oonxqsPr7ZxiTCbHrHm3ISJ/a0m3XxzN0X7vwxdhTKy7anwH95HpfGW3RA2EZQw/39FgBwnzS+3jVRa8aN9nx2gCrReYTFXjcrkSHNAT+RSYORsoeAzyAS5DsqCQmj7PDosItmlg54cWlRDMxcOE8V84G0FZvvUYqg/4G1WQBSg8xAbmoJbaLcPWPembSW069KgF2P4zsuj4vYE/YW/H4hum3Mc5L0lfQZONFyXKLRW4LqN2tAo0oYKJDOPhAp7xh0EnXZ4x6MYgAdDRfxjgMm1ZeyMoEmVzpoGFhdOWJwF0G9Iqxaf7RNhjTI5fGQof309j3j1CpGR9zu+XcR7xC1wr0OWRmYHeOMOwtU/UOH7oXjqqbvGmdvDborHjhIcUNdr/uz4h/a6y3GifJ0+538zOYfN8nUM7TVzS2ODB51hUtM88mmAzgjeucwcpHI8BoJ9gJDbJA+ZfxTU8gNEyqk4eoBaiRxQ+RZlp6wTNCPoQmeJtHEUEG4XsRLYx8bhCSDFVM6tMx2YtWRawVgCdxYtbVYD1iWe9HCg7N5Y2vudupeK7SMjb/U2Ac2/L5NtYwPTnnemukV76ae+Rvt89DtUIgXCaqJPBGzGKHG3QMxfWzLTxlAE4c6LcrPR9KMoiiHoSE=; rskxRunCookie=0; rCookie=a9habht7tm711246vvsuosqm8h42wgg; lastRskxRun=1742465769640; datadome=d234~ExLTPjxG2_MIfDRxaJs~Zk~oNvIG8vK3MgnIRN4tBdFp8jj3Ov8BTHQd0s1LVwaZlphOsA~ZROskHMF3U7Q0nNww~FYpWBCdZX80x5wSSZ1MgYmtKC45hYcHGI0"
],
"cookieResponse": [
"MSISAuth=AAEAAGfufZvnuPj4k/3ZlXVbEOSU4pJrkOii3+sEPKbSX11ESqhvljM620lxmq+vh433bWzQVAPHVRevwWCr3oZ+oUH0fuXtUUaCSfh2KBdkFu61u2DwNpVkVJD1kKhGArNbZeEF9Mga5DTIueucGB4pxZJjk60yNuZaXrz2GUtKRP2qaeJWtqk1DuzvKYB5USGHEZxfjQXtKCeWVIjKQ7rFqiQ0KlA+W4Fsf6EjkeHN3VjOgjo82tQSSkes554gxR84OLUHh1+E/F5h6cWqmsOcig4KSA52dkiBKbBhW7nnY4pAAfesCmtfdJ4SpYtvTEui9Whc3itePc9bH6ElFX3UGpbKcgVH7AbwPjPPSbOEvJpCD29woUL/ENs+Bll6HG+/7QABAACOc5XE1qq8pA0U4yw6a6hjoQ6sEbd3VvIf/jzwGO5NUrlmeq/0uH9B8DTPUnvc9eeWHod2NRyx6L+/GXO/G9q2RKd30hmhws8ND8ujuHFrjC8U6TK+bBuTIr/e79CwGwoz6/fkcw/C/4gf3V26SWBYvLcDwBy1C2hlhzXw6H23di8Ke9WSYCLpC3Uevg83xuCl6BxKOwJ/zxOMItUN+gnZdodMykw8S9TLv9V0Et7eLoy73aHNZEps/of/qEQTo93+wV2m9tvQijIOwctpWcwIAZxDXBSBXViNB4N2ErItEOnWfcHtYYnXi4hgpJR9wN51HHbrYpSVN2QI9DkZ7dFcoAMAAPgZKLQp9Mnr8H2XmP5fBYQPEEduT1JmRpH24COGE7AgenYikQFGZi7oBCugmcfqCTzKZW6I+ZmM0Pkaebg/eRg3SdJY3E0GXtJ0LL2vpf2+Kr31bgNP2u5vCyAPvBitUTMgQa2rcAXesCvvvXvzejn0+oam5Be3/kwIwYv5B3H6wvGFBOpQbUIR8z1oTyYitCjqQgDA3d4J6vPgmj9lnK9WlQ/TbeWB650hX6oMq/ILdRscNJPJfbpakFXupdoriLHo5RIjbz8TT5+coIozEsUOEvpWtX3LDclHZMCLL1MlBPPUBi5LIUAvakXXbMXzWO0FjuaBb3xC58tAZv72E3ZmdTrcVK70/mAqL2HJVsi4Gzi35G32FLAkQuFsfvh/xNYlgDuZabwpAuHB/KevWaY76M3v7CnAuvRou9ekYBlTP6Nj5JOgg2O+U8OgBVA1ZxgExDl/v0G118k9GK2TjAWsbd9tZjP2iRcLSBWKleJdv0Q+CawWm/J61egi3d03ES2JbYu6biNu/XKxQ/rsGvx0zjZQpP9FLF+eFnAWbXXN3Icsuzjte9f37wG5Tp6hbwdZUDndkZDtXsmn+N1SZJYZOUuObb7MXgZnlEGpu8K6ruJUOGgwXdQ9EcFjuZpXzJF4AVX6l2aQumDF/nnMWZvzb66rpJ3kqOKI3CDm/YN395kLnbdPKx+755yYsp8iJirD7EPY6jvCWKTeiBIROIwZOQA1hu1ww9brk9queSsEZCPkQGjUOuJxWqDop/Stewq5R6i8Q6O6HJiAVfWAwXwcCOyYQoJT34PjNreVE3qbT0zOGTTuxRhwnjcaDCivD42fWC5CtrDbXuQeV+ex+sdFOvCqj02YNvVb7dLLrYFI+9UURyp3fpr/syxlftNL/CqOunCH0h9U3OnmVzaOzMu24F286sYaEsDv+nYXQHrRboGhoywBvnoe73xNfPjwWBMlU6NJ8Otl4qRpFxeyY4LB53Giw5j338AsvxXo7L3lbjP+YCdD9Ucv+P/IPT2wfr+e+KtAzcwKC5F89JLxqu2hc8mLz0SZZQaGwDT3afVqsE1jSDiKsb+G5J6HBNKZwDJTLk9RF6YCotBIEVaq6EL+m2v+bgXALLC3ZKWbxqE7YSkeDf5W5Frw4RtjjPo8CLzZUqOyChO7vDyp1s+DPz72A5uEYOnbhQZcns9KbkL62L4KSqh/Lm3JzDVGDD4jn6dhGlBu5/VajT+Kn43owCY=; path=/adfs; HttpOnly; Secure; SameSite=None",
"MSISAuth=; expires=Sun, 11 May 2025 14:41:22 GMT; path=/adfs/ls; Secure; SameSite=None",
"SamlSession=aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZm1ldGFkYXRhJkZhbHNlJmFtaW5lLmJlbmF5YWRhJTQwZXh0Lmhlcm1lcy5jb20mdXJuJTNhb2FzaXMlM2FuYW1lcyUzYXRjJTNhU0FNTCUzYTIuMCUzYW5hbWVpZC1mb3JtYXQlM2FwZXJzaXN0ZW50JiYmJl8yMjM5ZDNhNi04OTY5LTQzZjYtODVjNi05NDIyMzIwZTI3N2UmXzgxM2E0MjFiLTUwNjEtNGViNy04ZjYyLTI3ZTUyZmIxMmNlZCZfZTA5MTZiMjgtNWI0NC00ZGEyLTkyNTktZTllMWQ5NWE3NmI2; path=/adfs; HttpOnly; Secure; SameSite=None",
"MSISAuthenticated=NS8xMi8yMDI1IDI6NDE6MjIgUE0=; path=/adfs; HttpOnly; Secure; SameSite=None",
"MSISLoopDetectionCookie=MjAyNS0wNS0xMjoxNDo0MToyMlpcMQ==; path=/adfs; HttpOnly; Secure; SameSite=None"
],
"protocol": "SAML",
"messageType": "Request",
"messageVerb": "authnrequest",
"message": "<samlp:AuthnRequest\n xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"\n xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\"\n ID=\"ONELOGIN_7bc388c175dffb87f691ba83f313492e94be52f8\"\n Version=\"2.0\"\n ProviderName=\"ComputableFacts\"\n IssueInstant=\"2025-05-12T14:41:05Z\"\n Destination=\"https://fed.hermes.com/adfs/ls/\"\n ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n AssertionConsumerServiceURL=\"https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/acs\">\n <saml:Issuer>https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/metadata</saml:Issuer>\n <samlp:NameIDPolicy\n Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\"\n AllowCreate=\"true\" />\n <samlp:RequestedAuthnContext Comparison=\"exact\">\n <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>\n </samlp:RequestedAuthnContext>\n</samlp:AuthnRequest>",
"state": "https://app.cywise.io/home",
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"signature": "sXpOsDJHdDZwiZa2mvzN5c8gjdipNbT1OBnHA/ZFL1tVdhvt/u0aDRGfVJ2DzjX629NoD0GAb87kDjb3EblIfICOibAeOKmPv0HgCNyx2JtFR8qqUD2jkPgfNdhGwNE16VPaIIcMs8RpHCvawd6NSo63teW4C+IM5VhGH+rI0D1LTkugGAam0ruVR0gdLT6R7tyq/lYBm5iem+C0dfkHxmsF83I7ccB3F/hAojIjj0igwBamaGqebk2QsHuqpdX8uqOxfpBipqAnmG4QHsXyTAWE5tWsjlGmt3a0YuXiSAjCMCPjewgfnv50i4q5EwkZVVvp+XLV8LnQJyq26y2w6g=="
},
{
"guid": "b0a0e4ff-12e3-c5e4-b517-4daf30bb3910",
"requestId": "38290",
"timestamp": "2025-05-12T14:41:05.439Z",
"url": "https://fed.hermes.com/adfs/ls/",
"httpMethod": "GET",
"cookieRequest": [
"SamlLogout=aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZm1ldGFkYXRhP09ORUxPR0lOX2QyZjVmZTAwMGVhMjdmOWE2ZjNjZmU0ZTZkZWM2OGU5NDY2ZTNiNGE/aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZmxvZ291dD91cm4lM2FhdXRoMCUzYWN5YmVydmFkaXMtcHJvZCUzYUdTUTdNSTZRUVEyMFlTTVhaOU41JkZhbHNlJmFtaW5lLmJlbmF5YWRhJTQwZXh0Lmhlcm1lcy5jb20mJiYmJl8xYThhYmRmYy03MjU5LTRhZTYtOGNhYS0zYzU5NGFjY2RmY2EmXzc1YWUzMjBjLTM0MzYtNDI4Ny1iNjk2LTU4MTFmM2JkMTcwYSZfZDlmNzg3YmMtODEzMC00OTRlLTgwZGYtYjFkMTFlYjE4MGUwJl9mZTgwNGVlMS05MjNjLTRhMDMtOWVmNy1hNmJhNmVlYWJmNmQmX2JjOWE0YWNmLWU3NDAtNDhiMC04YThlLTYxZjhhMWU4NmJhYSZfMzM5YjAxYzUtZDliYy00NGM0LTllNGEtNTc3YjNiYTAzZGRhJl9mZjg0NGQ0OC02ZTZkLTRhZDEtYjE5MC00YmU0MmI2ZGEyYWU/XzI4MDhhODQwLTM1NTMtNGQwZS1iMTEyLTA4ZjIyMjZlN2M1OT91cm4lM2FvYXNpcyUzYW5hbWVzJTNhdGMlM2FTQU1MJTNhMi4wJTNhc3RhdHVzJTNhU3VjY2Vzcw==; MSISSignoutProtocol=U2FtbA==; SamlSession=aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZm1ldGFkYXRhJkZhbHNlJmFtaW5lLmJlbmF5YWRhJTQwZXh0Lmhlcm1lcy5jb20mdXJuJTNhb2FzaXMlM2FuYW1lcyUzYXRjJTNhU0FNTCUzYTIuMCUzYW5hbWVpZC1mb3JtYXQlM2FwZXJzaXN0ZW50JiYmJl8yMjM5ZDNhNi04OTY5LTQzZjYtODVjNi05NDIyMzIwZTI3N2UmXzgxM2E0MjFiLTUwNjEtNGViNy04ZjYyLTI3ZTUyZmIxMmNlZA==; MSISLoopDetectionCookie=MjAyNS0wNS0xMjoxNDozOTo0NVpcMQ==; rskxRunCookie=0; rCookie=a9habht7tm711246vvsuosqm8h42wgg; lastRskxRun=1742465769640; datadome=d234~ExLTPjxG2_MIfDRxaJs~Zk~oNvIG8vK3MgnIRN4tBdFp8jj3Ov8BTHQd0s1LVwaZlphOsA~ZROskHMF3U7Q0nNww~FYpWBCdZX80x5wSSZ1MgYmtKC45hYcHGI0"
],
"protocol": "SAML",
"messageType": "Request",
"messageVerb": "authnrequest",
"message": "<samlp:AuthnRequest\n xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"\n xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\"\n ID=\"ONELOGIN_7bc388c175dffb87f691ba83f313492e94be52f8\"\n Version=\"2.0\"\n ProviderName=\"ComputableFacts\"\n IssueInstant=\"2025-05-12T14:41:05Z\"\n Destination=\"https://fed.hermes.com/adfs/ls/\"\n ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n AssertionConsumerServiceURL=\"https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/acs\">\n <saml:Issuer>https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/metadata</saml:Issuer>\n <samlp:NameIDPolicy\n Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\"\n AllowCreate=\"true\" />\n <samlp:RequestedAuthnContext Comparison=\"exact\">\n <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>\n </samlp:RequestedAuthnContext>\n</samlp:AuthnRequest>",
"state": "https://app.cywise.io/home",
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"signature": "sXpOsDJHdDZwiZa2mvzN5c8gjdipNbT1OBnHA/ZFL1tVdhvt/u0aDRGfVJ2DzjX629NoD0GAb87kDjb3EblIfICOibAeOKmPv0HgCNyx2JtFR8qqUD2jkPgfNdhGwNE16VPaIIcMs8RpHCvawd6NSo63teW4C+IM5VhGH+rI0D1LTkugGAam0ruVR0gdLT6R7tyq/lYBm5iem+C0dfkHxmsF83I7ccB3F/hAojIjj0igwBamaGqebk2QsHuqpdX8uqOxfpBipqAnmG4QHsXyTAWE5tWsjlGmt3a0YuXiSAjCMCPjewgfnv50i4q5EwkZVVvp+XLV8LnQJyq26y2w6g=="
},
{
"guid": "04266403-d13b-4599-ffdb-520b2907cde6",
"requestId": "38286",
"timestamp": "2025-05-12T14:41:05.286Z",
"url": "https://fed.hermes.com/adfs/ls/",
"httpMethod": "GET",
"cookieRequest": [
"SamlLogout=aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZm1ldGFkYXRhP09ORUxPR0lOX2QyZjVmZTAwMGVhMjdmOWE2ZjNjZmU0ZTZkZWM2OGU5NDY2ZTNiNGE/aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZmxvZ291dD91cm4lM2FhdXRoMCUzYWN5YmVydmFkaXMtcHJvZCUzYUdTUTdNSTZRUVEyMFlTTVhaOU41JkZhbHNlJmFtaW5lLmJlbmF5YWRhJTQwZXh0Lmhlcm1lcy5jb20mJiYmJl8xYThhYmRmYy03MjU5LTRhZTYtOGNhYS0zYzU5NGFjY2RmY2EmXzc1YWUzMjBjLTM0MzYtNDI4Ny1iNjk2LTU4MTFmM2JkMTcwYSZfZDlmNzg3YmMtODEzMC00OTRlLTgwZGYtYjFkMTFlYjE4MGUwJl9mZTgwNGVlMS05MjNjLTRhMDMtOWVmNy1hNmJhNmVlYWJmNmQmX2JjOWE0YWNmLWU3NDAtNDhiMC04YThlLTYxZjhhMWU4NmJhYSZfMzM5YjAxYzUtZDliYy00NGM0LTllNGEtNTc3YjNiYTAzZGRhJl9mZjg0NGQ0OC02ZTZkLTRhZDEtYjE5MC00YmU0MmI2ZGEyYWU/XzI4MDhhODQwLTM1NTMtNGQwZS1iMTEyLTA4ZjIyMjZlN2M1OT91cm4lM2FvYXNpcyUzYW5hbWVzJTNhdGMlM2FTQU1MJTNhMi4wJTNhc3RhdHVzJTNhU3VjY2Vzcw==; MSISSignoutProtocol=U2FtbA==; SamlSession=aHR0cHMlM2ElMmYlMmZhcHAuY3l3aXNlLmlvJTJmc2FtbCUyZjBkOTJlYzk1LWFmNmEtNDhjNC1iODkwLTZmNzAxYTZmMzNmYiUyZm1ldGFkYXRhJkZhbHNlJmFtaW5lLmJlbmF5YWRhJTQwZXh0Lmhlcm1lcy5jb20mdXJuJTNhb2FzaXMlM2FuYW1lcyUzYXRjJTNhU0FNTCUzYTIuMCUzYW5hbWVpZC1mb3JtYXQlM2FwZXJzaXN0ZW50JiYmJl8yMjM5ZDNhNi04OTY5LTQzZjYtODVjNi05NDIyMzIwZTI3N2UmXzgxM2E0MjFiLTUwNjEtNGViNy04ZjYyLTI3ZTUyZmIxMmNlZA==; MSISLoopDetectionCookie=MjAyNS0wNS0xMjoxNDozOTo0NVpcMQ==; rskxRunCookie=0; rCookie=a9habht7tm711246vvsuosqm8h42wgg; lastRskxRun=1742465769640; datadome=d234~ExLTPjxG2_MIfDRxaJs~Zk~oNvIG8vK3MgnIRN4tBdFp8jj3Ov8BTHQd0s1LVwaZlphOsA~ZROskHMF3U7Q0nNww~FYpWBCdZX80x5wSSZ1MgYmtKC45hYcHGI0"
],
"protocol": "SAML",
"messageType": "Request",
"messageVerb": "authnrequest",
"message": "<samlp:AuthnRequest\n xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"\n xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\"\n ID=\"ONELOGIN_7bc388c175dffb87f691ba83f313492e94be52f8\"\n Version=\"2.0\"\n ProviderName=\"ComputableFacts\"\n IssueInstant=\"2025-05-12T14:41:05Z\"\n Destination=\"https://fed.hermes.com/adfs/ls/\"\n ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n AssertionConsumerServiceURL=\"https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/acs\">\n <saml:Issuer>https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/metadata</saml:Issuer>\n <samlp:NameIDPolicy\n Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\"\n AllowCreate=\"true\" />\n <samlp:RequestedAuthnContext Comparison=\"exact\">\n <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>\n </samlp:RequestedAuthnContext>\n</samlp:AuthnRequest>",
"state": "https://app.cywise.io/home",
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"signature": "sXpOsDJHdDZwiZa2mvzN5c8gjdipNbT1OBnHA/ZFL1tVdhvt/u0aDRGfVJ2DzjX629NoD0GAb87kDjb3EblIfICOibAeOKmPv0HgCNyx2JtFR8qqUD2jkPgfNdhGwNE16VPaIIcMs8RpHCvawd6NSo63teW4C+IM5VhGH+rI0D1LTkugGAam0ruVR0gdLT6R7tyq/lYBm5iem+C0dfkHxmsF83I7ccB3F/hAojIjj0igwBamaGqebk2QsHuqpdX8uqOxfpBipqAnmG4QHsXyTAWE5tWsjlGmt3a0YuXiSAjCMCPjewgfnv50i4q5EwkZVVvp+XLV8LnQJyq26y2w6g=="
}
]
Logs de Cywise correspondants
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:16 +0000] "POST /logalert/LkCmJX3Fkq6ERg7z HTTP/1.0" 200 1169 "-" "LogAlert/1.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:18 +0000] "POST /logalert/LtAgoGdgm2JAN5tAk6CRzkOVTpRkgR HTTP/1.0" 200 1169 "-" "LogAlert/1.0.0"
[2025-05-12 14:41:22] prod.DEBUG: [SAML2 Authentication] User Attributes with Friendly Name:
[2025-05-12 14:41:22] prod.DEBUG: [SAML2 Authentication] User Attributes with Name: {"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress":["amine.benayada@ext.hermes.com"],"firstname":["Amine"],"lastname":["BENAYADA"],"http://schemas.xmlsoap.org/claims/Group":["SG-WORLD-APP-CU-CYBERBUDDY-ADMIN-SSO"]}
[2025-05-12 14:41:22] prod.DEBUG: [SAML2 Authentication] User email found: "amine.benayada@ext.hermes.com" (from name "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")
[2025-05-12 14:41:22] prod.INFO: [SAML2 Authentication] User email found: "amine.benayada@ext.hermes.com"
[2025-05-12 14:41:22] prod.DEBUG: [SAML2 Authentication] User name found: "BENAYADA" (from name "lastname")
[2025-05-12 14:41:22] prod.INFO: [SAML2 Authentication] User name found: "BENAYADA"
[2025-05-12 14:41:22] prod.INFO: [SAML2 Authentication] Role attribute NOT found from friendly name "group"
[2025-05-12 14:41:22] prod.INFO: [SAML2 Authentication] Role attribute found from name "http://schemas.xmlsoap.org/claims/Group"
[2025-05-12 14:41:22] prod.INFO: [SAML2 Authentication] Role value=SG-WORLD-APP-CU-CYBERBUDDY-ADMIN-SSO
[2025-05-12 14:41:22] prod.INFO: [SAML2 Authentication] User already exist, we update attributes
[2025-05-12 14:41:22] prod.DEBUG: [SAML2 Authentication] Default roles = ["App\\Models\\Role::CYBERBUDDY_ONLY"]
[2025-05-12 14:41:22] prod.DEBUG: [SAML2 Authentication] SAML roles = ["SG-WORLD-APP-CU-CYBERBUDDY-ADMIN-SSO"]
[2025-05-12 14:41:22] prod.DEBUG: [SAML2 Authentication] IdP roles settings = ["SG-WORLD-APP-CU-CYBERBUDDY-ADMIN-SSO"]
[2025-05-12 14:41:22] prod.DEBUG: [SAML2 Authentication] Cywise roles for SG-WORLD-APP-CU-CYBERBUDDY-ADMIN-SSO = ["App\\Models\\Role::CYBERBUDDY_ADMIN"]
[2025-05-12 14:41:22] prod.DEBUG: [SAML2 Authentication] New roles = ["App\\Models\\Role::CYBERBUDDY_ONLY","App\\Models\\Role::CYBERBUDDY_ADMIN"]
[2025-05-12 14:41:22] prod.DEBUG: [SAML2 Authentication] User roles IDs = [9,10]
[2025-05-12 14:41:22] prod.DEBUG: [SAML2 Authentication] User roles IDs updated = {"attached":[],"detached":[],"updated":[]}
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:22 +0000] "POST /saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/acs HTTP/1.0" 302 1070 "https://fed.hermes.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:22 +0000] "GET /home HTTP/1.0" 302 1556 "https://fed.hermes.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:22 +0000] "GET /home?tab=ama HTTP/1.0" 200 6441 "https://fed.hermes.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:23 +0000] "GET /cyber-buddy/chat?conf=%7B%22title%22%3A%22CyberBuddy%22%2C%22aboutText%22%3Anull%2C%22aboutLink%22%3A%22https%3A%2F%2Fapp.cywise.io%22%2C%22userId%22%3A%22106%22%2C%22chatServer%22%3A%22%2Fbotman%22%2C%22bubbleAvatarUrl%22%3A%22%2Fimages%2Ficons%2Fcyber-buddy.svg%22%2C%22frameEndpoint%22%3A%22%2Fcyber-buddy%2Fchat%22%2C%22introMessage%22%3A%22Bonjour!%20Je%20suis%20votre%20cyber%20assistant.%20Que%20puis-je%20faire%20pour%20vous%3F%22%2C%22desktopHeight%22%3A900%2C%22desktopWidth%22%3A1200%2C%22mainColor%22%3A%22%2347627F%22%2C%22bubbleBackground%22%3A%22%2300264b%22%2C%22headerTextColor%22%3A%22white%22%7D HTTP/1.0" 200 1862 "https://app.cywise.io/home?tab=ama" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:23 +0000] "GET /adversary_meter/src/blueprintjs/blueprintjs/blueprint-select.css.map HTTP/1.0" 404 6826 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:23 +0000] "GET /adversary_meter/src/blueprintjs/blueprintjs/blueprint-popover2.css.map HTTP/1.0" 404 6826 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:23 +0000] "GET /adversary_meter/src/blueprintjs/blueprintjs/blueprint.css.map HTTP/1.0" 404 6826 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:23 +0000] "GET /adversary_meter/src/blueprintjs/blueprintjs/blueprint-datetime.css.map HTTP/1.0" 404 6826 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:23 +0000] "GET /adversary_meter/src/blueprintjs/blueprintjs/blueprint-icons.css.map HTTP/1.0" 404 6826 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:23 +0000] "GET /adversary_meter/src/blueprintjs/blueprintjs/table.css.map HTTP/1.0" 404 6826 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
[2025-05-12 14:41:31] prod.DEBUG: [SAML2 Authentication] Logout {"uuid":"0d92ec95-af6a-48c4-b890-6f701a6f33fb","email":"amine.benayada@ext.hermes.com","nameId":"amine.benayada@ext.hermes.com"}
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:31 +0000] "POST /logout HTTP/1.0" 302 1931 "https://app.cywise.io/home?tab=ama" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:31 +0000] "GET /saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/logout?nameId=amine.benayada%40ext.hermes.com HTTP/1.0" 302 1285 "https://app.cywise.io/home?tab=ama" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
[2025-05-12 14:41:31] prod.ERROR: saml2.error_detail {"uuid":"0d92ec95-af6a-48c4-b890-6f701a6f33fb","error":null}
[2025-05-12 14:41:31] prod.ERROR: saml2.error ["logout_not_success"]
[2025-05-12 14:41:31] prod.ERROR: Symfony\Component\HttpFoundation\Response::setContent(): Argument #1 ($content) must be of type ?string, Illuminate\Routing\Redirector given, called in /var/www/html/vendor/laravel/framework/src/Illuminate/Http/Response.php on line 82 {"userId":106,"exception":"[object] (TypeError(code: 0): Symfony\\Component\\HttpFoundation\\Response::setContent(): Argument #1 ($content) must be of type ?string, Illuminate\\Routing\\Redirector given, called in /var/www/html/vendor/laravel/framework/src/Illuminate/Http/Response.php on line 82 at /var/www/html/vendor/symfony/http-foundation/Response.php:418)
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:31 +0000] "GET /saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/sls?SAMLResponse=fVJLS8QwEP4rJfc2TdNmN6FbEBVZWBVUPHiRNJ1ooZvUTurj35vu4mFB9jjD98o3qVHvh1Ht%2fJufwwPg6B1Csr3akFcrLdhcVCmTwNKyhC5thS3TVuadFZavuACSPMOEvXcbUmQ5SbaIM2wdBu1CXOVFleZRoHhipSqZ4ixjonghyRVg6J0OB%2bZ7CCMqSvU4Zubnq0fIek%2bXYDTvZAFGVqm2Qqfl2kT7tcxTYVc50zEEty3FAUlyuQRfTOfJKa%2bxR%2bX0HlAFox4vbncq5lPmCFKzwxFMb3voYmb39%2b4nvyH3d9e7%2b5vt3auBta5kK1vO15LzqmWV5FUFfFWA7lhrLYiSCUOS7%2f3gUB2KPG8%2fTj544wfS1IeipiP1PEkjwrQURZqlqNiThS57hykCM%2bP3VHcWacR89gaQhmnGUNOjflMfz%2fsYdJjxdLr0HSTPepjhvD8e0OoBPuZ4NJhIQpuanurS%2f35R8ws%3d&RelayState=https%3a%2f%2fapp.cywise.io%2fsaml%2f0d92ec95-af6a-48c4-b890-6f701a6f33fb%2flogout&Signature=LGgjfDFT9jkkTP7SApdrDsC%2bJniBH14V96HnaEJ2nvMDHmuuuOWZbEQ9NbeNhooJMT5O48Pe3yc6CKfb%2bGSBFVR%2f%2bHeWDX2ItIXQcxC0tQRlPcG%2biX%2bK2rhENR8RSzGoRDNp0KhqJMHry6JVxN%2fZ3hennMRGz%2fWENoQOiyDHBsLgnwCR9qm7oy8SGWt0aTQbhtV5%2fmmU7Aq3UihWNDKdTuAdIqW%2bERhxU%2fTHh5HbUrAm%2flbXuMxxDO5XHgiMVQ0ghOVEr3DJck2iPQM8Vt1%2bdE0kNN9oYhASjgJOPsX%2bynJojEs80E9KZjHtPoeYm7j63ufDx3A3KNWHizMips2Izw%3d%3d&SigAlg=http%3a%2f%2fwww.w3.org%2f2001%2f04%2fxmldsig-more%23rsa-sha256 HTTP/1.0" 500 7304 "https://app.cywise.io/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:31 +0000] "POST /logalert/ezQbPB8ANHtyEGgBzdEA HTTP/1.0" 200 1169 "-" "LogAlert/1.0.0"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:34 +0000] "GET /home HTTP/1.0" 302 1521 "-" "FreshpingBot/1.0 (+https://freshping.io/)"
10.0.6.31:80 10.0.0.2 - - [12/May/2025:14:41:34 +0000] "GET /login HTTP/1.0" 200 3233 "-" "FreshpingBot/1.0 (+https://freshping.io/)"
J'ai répondu à Amine :
J'ai bien retrouvé la trace de votre test qui a échoué hier à 14:41 UTC. C'est la phase de logout qui ne fonctionne pas :
[2025-05-12 14:41:31] prod.ERROR: saml2.error_detail {"uuid":"0d92ec95-af6a-48c4-b890-6f701a6f33fb","error":null}
[2025-05-12 14:41:31] prod.ERROR: saml2.error ["logout_not_success"]
Cela ne m'aide pas à comprendre l'origine du problème.
J'ai comparé les messages "LogoutRequest" envoyés par mon application :
Logs Edge :
<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="ONELOGIN_ce8a59b9b3389335b159355e372ead1bffe6416c"
Version="2.0"
IssueInstant="2025-05-12T14:41:31Z"
Destination="https://fed.hermes.com/adfs/ls/"
>
<saml:Issuer>https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/metadata</saml:Issuer>
<saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">amine.benayada@ext.hermes.com</saml:NameID>
</samlp:LogoutRequest>
Logs Chrome :
<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="ONELOGIN_9d5260879903e743b71f04dec9b6eadf379052fb"
Version="2.0"
IssueInstant="2025-05-12T14:25:02Z"
Destination="https://fed.hermes.com/adfs/ls/"
>
<saml:Issuer>https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/metadata</saml:Issuer>
<saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">amine.benayada@ext.hermes.com</saml:NameID>
</samlp:LogoutRequest>
Les deux me semblent identiques et, notamment, mon application envoie bien le NameID, votre email, dans les 2 cas.
Mais la réponse renvoyée ("LogoutResponse") par votre ADFS n'est pas la même.
Pour Chrome, j'ai un statut "Success" :
<samlp:LogoutResponse ID="_7c38d1bc-8840-4075-9a76-5d09a7e6d21b"
Version="2.0"
IssueInstant="2025-05-12T14:25:02.304Z"
Destination="https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/sls"
Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
InResponseTo="ONELOGIN_9d5260879903e743b71f04dec9b6eadf379052fb"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
>
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://fed.hermes.com/adfs/services/trust</Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
</samlp:Status>
</samlp:LogoutResponse>
Alors que pour Edge, j'ai un statut "Requester" :
<samlp:LogoutResponse ID="_f9fef065-19e1-44ed-b6f4-b90df6f3736e"
Version="2.0"
IssueInstant="2025-05-12T14:41:31.162Z"
Destination="https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/sls"
Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
InResponseTo="ONELOGIN_ce8a59b9b3389335b159355e372ead1bffe6416c"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
>
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://fed.hermes.com/adfs/services/trust</Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Requester" />
</samlp:Status>
</samlp:LogoutResponse>
Hermès - Point du 2025-05-14¶
Finalement, le problème avec Edge se situe au moment du logout et est dû à la déconnexion d'un autre service (Cybervadis). En effet, au moment de la déconnexion de CyberBuddy, je déclenche la déconnexion de l'IdP qui déclenche la déconnexion des autres services.
Sur ce point, nous avons décidé que la déconnexion de CyberBuddy ne doit pas déconnecter l'utilisateur de l'IdP.
Amine m'a montré un autre problème : s'il se connecte en premier à Cybervadis puis qu'il se connecte ensuite à CyberBuddy, alors l'ADFS lui demande de saisir à nouveau son login et son mot de passe. Avec le SSO, l'ADFS ne devrait pas le demander puisque l'utilisateur est déjà authentifié.
Amine a tenté la connexion à CyberBuddy en premier puis à Cybervadis et, dans ce sens, l'ADFS ne demande pas le login et le mot de passe : le SSO fonctionne.
Dans les logs SAML Tracer qu'Amine m'a envoyés, je retrouve les LogoutRequest. De Cybervadis :
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
Destination="https://fed.hermes.com/adfs/ls/"
ID="_f39561983567d69bef91f2181c7b0029"
IssueInstant="2025-05-14T09:45:10Z"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Version="2.0"
>
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:auth0:cybervadis-prod:GSQ7MI6QQQ20YSMXZ9N5</saml:Issuer>
</samlp:AuthnRequest>
De CyberBuddy :
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="ONELOGIN_f57bdb6ca1db02817a102920c7d52d7301ee02be"
Version="2.0"
ProviderName="ComputableFacts"
IssueInstant="2025-05-14T09:45:25Z"
Destination="https://fed.hermes.com/adfs/ls/"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
AssertionConsumerServiceURL="https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/acs"
>
<saml:Issuer>https://app.cywise.io/saml/0d92ec95-af6a-48c4-b890-6f701a6f33fb/metadata</saml:Issuer>
<samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
AllowCreate="true"
/>
<samlp:RequestedAuthnContext Comparison="exact">
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</samlp:RequestedAuthnContext>
</samlp:AuthnRequest>
Cela pourrait être le contexte PasswordProtectedTransport ajouté par CyberBuddy qui provoque le demande du login et du mot
de passe par l'ADFS. A creuser.
Enfin, nous reparlons des sous-domaines à autoriser et je confirme que je peux les autoriser tous avec quelque chose du genre *.hermes.com
2025-05-14 - regex pour le domaine¶
J'ai modifié le code pour supporter une regex dans les colonnes domain et alt_domain1 de
ma table saml2_tenants.
Si le texte commence par ~, je considère que c'est une regex, sinon je fais une simple
comparaison (===).
Cette modification a été poussée en PROD le 2025-05-14.
2025-05-15 - Pas de SLO¶
Le fait de demander à l'IdP de déconnecter l'utilisateur s'appelle le Single LogOut (SLO).
Je vais ajouter une option dans le JSON de configuration d'un Tenant SAML pour activer ou non le SLO.
J'ai ajouté l'option logoutSlo, false par défaut. J'ai validé que cela fonctionne
sur mon poste en local.
Pas besoin de changer la configuration en PROD pour Hermès puisque l'option est à false
par défaut.
TODO : s'assurer de supprimer saml2NameId de la session quand l'utilisateur se déconnecte.
J'ai changé le code qui met fin à la session Laravel en cas de SLO et la session est bien détruite maintenant.
Cette modification à été poussée en PROD le 2025-05-15.
2025-05-15 - SSO sans demande de login / mot de passe¶
Je ne reproduis pas le problème avec Keycloak cywise1.
D'après cet article Microsoft, le contexte PasswordProtectedTransport n'est pas supporté.
J'ai modifié une option de ma lib SAML pour ajouter d'autres possibilités :
'requestedAuthnContext' => [
'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport',
'urn:oasis:names:tc:SAML:2.0:ac:classes:X509',
'urn:federation:authentication:windows',
'urn:oasis:names:tc:SAML:2.0:ac:classes:Unspecified',
],
J'ai testé que le SSO fonctionnait toujours avec Keycloak cywise1 et c'est le cas mais je ne peux pas vérifier pour l'ADFS de Hermès.
Cette modification à été poussée en PROD le 2025-05-15.