Compare commits
26 Commits
main
...
loic-masi/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90dc1af5ec | ||
|
|
7fd3137156 | ||
|
|
76f8ec1cf5 | ||
|
|
50ca65c239 | ||
|
|
3451f8228a | ||
|
|
777ddb4069 | ||
|
|
2c144a2b5f | ||
|
|
ab488843ac | ||
|
|
e08a73dbd5 | ||
|
|
c23f481080 | ||
|
|
a24fa109d4 | ||
|
|
05f3dd5462 | ||
|
|
560ac04985 | ||
|
|
d5ca4dcb47 | ||
|
|
265297519c | ||
|
|
8be0047eee | ||
|
|
fd574b0b1f | ||
|
|
097f7b8a20 | ||
|
|
cd9da8a9a3 | ||
|
|
2f563438a2 | ||
|
|
6fd75d1d7f | ||
|
|
c7a72eeea2 | ||
|
|
7056a30e60 | ||
|
|
74ff359049 | ||
|
|
3a116f9bae | ||
|
|
4157991805 |
23
.gitea/workflows/deploy-test-keycloak.yaml
Normal file
23
.gitea/workflows/deploy-test-keycloak.yaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: Deploiement Test Keycloak
|
||||||
|
run-nale: ${{ gitea.actor }} déploie test keycloak.
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- loic-masi/ajout-auth-keycloak
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Deploiement-Test-Keycloak:
|
||||||
|
name: Build et Déploiement
|
||||||
|
runs-on: test-keycloak
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Récupération du code source
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Redémarrage Docker
|
||||||
|
run: |
|
||||||
|
echo "Démarrage du déploiement Docker"
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d --build
|
||||||
|
echo "Déploiement terminé !"
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
@@ -38,6 +39,7 @@ public class ArtisteController : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="page">Le numéro de page pour la pagination des artistes (par défaut à 0).</param>
|
/// <param name="page">Le numéro de page pour la pagination des artistes (par défaut à 0).</param>
|
||||||
/// <returns>Redirection.</returns>
|
/// <returns>Redirection.</returns>
|
||||||
|
[Authorize(Roles = "ADMIN")]
|
||||||
public IActionResult Index(int page = 0)
|
public IActionResult Index(int page = 0)
|
||||||
{
|
{
|
||||||
int artistes_par_page = this.configuration.GetValue<int>("Webzine:NombreDeLignesAdministration");
|
int artistes_par_page = this.configuration.GetValue<int>("Webzine:NombreDeLignesAdministration");
|
||||||
@@ -93,6 +95,7 @@ public class ArtisteController : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">L'identifiant de l'artiste à modifier. </param>
|
/// <param name="id">L'identifiant de l'artiste à modifier. </param>
|
||||||
/// <returns>Redirection.</returns>
|
/// <returns>Redirection.</returns>
|
||||||
|
[Authorize(Roles = "ADMIN")]
|
||||||
public IActionResult Edit(int id)
|
public IActionResult Edit(int id)
|
||||||
{
|
{
|
||||||
var artiste = this.artisteRepository.Find(id);
|
var artiste = this.artisteRepository.Find(id);
|
||||||
@@ -110,6 +113,7 @@ public class ArtisteController : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">Paramètre d'un artiste.</param>
|
/// <param name="model">Paramètre d'un artiste.</param>
|
||||||
/// <returns>Redirection sur Index.</returns>
|
/// <returns>Redirection sur Index.</returns>
|
||||||
|
[Authorize(Roles = "ADMIN")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult Edit(ArtisteEditViewModel model)
|
public IActionResult Edit(ArtisteEditViewModel model)
|
||||||
{
|
{
|
||||||
@@ -133,6 +137,7 @@ public class ArtisteController : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">L'identifiant de l'artiste à supprimer. </param>
|
/// <param name="id">L'identifiant de l'artiste à supprimer. </param>
|
||||||
/// <returns>Redirection.</returns>
|
/// <returns>Redirection.</returns>
|
||||||
|
[Authorize(Roles = "ADMIN")]
|
||||||
public IActionResult Delete(int id)
|
public IActionResult Delete(int id)
|
||||||
{
|
{
|
||||||
var artiste = this.artisteRepository.Find(id);
|
var artiste = this.artisteRepository.Find(id);
|
||||||
@@ -156,6 +161,7 @@ public class ArtisteController : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">L'artiste à supprimer.</param>
|
/// <param name="model">L'artiste à supprimer.</param>
|
||||||
/// <returns>Redirige vers la page d'index d'admin artiste.</returns>
|
/// <returns>Redirige vers la page d'index d'admin artiste.</returns>
|
||||||
|
[Authorize(Roles = "ADMIN")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult Delete(AdminArtisteForm model)
|
public IActionResult Delete(AdminArtisteForm model)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace Webzine.WebApplication.Areas.Administration.Controllers
|
namespace Webzine.WebApplication.Areas.Administration.Controllers
|
||||||
{
|
{
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
@@ -40,6 +41,7 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="page">Le numéro de page pour la pagination des commentaires (par défaut à 0).</param>
|
/// <param name="page">Le numéro de page pour la pagination des commentaires (par défaut à 0).</param>
|
||||||
/// <returns>La vue Index avec le ViewModel contenant la liste des commentaires.</returns>
|
/// <returns>La vue Index avec le ViewModel contenant la liste des commentaires.</returns>
|
||||||
|
[Authorize(Roles = "ADMIN")]
|
||||||
public IActionResult Index(int page = 0)
|
public IActionResult Index(int page = 0)
|
||||||
{
|
{
|
||||||
int commentaires_par_page = this.configuration.GetValue<int>("Webzine:NombreDeLignesAdministration");
|
int commentaires_par_page = this.configuration.GetValue<int>("Webzine:NombreDeLignesAdministration");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
using Webzine.Business.Contracts;
|
using Webzine.Business.Contracts;
|
||||||
@@ -32,6 +33,7 @@ public class DashboardController : Controller
|
|||||||
/// Affiche le tableau de bord de l'administration.
|
/// Affiche le tableau de bord de l'administration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>La vue Index du tableau de bord.</returns>
|
/// <returns>La vue Index du tableau de bord.</returns>
|
||||||
|
[Authorize(Roles = "ADMIN")]
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
DashboardDTO data = this.dashboardService.GetDashboardData();
|
DashboardDTO data = this.dashboardService.GetDashboardData();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace Webzine.WebApplication.Areas.Administration.Controllers
|
namespace Webzine.WebApplication.Areas.Administration.Controllers
|
||||||
{
|
{
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
@@ -40,6 +41,7 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="page">Le numero de page pour la pagination des styles (par defaut a 0).</param>
|
/// <param name="page">Le numero de page pour la pagination des styles (par defaut a 0).</param>
|
||||||
/// <returns>La vue Index avec la liste des styles.</returns>
|
/// <returns>La vue Index avec la liste des styles.</returns>
|
||||||
|
[Authorize(Roles = "ADMIN")]
|
||||||
public IActionResult Index(int page = 0)
|
public IActionResult Index(int page = 0)
|
||||||
{
|
{
|
||||||
int styles_par_page = this.configuration.GetValue<int>("Webzine:NombreDeLignesAdministration");
|
int styles_par_page = this.configuration.GetValue<int>("Webzine:NombreDeLignesAdministration");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
|||||||
|
|
||||||
using Business.Contracts;
|
using Business.Contracts;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ public class TitreController : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="page">Le numéro de page pour la pagination des titres (par défaut à 0).</param>
|
/// <param name="page">Le numéro de page pour la pagination des titres (par défaut à 0).</param>
|
||||||
/// <returns>La vue Index avec le ViewModel contenant la liste des titres.</returns>
|
/// <returns>La vue Index avec le ViewModel contenant la liste des titres.</returns>
|
||||||
|
[Authorize(Roles = "ADMIN")]
|
||||||
public IActionResult Index(int page = 0)
|
public IActionResult Index(int page = 0)
|
||||||
{
|
{
|
||||||
int titres_par_page = this.configuration.GetValue<int>("Webzine:NombreDeLignesAdministration");
|
int titres_par_page = this.configuration.GetValue<int>("Webzine:NombreDeLignesAdministration");
|
||||||
|
|||||||
49
Webzine.WebApplication/Controllers/AccountController.cs
Normal file
49
Webzine.WebApplication/Controllers/AccountController.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
namespace Webzine.WebApplication.Controllers
|
||||||
|
{
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
public class AccountController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet("/account/login")]
|
||||||
|
public IActionResult Login(string? returnUrl = "/")
|
||||||
|
{
|
||||||
|
return this.Challenge(
|
||||||
|
new AuthenticationProperties
|
||||||
|
{
|
||||||
|
RedirectUri = string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl,
|
||||||
|
},
|
||||||
|
OpenIdConnectDefaults.AuthenticationScheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("/account/logout")]
|
||||||
|
public IActionResult Logout()
|
||||||
|
{
|
||||||
|
return this.SignOut(
|
||||||
|
new AuthenticationProperties
|
||||||
|
{
|
||||||
|
RedirectUri = "/",
|
||||||
|
},
|
||||||
|
CookieAuthenticationDefaults.AuthenticationScheme,
|
||||||
|
OpenIdConnectDefaults.AuthenticationScheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("/account/access-denied")]
|
||||||
|
public IActionResult AccessDenied()
|
||||||
|
{
|
||||||
|
return this.View();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("/account/auth-error")]
|
||||||
|
public IActionResult AuthError(string? message = null)
|
||||||
|
{
|
||||||
|
this.ViewData["Message"] = string.IsNullOrWhiteSpace(message)
|
||||||
|
? "Une erreur est survenue pendant la connexion."
|
||||||
|
: message;
|
||||||
|
|
||||||
|
return this.View();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ namespace Webzine.WebApplication.Controllers
|
|||||||
/// Affichage de la page Recherche depuis le header de l'app.
|
/// Affichage de la page Recherche depuis le header de l'app.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mot">Nom d'artiste ou de titre.</param>
|
/// <param name="mot">Nom d'artiste ou de titre.</param>
|
||||||
/// <returns>Page de recherche avec les r<EFBFBD>sultats.</returns>
|
/// <returns>Page de recherche avec les résultats.</returns>
|
||||||
public IActionResult Index(string mot)
|
public IActionResult Index(string mot)
|
||||||
{
|
{
|
||||||
// Logger la recherche.
|
// Logger la recherche.
|
||||||
|
|||||||
@@ -34,29 +34,51 @@
|
|||||||
var methode = context.Request.Method;
|
var methode = context.Request.Method;
|
||||||
var endpoint = context.Request.Path;
|
var endpoint = context.Request.Path;
|
||||||
var traceId = context.TraceIdentifier; // Identifiant unique généré par .NET
|
var traceId = context.TraceIdentifier; // Identifiant unique généré par .NET
|
||||||
|
var exceptionLevee = false;
|
||||||
|
|
||||||
this.logger.LogInformation("[IN] TraceId: {traceId} | Méthode: {methode} | Endpoint: {endpoint}", traceId, methode, endpoint);
|
this.logger.LogInformation("[IN] TraceId: {traceId} | Méthode: {methode} | Endpoint: {endpoint}", traceId, methode, endpoint);
|
||||||
|
|
||||||
await this.next(context);
|
try
|
||||||
|
|
||||||
// (Après le contrôleur)
|
|
||||||
chronometre.Stop(); // arrête le chrono
|
|
||||||
var tempsEcoule = chronometre.ElapsedMilliseconds;
|
|
||||||
|
|
||||||
var httpCode = context.Response.StatusCode; // exemple: 200, 404, 500
|
|
||||||
|
|
||||||
// --- OUT ---
|
|
||||||
if (httpCode >= 500)
|
|
||||||
{
|
{
|
||||||
this.logger.LogError("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint);
|
await this.next(context);
|
||||||
}
|
}
|
||||||
else if (httpCode >= 400)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
this.logger.LogWarning("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint);
|
exceptionLevee = true;
|
||||||
|
chronometre.Stop();
|
||||||
|
this.logger.LogError(
|
||||||
|
exception,
|
||||||
|
"[EXCEPTION] TraceId: {traceId} | Temps: {tempsEcoule} ms | Endpoint: {endpoint} | Type: {exceptionType} | Message: {exceptionMessage}",
|
||||||
|
traceId,
|
||||||
|
chronometre.ElapsedMilliseconds,
|
||||||
|
endpoint,
|
||||||
|
exception.GetType().FullName,
|
||||||
|
exception.Message);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
else
|
finally
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint);
|
if (chronometre.IsRunning)
|
||||||
|
{
|
||||||
|
chronometre.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempsEcoule = chronometre.ElapsedMilliseconds;
|
||||||
|
var httpCode = exceptionLevee ? StatusCodes.Status500InternalServerError : context.Response.StatusCode; // exemple: 200, 404, 500
|
||||||
|
|
||||||
|
// --- OUT ---
|
||||||
|
if (httpCode >= 500)
|
||||||
|
{
|
||||||
|
this.logger.LogError("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint);
|
||||||
|
}
|
||||||
|
else if (httpCode >= 400)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.logger.LogInformation("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
// L'erreur SA1200 (ordre des using directives) est desactivee pour Program.cs
|
// L'erreur SA1200 (ordre des using directives) est desactivee pour Program.cs
|
||||||
#pragma warning disable SA1200
|
#pragma warning disable SA1200
|
||||||
|
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using NLog.Web;
|
using NLog.Web;
|
||||||
@@ -23,30 +32,298 @@ using Webzine.WebApplication.Interceptors;
|
|||||||
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
||||||
logger.Debug("init main");
|
logger.Debug("init main");
|
||||||
|
|
||||||
|
static void AddRoleClaim(ClaimsIdentity identity, string? roleValue)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(roleValue) &&
|
||||||
|
!identity.HasClaim(claim => claim.Type == "role" && claim.Value == roleValue))
|
||||||
|
{
|
||||||
|
identity.AddClaim(new Claim("role", roleValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AddKeycloakRolesFromJson(ClaimsIdentity identity, string? json, string? clientId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var document = JsonDocument.Parse(json);
|
||||||
|
|
||||||
|
if (document.RootElement.TryGetProperty("realm_access", out var realmAccessElement) &&
|
||||||
|
realmAccessElement.TryGetProperty("roles", out var realmRolesElement))
|
||||||
|
{
|
||||||
|
foreach (var role in realmRolesElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
AddRoleClaim(identity, role.GetString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(clientId) &&
|
||||||
|
document.RootElement.TryGetProperty("resource_access", out var resourceAccessElement) &&
|
||||||
|
resourceAccessElement.TryGetProperty(clientId, out var clientAccessElement) &&
|
||||||
|
clientAccessElement.TryGetProperty("roles", out var clientRolesElement))
|
||||||
|
{
|
||||||
|
foreach (var role in clientRolesElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
AddRoleClaim(identity, role.GetString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AddKeycloakRolesFromClaims(ClaimsIdentity identity, ClaimsPrincipal principal, string? clientId)
|
||||||
|
{
|
||||||
|
var realmAccessClaim = principal.FindFirst("realm_access")?.Value;
|
||||||
|
if (!string.IsNullOrWhiteSpace(realmAccessClaim))
|
||||||
|
{
|
||||||
|
AddKeycloakRolesFromJson(identity, $$"""{ "realm_access": {{realmAccessClaim}} }""", clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var resourceAccessClaim = principal.FindFirst("resource_access")?.Value;
|
||||||
|
if (!string.IsNullOrWhiteSpace(resourceAccessClaim))
|
||||||
|
{
|
||||||
|
AddKeycloakRolesFromJson(identity, $$"""{ "resource_access": {{resourceAccessClaim}} }""", clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AddKeycloakRolesFromAccessToken(ClaimsIdentity identity, string? accessToken, string? clientId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(accessToken))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
|
||||||
|
var payload = JsonSerializer.Serialize(token.Payload);
|
||||||
|
|
||||||
|
AddKeycloakRolesFromJson(identity, payload, clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task LogKeycloakMetadataAsync(IConfiguration configuration, Logger logger)
|
||||||
|
{
|
||||||
|
var metadataAddress = configuration["Keycloak:MetadataAddress"];
|
||||||
|
if (string.IsNullOrWhiteSpace(metadataAddress))
|
||||||
|
{
|
||||||
|
var authority = configuration["Keycloak:Authority"]?.TrimEnd('/');
|
||||||
|
metadataAddress = string.IsNullOrWhiteSpace(authority)
|
||||||
|
? null
|
||||||
|
: authority + "/.well-known/openid-configuration";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(metadataAddress))
|
||||||
|
{
|
||||||
|
logger.Warn("Diagnostic Keycloak ignore : aucune adresse de metadata configuree.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var handler = new HttpClientHandler
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback =
|
||||||
|
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
|
||||||
|
};
|
||||||
|
using var httpClient = new HttpClient(handler);
|
||||||
|
using var response = await httpClient.GetAsync(metadataAddress);
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
var preview = content.Length > 500 ? content[..500] : content;
|
||||||
|
|
||||||
|
logger.Info(
|
||||||
|
"Diagnostic Keycloak metadata | Url: {MetadataAddress} | Status: {StatusCode} | ContentType: {ContentType} | Body: {BodyPreview}",
|
||||||
|
metadataAddress,
|
||||||
|
(int)response.StatusCode,
|
||||||
|
response.Content.Headers.ContentType?.ToString(),
|
||||||
|
preview.Replace(Environment.NewLine, " "));
|
||||||
|
Console.WriteLine(
|
||||||
|
"Diagnostic Keycloak metadata | Url: " + metadataAddress +
|
||||||
|
" | Status: " + (int)response.StatusCode +
|
||||||
|
" | ContentType: " + response.Content.Headers.ContentType +
|
||||||
|
" | Body: " + preview.Replace(Environment.NewLine, " "));
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
logger.Error(exception, "Diagnostic Keycloak metadata impossible | Url: {MetadataAddress} | Message: {Message}", metadataAddress, exception.Message);
|
||||||
|
Console.WriteLine("Diagnostic Keycloak metadata impossible | Url: " + metadataAddress + " | Message: " + exception.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Ajoute les services necessaires pour permettre l'utilisation des
|
|
||||||
// controllers avec des vues.
|
|
||||||
builder.Services.AddControllersWithViews(options =>
|
builder.Services.AddControllersWithViews(options =>
|
||||||
|
{
|
||||||
|
options.Filters.Add<ValidationActionFilter>();
|
||||||
|
options.Filters.Add<GlobalExceptionFilter>();
|
||||||
|
})
|
||||||
|
.AddRazorRuntimeCompilation();
|
||||||
|
|
||||||
|
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
|
||||||
|
|
||||||
|
// Important derrière Nginx pour que ASP.NET Core comprenne bien :
|
||||||
|
// - le host public
|
||||||
|
// - le scheme https
|
||||||
|
builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
||||||
|
{
|
||||||
|
options.ForwardedHeaders =
|
||||||
|
ForwardedHeaders.XForwardedFor |
|
||||||
|
ForwardedHeaders.XForwardedProto |
|
||||||
|
ForwardedHeaders.XForwardedHost;
|
||||||
|
|
||||||
|
options.KnownNetworks.Clear();
|
||||||
|
options.KnownProxies.Clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services
|
||||||
|
.AddAuthentication(options =>
|
||||||
{
|
{
|
||||||
// options.Filters.Add<GlobalExceptionFilter>();
|
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||||
options.Filters.Add<ValidationActionFilter>();
|
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
|
||||||
options.Filters.Add<GlobalExceptionFilter>();
|
})
|
||||||
|
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
|
||||||
|
{
|
||||||
|
options.LoginPath = "/account/login";
|
||||||
|
options.LogoutPath = "/account/logout";
|
||||||
|
options.AccessDeniedPath = "/account/access-denied";
|
||||||
|
})
|
||||||
|
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
|
||||||
|
{
|
||||||
|
var publicOrigin = builder.Configuration["Keycloak:PublicOrigin"]?.TrimEnd('/');
|
||||||
|
var callbackPath = builder.Configuration["Keycloak:CallbackPath"] ?? "/signin-oidc";
|
||||||
|
var signedOutCallbackPath = builder.Configuration["Keycloak:SignedOutCallbackPath"] ?? "/signout-callback-oidc";
|
||||||
|
var metadataAddress = builder.Configuration["Keycloak:MetadataAddress"];
|
||||||
|
|
||||||
|
options.Authority = builder.Configuration["Keycloak:Authority"];
|
||||||
|
if (!string.IsNullOrWhiteSpace(metadataAddress))
|
||||||
|
{
|
||||||
|
options.MetadataAddress = metadataAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.ClientId = builder.Configuration["Keycloak:ClientId"];
|
||||||
|
options.ClientSecret = builder.Configuration["Keycloak:ClientSecret"];
|
||||||
|
options.ResponseType = OpenIdConnectResponseType.Code;
|
||||||
|
|
||||||
|
options.CallbackPath = callbackPath;
|
||||||
|
options.SignedOutCallbackPath = signedOutCallbackPath;
|
||||||
|
|
||||||
|
// Reverse proxy + certificat autosigné
|
||||||
|
options.RequireHttpsMetadata = false;
|
||||||
|
|
||||||
|
// Désactive PAR pour éviter l’erreur "Invalid parameter: redirect_uri"
|
||||||
|
options.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable;
|
||||||
|
|
||||||
|
options.SaveTokens = false;
|
||||||
|
options.GetClaimsFromUserInfoEndpoint = false;
|
||||||
|
|
||||||
|
options.Scope.Clear();
|
||||||
|
options.Scope.Add("openid");
|
||||||
|
options.Scope.Add("profile");
|
||||||
|
options.Scope.Add("email");
|
||||||
|
|
||||||
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
|
{
|
||||||
|
NameClaimType = "preferred_username",
|
||||||
|
RoleClaimType = "role",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Temporaire pour environnement de test avec certificat autosigné
|
||||||
|
options.BackchannelHttpHandler = new HttpClientHandler
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback =
|
||||||
|
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
|
||||||
|
};
|
||||||
|
|
||||||
|
options.Events = new OpenIdConnectEvents
|
||||||
|
{
|
||||||
|
OnAuthenticationFailed = context =>
|
||||||
|
{
|
||||||
|
logger.Error(context.Exception, "Erreur d'authentification OIDC : {Message}", context.Exception.Message);
|
||||||
|
context.HandleResponse();
|
||||||
|
context.Response.Redirect("/account/auth-error?message=" + Uri.EscapeDataString(context.Exception.Message));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
|
||||||
|
OnRedirectToIdentityProvider = context =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(publicOrigin))
|
||||||
|
{
|
||||||
|
context.ProtocolMessage.RedirectUri = publicOrigin + context.Options.CallbackPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("RedirectUri Keycloak envoyee : {RedirectUri}", context.ProtocolMessage.RedirectUri);
|
||||||
|
logger.Info("MetadataAddress Keycloak utilisee : {MetadataAddress}", context.Options.MetadataAddress);
|
||||||
|
Console.WriteLine("RedirectUri envoyé à Keycloak : " + context.ProtocolMessage.RedirectUri);
|
||||||
|
Console.WriteLine("Authority utilisée : " + context.Options.Authority);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
|
||||||
|
OnRedirectToIdentityProviderForSignOut = context =>
|
||||||
|
{
|
||||||
|
var idToken = context.HttpContext.User.FindFirst("id_token")?.Value;
|
||||||
|
if (!string.IsNullOrWhiteSpace(idToken))
|
||||||
|
{
|
||||||
|
context.ProtocolMessage.IdTokenHint = idToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(publicOrigin))
|
||||||
|
{
|
||||||
|
context.ProtocolMessage.PostLogoutRedirectUri = publicOrigin + context.Options.SignedOutCallbackPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
|
||||||
|
OnTokenValidated = context =>
|
||||||
|
{
|
||||||
|
var identity = (ClaimsIdentity)context.Principal!.Identity!;
|
||||||
|
var clientId = context.Options.ClientId;
|
||||||
|
|
||||||
|
if (context.SecurityToken is JwtSecurityToken idToken &&
|
||||||
|
!string.IsNullOrWhiteSpace(idToken.RawData))
|
||||||
|
{
|
||||||
|
identity.AddClaim(new Claim("id_token", idToken.RawData));
|
||||||
|
}
|
||||||
|
|
||||||
|
AddKeycloakRolesFromClaims(identity, context.Principal, clientId);
|
||||||
|
AddKeycloakRolesFromAccessToken(identity, context.TokenEndpointResponse?.AccessToken, clientId);
|
||||||
|
|
||||||
|
logger.Info(
|
||||||
|
"Roles Keycloak ajoutes a l'utilisateur {User}: {Roles}",
|
||||||
|
identity.Name,
|
||||||
|
string.Join(", ", identity.FindAll("role").Select(claim => claim.Value)));
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
|
||||||
|
OnRemoteFailure = context =>
|
||||||
|
{
|
||||||
|
var remoteError = context.Request.Query["error"].ToString();
|
||||||
|
var remoteDescription = context.Request.Query["error_description"].ToString();
|
||||||
|
var failureMessage = context.Failure?.Message;
|
||||||
|
var message = string.Join(
|
||||||
|
Environment.NewLine,
|
||||||
|
new[] { remoteError, remoteDescription, failureMessage }.Where(value => !string.IsNullOrWhiteSpace(value)));
|
||||||
|
|
||||||
|
logger.Error(context.Failure, "Erreur OIDC distante : {Message}", message);
|
||||||
|
Console.WriteLine("Erreur OIDC distante : " + message);
|
||||||
|
context.HandleResponse();
|
||||||
|
context.Response.Redirect("/account/auth-error?message=" + Uri.EscapeDataString(message));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ajoute les services necessaires pour permettre l'utilisation des controllers avec des vues.
|
builder.Services.AddAuthorization(options =>
|
||||||
builder.Services.AddControllersWithViews()
|
{
|
||||||
|
options.AddPolicy("RequireUser", policy => policy.RequireRole("USER", "ADMIN"));
|
||||||
// Ajoute la compilation des vues lors de l'execution de l'application.
|
options.AddPolicy("RequireAdmin", policy => policy.RequireRole("ADMIN"));
|
||||||
// Cela nous evite de recompiler l'application a chaque modification de vue.
|
});
|
||||||
// Necessite le package Nuget Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.
|
|
||||||
.AddRazorRuntimeCompilation();
|
|
||||||
|
|
||||||
// NLog: Setup NLog for Dependency injection
|
// NLog: Setup NLog for Dependency injection
|
||||||
builder.Logging.ClearProviders();
|
builder.Logging.ClearProviders();
|
||||||
builder.Host.UseNLog();
|
builder.Host.UseNLog();
|
||||||
|
|
||||||
builder.Services.Configure<SpotifySeederOptions>(builder.Configuration.GetSection("SpotifySeeder"));
|
builder.Services.Configure<SpotifySeederOptions>(builder.Configuration.GetSection("SpotifySeeder"));
|
||||||
builder.Services.AddHttpClient<SeedDataSpotify>();
|
builder.Services.AddHttpClient<SeedDataSpotify>();
|
||||||
|
|
||||||
@@ -54,10 +331,10 @@ try
|
|||||||
{
|
{
|
||||||
options.SeuilMs = builder.Configuration.GetValue<int>("EfPerformance:SeuilMs");
|
options.SeuilMs = builder.Configuration.GetValue<int>("EfPerformance:SeuilMs");
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddSingleton<EfSlowQueryInterceptor>();
|
builder.Services.AddSingleton<EfSlowQueryInterceptor>();
|
||||||
|
|
||||||
// En fonction de la configuration, utilise soit les repositories basés sur une base de données, soit les repositories basés sur des listes locales.
|
// En fonction de la configuration, utilise soit les repositories basés sur une base de données, soit les repositories basés sur des listes locales.
|
||||||
// En fonction de la configuration, utilise soit les repositories bases sur une base de donnees, soit les repositories bases sur des listes locales.
|
|
||||||
var repositoryType = builder.Configuration.GetValue<RepositoryType>("Repository");
|
var repositoryType = builder.Configuration.GetValue<RepositoryType>("Repository");
|
||||||
var seederType = builder.Configuration.GetValue<SeederType>("Seeder");
|
var seederType = builder.Configuration.GetValue<SeederType>("Seeder");
|
||||||
var shouldSeed = args.Contains("--seed");
|
var shouldSeed = args.Contains("--seed");
|
||||||
@@ -104,11 +381,13 @@ try
|
|||||||
builder.Services.AddScoped<ValidationActionFilter>();
|
builder.Services.AddScoped<ValidationActionFilter>();
|
||||||
builder.Services.AddScoped<GlobalExceptionFilter>();
|
builder.Services.AddScoped<GlobalExceptionFilter>();
|
||||||
|
|
||||||
// https://learn.microsoft.com/fr-fr/aspnet/core/performance/response-compression?view=aspnetcore-10.0#configuration
|
|
||||||
// Ajoute le service de compression des reponses HTTP pour reduire la taille des donnees envoyees au client et ameliorer les performances de l'application.
|
|
||||||
builder.Services.AddResponseCompression();
|
builder.Services.AddResponseCompression();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
await LogKeycloakMetadataAsync(builder.Configuration, logger);
|
||||||
|
|
||||||
|
// Très important avant tout middleware qui lit le scheme/host de la requête.
|
||||||
|
app.UseForwardedHeaders();
|
||||||
|
|
||||||
app.UseMiddleware<Webzine.WebApplication.Middlewares.LogTempsExecutionMiddleware>();
|
app.UseMiddleware<Webzine.WebApplication.Middlewares.LogTempsExecutionMiddleware>();
|
||||||
|
|
||||||
@@ -168,32 +447,30 @@ try
|
|||||||
|
|
||||||
app.UseResponseCompression();
|
app.UseResponseCompression();
|
||||||
|
|
||||||
// Active la possibilite de servir des fichiers statiques presents dans le dossier wwwroot.
|
|
||||||
app.UseStaticFiles(new StaticFileOptions
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
{
|
{
|
||||||
OnPrepareResponse = ctx =>
|
OnPrepareResponse = ctx =>
|
||||||
{
|
{
|
||||||
// https://learn.microsoft.com/fr-fr/aspnet/core/fundamentals/static-files?view=aspnetcore-10.0#set-http-response-headers
|
|
||||||
ctx.Context.Response.Headers.Append("Cache-Control", "public, max-age=31536000");
|
ctx.Context.Response.Headers.Append("Cache-Control", "public, max-age=31536000");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Active le middleware permettant le routage des requetes entrantes.
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|
||||||
// Appelle les routes definies dans le dossier Extensions.
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapCustomRoutes();
|
app.MapCustomRoutes();
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
// NLog: attrape les exceptions non gerees et les logger.
|
|
||||||
logger.Error(exception, "Stopped program because of exception");
|
logger.Error(exception, "Stopped program because of exception");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Assure que NLog flush tous les messages de log avant de fermer l'application.
|
|
||||||
LogManager.Shutdown();
|
LogManager.Shutdown();
|
||||||
}
|
}
|
||||||
13
Webzine.WebApplication/Views/Account/AccessDenied.cshtml
Normal file
13
Webzine.WebApplication/Views/Account/AccessDenied.cshtml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Accès refusé";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container mt-5 text-center">
|
||||||
|
<h2>Accès refusé</h2>
|
||||||
|
|
||||||
|
<p>Vous n'avez pas les droits pour accéder à cette page.</p>
|
||||||
|
|
||||||
|
<a href="/" class="btn btn-secondary">
|
||||||
|
Retour à l'accueil
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
19
Webzine.WebApplication/Views/Account/AuthError.cshtml
Normal file
19
Webzine.WebApplication/Views/Account/AuthError.cshtml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Erreur de connexion";
|
||||||
|
var message = ViewData["Message"]?.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container mt-5 text-center">
|
||||||
|
<h2>Erreur de connexion</h2>
|
||||||
|
|
||||||
|
<p>La connexion avec Keycloak n'a pas pu aboutir.</p>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(message))
|
||||||
|
{
|
||||||
|
<pre class="alert alert-danger text-start">@message</pre>
|
||||||
|
}
|
||||||
|
|
||||||
|
<a href="/" class="btn btn-secondary">
|
||||||
|
Retour à l'accueil
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
15
Webzine.WebApplication/Views/Account/Login.cshtml
Normal file
15
Webzine.WebApplication/Views/Account/Login.cshtml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Connexion";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container mt-5 text-center">
|
||||||
|
<h2>Connexion</h2>
|
||||||
|
|
||||||
|
<p>Vous allez être redirigé vers le serveur d'authentification.</p>
|
||||||
|
|
||||||
|
<a asp-controller="Account"
|
||||||
|
asp-action="Login"
|
||||||
|
class="btn btn-primary">
|
||||||
|
Se connecter avec Keycloak
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
15
Webzine.WebApplication/Views/Account/Logout.cshtml
Normal file
15
Webzine.WebApplication/Views/Account/Logout.cshtml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Déconnexion";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container mt-5 text-center">
|
||||||
|
<h2>Déconnexion</h2>
|
||||||
|
|
||||||
|
<p>Vous êtes sur le point de vous déconnecter.</p>
|
||||||
|
|
||||||
|
<form asp-controller="Account" asp-action="Logout" method="get">
|
||||||
|
<button type="submit" class="btn btn-danger">
|
||||||
|
Se déconnecter
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -58,6 +58,27 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
@if (User.Identity?.IsAuthenticated == true)
|
||||||
|
{
|
||||||
|
<span class="text-primary">Bonjour @User.Identity.Name</span>
|
||||||
|
|
||||||
|
<a asp-controller="Account"
|
||||||
|
asp-action="Logout"
|
||||||
|
class="btn btn-outline-danger btn-sm">
|
||||||
|
Déconnexion
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<a asp-controller="Account"
|
||||||
|
asp-action="Login"
|
||||||
|
class="btn btn-outline-primary btn-sm">
|
||||||
|
Connexion
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- Barre de recherche -->
|
<!-- Barre de recherche -->
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
@@ -22,9 +22,15 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Faker.Net" Version="2.0.163" />
|
<PackageReference Include="Faker.Net" Version="2.0.163" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.3.9" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.6" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.17.0" />
|
||||||
|
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.17.0" />
|
||||||
|
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.17.0" />
|
||||||
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
|
||||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.*" />
|
<PackageReference Include="NLog.Web.AspNetCore" Version="5.*" />
|
||||||
<PackageReference Include="NLog" Version="6.1.1" />
|
<PackageReference Include="NLog" Version="6.1.1" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
{
|
{
|
||||||
"Seeder": "Local",
|
"Seeder": "Local",
|
||||||
"Repository": "Db",
|
"Repository": "Local",
|
||||||
"SpotifySeeder": {
|
"SpotifySeeder": {
|
||||||
"ClientId": "",
|
"ClientId": "",
|
||||||
"ClientSecret": ""
|
"ClientSecret": ""
|
||||||
|
},
|
||||||
|
"Keycloak": {
|
||||||
|
"Authority": "https://10.4.0.131:8443/keycloak/realms/webzine-realm",
|
||||||
|
"MetadataAddress": "http://10.4.0.131:8080/keycloak/realms/webzine-realm/.well-known/openid-configuration",
|
||||||
|
"ClientId": "webzine-client",
|
||||||
|
"ClientSecret": "EEUiJ5mBmuOSElwnbNZeajLuw6yOyc8E",
|
||||||
|
"CallbackPath": "/signin-oidc",
|
||||||
|
"SignedOutCallbackPath": "/signout-callback-oidc"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
{
|
{
|
||||||
"Seeder": "Spotify",
|
"Seeder": "Local",
|
||||||
"Repository": "Db",
|
"Repository": "Local",
|
||||||
"SpotifySeeder": {
|
"SpotifySeeder": {
|
||||||
"ClientId": "390689c2fc79408b830d2f518375ef84",
|
"ClientId": "",
|
||||||
"ClientSecret": "6e98a09c77ad43ae93bc0f0560cfcbe1"
|
"ClientSecret": ""
|
||||||
|
},
|
||||||
|
"Keycloak": {
|
||||||
|
"Authority": "https://10.4.0.131:8443/keycloak/realms/webzine-realm",
|
||||||
|
"MetadataAddress": "https://10.4.0.131:8443/keycloak/realms/webzine-realm/.well-known/openid-configuration",
|
||||||
|
"PublicOrigin": "https://10.4.0.131:8443"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"SqliteConnection": "Data Source=Data/webzine.sqlite",
|
"SqliteConnection": "Data Source=Data/webzine.sqlite",
|
||||||
"PostGreSQLConnection": "Host=localhost;Port=5432;Username=admin;Password=admin123;Database=webzine_db"
|
"PostGreSQLConnection": ""
|
||||||
},
|
},
|
||||||
"SpotifySeeder": {
|
"SpotifySeeder": {
|
||||||
"ClientId": "",
|
"ClientId": "",
|
||||||
@@ -25,8 +25,17 @@
|
|||||||
"TracksPerAlbum": 40,
|
"TracksPerAlbum": 40,
|
||||||
"MaxCommentsPerTrack": 3
|
"MaxCommentsPerTrack": 3
|
||||||
},
|
},
|
||||||
|
"Keycloak": {
|
||||||
|
"Authority": "https://10.4.0.131:8443/keycloak/realms/webzine-realm",
|
||||||
|
"PublicOrigin": "https://10.4.0.131:8443",
|
||||||
|
"ClientId": "webzine-client",
|
||||||
|
"ClientSecret": "EEUiJ5mBmuOSElwnbNZeajLuw6yOyc8E",
|
||||||
|
"ResponseType": "code",
|
||||||
|
"CallbackPath": "/signin-oidc",
|
||||||
|
"SignedOutCallbackPath": "/signout-callback-oidc"
|
||||||
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"EfPerformance": {
|
"EfPerformance": {
|
||||||
"SeuilMs": 60
|
"SeuilMs": 60
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<!-- Console pour debug immédiat -->
|
<!-- Console pour debug immédiat -->
|
||||||
<target xsi:type="Console" name="console"
|
<target xsi:type="Console" name="console"
|
||||||
layout="${longdate}|${level:uppercase=true}|${logger}|${message}" />
|
layout="${longdate}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}" />
|
||||||
</targets>
|
</targets>
|
||||||
|
|
||||||
<rules>
|
<rules>
|
||||||
@@ -32,10 +32,10 @@
|
|||||||
<logger name="Webzine.Repository.*" minlevel="Debug" writeTo="allfile,ownfile-web,console" />
|
<logger name="Webzine.Repository.*" minlevel="Debug" writeTo="allfile,ownfile-web,console" />
|
||||||
|
|
||||||
<!-- Logs Microsoft en Warning+ sauf Hosting.Lifetime -->
|
<!-- Logs Microsoft en Warning+ sauf Hosting.Lifetime -->
|
||||||
<logger name="Microsoft.*" minlevel="Warn" writeTo="allfile" final="true" />
|
<logger name="Microsoft.*" minlevel="Warn" writeTo="allfile,console" final="true" />
|
||||||
<logger name="Microsoft.Hosting.Lifetime*" minlevel="Info" writeTo="allfile,console" final="true" />
|
<logger name="Microsoft.Hosting.Lifetime*" minlevel="Info" writeTo="allfile,console" final="true" />
|
||||||
|
|
||||||
<!-- Tous les autres logs (y compris System) en Info+ -->
|
<!-- Tous les autres logs (y compris System) en Info+ -->
|
||||||
<logger name="*" minlevel="Info" writeTo="allfile" />
|
<logger name="*" minlevel="Info" writeTo="allfile,console" />
|
||||||
</rules>
|
</rules>
|
||||||
</nlog>
|
</nlog>
|
||||||
|
|||||||
Reference in New Issue
Block a user