case 4727 - attached-by override for admins only, export download by its owner only

OverrideUserId on attachment upload is used only from SuperUser or a user
with change rights on User, otherwise ignored. Export files carry the
user's id in their name and export/download serves only the key user's
own export files. F25 (4727 step 5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 21:39:50 -07:00
parent b0e3933a9f
commit cce07f3e6b
4 changed files with 12 additions and 6 deletions

View File

@@ -269,7 +269,9 @@ namespace AyaNova.Api.Controllers
{
AttachToAType = uploadFormData.FormFieldData["AttachToAType"].ToString();
//for v8 migrate purposes
if (uploadFormData.FormFieldData.ContainsKey("OverrideUserId"))
//case 4727 (F25): only SuperUser or a user who may manage users may set it, otherwise it is ignored
if (uploadFormData.FormFieldData.ContainsKey("OverrideUserId") &&
(UserIdFromContext.Id(HttpContext.Items) == 1 || Authorized.HasModifyRole(HttpContext.Items, AyaType.User)))
OverrideUserId = long.Parse(uploadFormData.FormFieldData["OverrideUserId"].ToString());
AttachToObjectId = uploadFormData.FormFieldData["AttachToObjectId"].ToString();

View File

@@ -80,7 +80,8 @@ namespace AyaNova.Api.Controllers
log.LogDebug($"Instantiating biz object handler for {selectedRequest.AType}");
var biz = BizObjectFactory.GetBizObject(selectedRequest.AType, ct, UserId, UserRoles, UserTranslationId);
log.LogDebug($"Fetching data for {selectedRequest.SelectedRowIds.Length} {selectedRequest.AType} items");
string baseFileName = FileUtil.StringToSafeFileName($"{selectedRequest.AType.ToString().ToLowerInvariant()}-{FileUtil.GetSafeDateFileName()}");
//case 4727 (F25): the name carries the user's id, so only that user can download it
string baseFileName = FileUtil.StringToSafeFileName($"{selectedRequest.AType.ToString().ToLowerInvariant()}-u{UserId}-{FileUtil.GetSafeDateFileName()}");
string outputSourceFileName = baseFileName + ".json";
string outputSourceFullPath = System.IO.Path.Combine(FileUtil.TemporaryFilesFolder, outputSourceFileName);
@@ -130,13 +131,15 @@ namespace AyaNova.Api.Controllers
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null)
var DownloadUser = await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct);
if (DownloadUser == null)
{
await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
}
if (!FileUtil.TemporaryFileExists(fileName))
//case 4727 (F25): only the caller's own export file (see the name in render), no other temporary file
if (fileName == null || !fileName.Contains($"-u{DownloadUser.Id}-") || !fileName.EndsWith(".json") || !FileUtil.TemporaryFileExists(fileName))
{
await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//fishing protection
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));