Skip to content

VPR-59 [3/6] CMS migration: import, bulk encrypt, user photos, rate limiting#247

Closed
rlorenzo wants to merge 3 commits into
VPR-59-cms-2-content-navfrom
VPR-59-cms-3-import-photos
Closed

VPR-59 [3/6] CMS migration: import, bulk encrypt, user photos, rate limiting#247
rlorenzo wants to merge 3 commits into
VPR-59-cms-2-content-navfrom
VPR-59-cms-3-import-photos

Conversation

@rlorenzo

@rlorenzo rlorenzo commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Part 3 of 6 (stacks on #246 — the diff below shows only this slice).

Scope

  • CmsFileImportService: import files from the legacy webroot (source containment-checked under configurable CMS:LegacyWebrootPath) with move semantics and oldURL tracking, optional SVMSecure.{folder} default permission; bulk encrypt with per-file results (skips already-encrypted).
  • UI: ImportFiles.vue, BulkEncrypt.vue.
  • User photos: CMSUserPhotoController + CmsUserPhotoService (by-mail/by-login/by-iam), serving alternate ProfilePhotos\{iamId}.jpg and delegating ID-card photos + nopic fallback to the existing Students IPhotoService (no duplicate pipeline).
  • Download rate limiting on /CMS/Files: strict per-user token bucket for ZIP requests (archives are assembled in memory), generous sliding window for single files; keys on CAS login with proxy-resolved client-IP fallback for anonymous requests; 429 + Retry-After; configurable under CMS:DownloadRateLimit with safe defaults.

rlorenzo added 3 commits July 2, 2026 14:38
- POST /api/cms/files/import moves files from the legacy VIPER webroot
  (CMS:LegacyWebrootPath) into the managed store with per-file results;
  the original path is stored as oldURL so existing links keep working,
  matching legacy move-and-track semantics; source paths are contained
  to the webroot and removed only after a fully successful import
- POST /api/cms/files/bulk-encrypt encrypts unencrypted files in place
  with per-file results; a failed key save rolls the file back to
  plaintext so disk and DB never disagree, and batch failures clear the
  change tracker so one bad file cannot poison the rest
- UI: Import page (paths textarea keeps failures for retry) and Bulk
  Encrypt page (multi-select over unencrypted files); list pages now
  surface fetch errors instead of showing silent empty tables
- /api/cms/photos serves user photos to any authenticated user by
  mailId, loginId, or iamId (legacy userPhoto.cfc parity), resolving
  ids through AAUD
- altPhoto=true serves the alternate ProfilePhotos/{iamId}.jpg when
  present; invalid or traversal-shaped ids fall back to the default
  photo without touching disk outside the photo roots
- ID-card photo lookup, caching, and the nopic fallback reuse the
  Students area PhotoService rather than duplicating the pipeline
- ZIP requests get a strict per-user token bucket (archives are
  assembled in memory); single files get a generous sliding window
- Buckets key on CAS login, falling back to the proxy-resolved client
  IP for anonymous requests; rejections return 429 with Retry-After
- Limits are configurable under CMS:DownloadRateLimit; invalid values
  fall back to defaults instead of failing downloads

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the next slice of the CMS migration by introducing admin tooling for legacy file ingestion/encryption, a CMS-specific user photo API that reuses the Students photo pipeline, and download rate limiting for /CMS/Files to protect memory-heavy ZIP generation.

Changes:

  • Add CMS file import + bulk encryption backend (CmsFileImportService) and expose new endpoints under /api/cms/files.
  • Add CMS user photo API (/api/cms/photos/...) with AAUD ID resolution and optional alternate profile-photo lookup.
  • Add configurable per-user/IP download rate limiting for /CMS/Files (separate buckets for ZIP vs single-file requests), plus new CMS UI pages/routes for Import + Bulk Encrypt.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
web/Program.cs Registers CMS download rate limiting services and adds UseRateLimiter() after auth.
web/Areas/CMS/Services/CmsUserPhotoService.cs Resolves identifiers via AAUD and serves alternate profile photos with traversal protection, falling back to Students IPhotoService.
web/Areas/CMS/Services/CmsFileImportService.cs Implements legacy-webroot import (move semantics + OldUrl tracking) and bulk encryption with per-file results.
web/Areas/CMS/Services/CmsDownloadRateLimiting.cs Defines the CMS download rate-limit policy and rejection behavior (429 + Retry-After).
web/Areas/CMS/Models/DTOs/CmsFileImportDtos.cs Adds request/response DTOs for import and bulk encryption operations.
web/Areas/CMS/Controllers/CMSUserPhotoController.cs Adds authenticated CMS photo endpoints (by-mail/by-login/by-iam).
web/Areas/CMS/Controllers/CMSFilesController.cs Wires new import and bulk-encrypt endpoints into the CMS file management API.
web/Areas/CMS/Controllers/CMSController.cs Applies rate limiting policy to the /CMS/Files download handler.
VueApp/src/CMS/router/routes.ts Adds CMS routes for the new Import and Bulk Encrypt pages.
VueApp/src/CMS/pages/ImportFiles.vue New UI to submit legacy paths for import and display per-path results.
VueApp/src/CMS/pages/Files.vue Adds a user-visible error notification on failed file list load.
VueApp/src/CMS/pages/FileAuditLog.vue Adds a user-visible error notification on failed audit log load.
VueApp/src/CMS/pages/CmsHome.vue Adds navigation links to Import and Bulk Encrypt admin tools.
VueApp/src/CMS/pages/BulkEncrypt.vue New UI to list unencrypted files, select, confirm, and bulk-encrypt with results display.
test/CMS/CmsUserPhotoServiceTests.cs Unit tests for AAUD resolution + alternate photo lookup + traversal defense.
test/CMS/CmsFileImportServiceTests.cs Unit tests for import containment/move semantics/default permission + bulk encrypt behavior.
test/CMS/CmsDownloadRateLimitingTests.cs Unit tests for ZIP detection and partition-key behavior (login vs IP).

Comment on lines +205 to +211
$q.dialog({
title: "Encrypt Files",
message: `Encrypt ${selected.value.length} file(s) in place?`,
cancel: { label: "Cancel", flat: true },
persistent: true,
ok: { label: "Encrypt", color: "primary", unelevated: true },
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already fixed on the branch: the confirmation uses inflect("file", count) per the project convention. In #253.

Comment on lines +17 to +21
<p
class="text-body2 text-grey-8"
style="max-width: 60rem"
>
Moves files out of the legacy VIPER webroot into the managed file store. The original path is saved as the

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already fixed on the branch: the inline width moved to the shared dialog classes. In #253.

Comment on lines +51 to +53
.LogInformation("Download rate limit hit for {PartitionKey} on {Path}",
GetPartitionKey(httpContext),
LogSanitizer.SanitizeString(httpContext.Request.Path));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the restack: the partition key is sanitized before logging — for anonymous requests it falls back to a client IP derived from forwardable headers. In #251.

Comment on lines +163 to +166
_context.ChangeTracker.Clear();
_storage.DeleteManagedFile(finalPath);
result.Message = ex.InnerException?.Message ?? ex.Message;
return result;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already fixed on the branch: source resolution returns fixed messages ('Path is not valid.', 'File not found.', etc.); no exception or inner-exception text reaches the API response. In #251.

@rlorenzo

rlorenzo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by the restacked 3-PR stack: #251 (files/photos/import/rate-limit backend) -> #252 (content blocks/left nav/link collections backend) -> #253 (management SPA). These historical cut-point slices predated the branch's later OS-independent path handling and cleanup commits, so they failed CI on Linux runners; the new slices are built from the CI-verified branch tip, carry every review fix (see thread replies), and each passes the full gate set. All feedback on this PR has been answered inline.

@rlorenzo rlorenzo closed this Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants