Ouvrir l'export de configuration aux apps visiteur
`Configuration/{id}/export` portait `[Authorize(AppReadAccess)]`, mais ASP.NET
Core combine les `[Authorize]` de la classe et de l'action : le controleur exige
`ContentEditor`, qu'une cle API n'a pas. La cle authentifiait la requete, puis
l'autorisation la refusait — 403 sans corps. Cote mymuseum-visitapp, le
telechargement d'une visite echouait donc systematiquement.
Seul `[AllowAnonymous]` court-circuite la policy du controleur ; le controle
d'acces se fait dans l'action, qui declenche le schema ApiKey explicitement et
verifie que la cle porte bien l'instance de la configuration demandee. Meme
correctif que InstanceController.GetDetail.
Les trois `catch` renvoyaient `null` : l'app recevait un 200 vide et croyait la
visite exportee. Ils renvoient les codes qui etaient deja ecrits, en commentaire.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1e1a36ad5a
commit
9cc45c5767
@ -12,6 +12,7 @@ using ManagerService.Data;
|
||||
using ManagerService.Data.SubSection;
|
||||
using ManagerService.DTOs;
|
||||
using ManagerService.Services;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@ -379,14 +380,28 @@ namespace ManagerService.Controllers
|
||||
/// Export a configuration
|
||||
/// </summary>
|
||||
/// <param name="id">Id of configuration to export</param>
|
||||
/// <param name="language">Language to export</param>
|
||||
[Authorize(Policy = ManagerService.Service.Security.Policies.AppReadAccess)]
|
||||
/// <param name="language">Language to export</param>
|
||||
/// <remarks>
|
||||
/// Ouverte aux apps visiteur par <c>X-Api-Key</c> : c'est l'appel que fait
|
||||
/// mymuseum-visitapp pour télécharger une visite hors ligne.
|
||||
///
|
||||
/// ⚠️ <c>[Authorize(AppReadAccess)]</c> ne suffisait pas : ASP.NET Core **combine**
|
||||
/// les <c>[Authorize]</c> de la classe et de l'action. Le contrôleur exige
|
||||
/// <c>ContentEditor</c>, qu'une clé API n'a pas — la clé authentifiait donc la
|
||||
/// requête, puis l'autorisation la refusait : 403 sans corps, et côté app un
|
||||
/// téléchargement qui échouait sans rien dire. Seul <c>[AllowAnonymous]</c>
|
||||
/// court-circuite la policy du contrôleur ; le contrôle d'accès se fait ici.
|
||||
/// Même correctif que <see cref="InstanceController.GetDetail"/>.
|
||||
/// </remarks>
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(FileContentResult), 200)]
|
||||
[ProducesResponseType(typeof(string), 400)]
|
||||
[ProducesResponseType(typeof(string), 401)]
|
||||
[ProducesResponseType(typeof(string), 403)]
|
||||
[ProducesResponseType(typeof(string), 404)]
|
||||
[ProducesResponseType(typeof(string), 500)]
|
||||
[HttpGet("{id}/export")]
|
||||
public FileContentResult Export(string id, [FromQuery] string language)
|
||||
public async Task<IActionResult> Export(string id, [FromQuery] string language)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -398,6 +413,26 @@ namespace ManagerService.Controllers
|
||||
if (configuration == null)
|
||||
throw new KeyNotFoundException("Configuration does not exist");
|
||||
|
||||
// Le schéma ApiKey n'est pas le schéma par défaut : sur une action
|
||||
// [AllowAnonymous] il faut le déclencher explicitement.
|
||||
var apiKeyAuth = await HttpContext.AuthenticateAsync("ApiKey");
|
||||
var keyInstanceId = apiKeyAuth.Succeeded
|
||||
? apiKeyAuth.Principal?.FindFirst(ManagerService.Service.Security.ClaimTypes.InstanceId)?.Value
|
||||
: null;
|
||||
|
||||
// Ne PAS déduire « utilisateur du manager » d'un claim de permission : le
|
||||
// handler de clé API pose lui aussi le claim Viewer.
|
||||
var isManager = !apiKeyAuth.Succeeded
|
||||
&& User?.Identity?.IsAuthenticated == true
|
||||
&& User.HasClaim(ManagerService.Service.Security.ClaimTypes.Permission,
|
||||
ManagerService.Service.Security.Permissions.Viewer);
|
||||
|
||||
if (!isManager && keyInstanceId == null)
|
||||
return new ObjectResult("Authentication required") { StatusCode = 401 };
|
||||
|
||||
if (!isManager && keyInstanceId != configuration.InstanceId)
|
||||
return new ObjectResult("This API key does not grant access to this configuration") { StatusCode = 403 };
|
||||
|
||||
// Les entités, pas seulement leurs DTO : la collecte des ressources passe
|
||||
// par GetReferencedResourceIds, qui vit sur le sous-type.
|
||||
List<Section> sections = _myInfoMateDbContext.Sections.Where(s => s.ConfigurationId == configuration.Id).ToList();
|
||||
@ -436,20 +471,19 @@ namespace ManagerService.Controllers
|
||||
FileDownloadName = fileName
|
||||
};
|
||||
}
|
||||
// Les trois `catch` renvoyaient `null` : l'app recevait un 200 vide et croyait
|
||||
// la visite exportée. Les codes ci-dessous étaient déjà écrits, en commentaire.
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
return null;
|
||||
//return new BadRequestObjectResult(ex.Message) { };
|
||||
return new BadRequestObjectResult(ex.Message);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
return null;
|
||||
//return new NotFoundObjectResult(ex.Message) { };
|
||||
return new NotFoundObjectResult(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return null;
|
||||
//return new ObjectResult(ex.Message) { StatusCode = 500 };
|
||||
return new ObjectResult(ex.Message) { StatusCode = 500 };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user