Files
mabibli/MaBibli.Api/Services/Identite/FournisseurUtilisateur.cs
T
Mathieu LimonierandClaude Opus 5 6a6d745af4 MaBibli 1.0.0
Gestion de bibliothèque personnelle auto-hébergée : catalogue, prêts,
scan de code-barres, consultation hors-ligne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 22:36:16 +02:00

98 lines
3.4 KiB
C#

using MaBibli.Shared.Dtos;
namespace MaBibli.Api.Services.Identite;
public interface IFournisseurUtilisateur
{
/// <summary>Identité de la requête en cours, jamais <c>null</c> (au pire <see cref="UtilisateurCourant.Anonyme"/>).</summary>
UtilisateurCourant Obtenir();
}
/// <summary>
/// Options de l'identité, section <c>Identite</c> de la configuration.
/// </summary>
public sealed class OptionsIdentite
{
public const string Section = "Identite";
/// <summary>
/// Utilisateur simulé en développement, où SSOwat n'existe pas.
/// </summary>
/// <remarks>
/// CLAUDE.md : « prévoir un utilisateur simulé plutôt que de désactiver l'auth ».
/// Il n'est utilisé que si les en-têtes SSOwat sont absents, et n'est renseigné que dans
/// <c>appsettings.Development.json</c> — en production, aucune valeur, donc aucun repli.
/// </remarks>
public string? UtilisateurSimule { get; set; }
public string? EmailSimule { get; set; }
public string? NomCompletSimule { get; set; }
}
/// <summary>
/// Lit l'identité dans les en-têtes HTTP injectés par SSOwat.
/// </summary>
/// <remarks>
/// nginx authentifie le visiteur via le portail YunoHost puis pousse <c>YNH_USER</c>,
/// <c>YNH_USER_EMAIL</c> et <c>YNH_USER_FULLNAME</c>. L'application se contente de les lire.
/// <para>
/// ⚠️ Ces en-têtes ne sont dignes de confiance que parce que le service n'écoute que sur
/// <c>127.0.0.1</c> derrière nginx : exposé directement, n'importe qui pourrait forger
/// <c>YNH_USER</c>. Cette contrainte se règle dans <c>systemd.service</c> / <c>nginx.conf</c>,
/// qui vivent dans le dépôt <c>mabibli_ynh</c> (phase 7) — pas ici.
/// </para>
/// </remarks>
public sealed class FournisseurUtilisateurSsowat(
IHttpContextAccessor accesseur,
Microsoft.Extensions.Options.IOptions<OptionsIdentite> options) : IFournisseurUtilisateur
{
public const string EnteteUtilisateur = "YNH_USER";
public const string EnteteEmail = "YNH_USER_EMAIL";
public const string EnteteNomComplet = "YNH_USER_FULLNAME";
public UtilisateurCourant Obtenir()
{
var entetes = accesseur.HttpContext?.Request.Headers;
var identifiant = Valeur(entetes, EnteteUtilisateur);
if (identifiant is not null)
{
return new UtilisateurCourant
{
Identifiant = identifiant,
Email = Valeur(entetes, EnteteEmail),
NomComplet = Valeur(entetes, EnteteNomComplet),
Simule = false,
};
}
var simule = options.Value;
if (!string.IsNullOrWhiteSpace(simule.UtilisateurSimule))
{
return new UtilisateurCourant
{
Identifiant = simule.UtilisateurSimule,
Email = simule.EmailSimule,
NomComplet = simule.NomCompletSimule,
Simule = true,
};
}
// Ni en-tête, ni utilisateur simulé : on n'invente pas d'identité.
// Le livre sera enregistré sans traçabilité plutôt que sous un faux nom.
return UtilisateurCourant.Anonyme;
}
private static string? Valeur(IHeaderDictionary? entetes, string nom)
{
if (entetes is null || !entetes.TryGetValue(nom, out var valeurs))
{
return null;
}
var valeur = valeurs.ToString().Trim();
return valeur.Length == 0 ? null : valeur;
}
}