You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(job-queue): generic job endpoint refuses queues that have a dedicated endpoint (#37883) - #37946
The generic job endpoint (POST /api/v1/jobs/{queueName} and /upload) refuses, with 403 naming the real route, the five queues whose entry is a dedicated endpoint: folderBulkDuplicate, folderBulkDelete, assetBulkUpload, maintenanceFixAssets, maintenanceCleanAssets. Nothing is created on refusal.
Every core queue declares its entry point: @JobQueueEntryPoint("<queue>") on the dedicated REST method, or @Queue(genericEntry = true) for queues fed by the generic endpoint (importContentlets, bulkRefreshContentlets, failSuccess, demo). JobQueueEntryPoints reads this from the Jandex index and builds the route in the message from the annotated method's own @Path.
Plugin queues (not in the index) stay generic and log one warning. With no index the registry fails closed.
openapi.yaml regenerated (the 403 description on both creation methods).
Checklist
Tests: JobQueueEntryPointsTest (7), JobQueueEntryPointsCoverageTest (2, one declaration per core queue), JobQueueDomainQueuesBypassIntegrationTest (8, registered in Junit5Suite1). Regression green: FolderBulkDuplicateResourceIT, FolderBulkDeleteResourceIT, BulkUploadResourceIT, MaintenanceResourceIntegrationTest, JobQueueHelperIntegrationTest.
Translations (n/a)
Security Implications Contemplated: closes a missing authorization on the maintenance queues. The dedicated endpoints require admin and the Maintenance portlet; before this change any back-end user could create those jobs through the generic endpoint (reproduced on main).
Additional Info
Rollback: REST contract change, 200 to 403 on five queues, no response shape added. Expected rollback safe (M-3 of docs/core/ROLLBACK_UNSAFE_CATEGORIES.md); @Queue gains a member with a default (M-4, compatible).
Known gaps (details in the handover comment on #37883):
The check in JobQueueManagerAPIImpl.start() that refuses to start the queue manager when a core queue does not have exactly one declaration is not implemented; only the build-time coverage test guards it. The spec still says "fails startup", so either implement it (checking first what InitServlet.init() does when start() throws) or align the spec wording.
No doc for new queues yet, and the proposed ADR ("every job queue declares its entry point, and the framework enforces it") is not written.
Reviewed the full diff against origin/issue-37883-job-queue-domain-queues-bypass. The design is clean: entry points are declared on the annotated methods and read from the Jandex index, routes in the 403 message come from the real @Path (verified ForbiddenException → HttpStatusCodeException.getFormattedMessage does String.format, so the %s placeholders resolve), nothing is created on refusal, and the gate is placed after auth but before createJob on both creation methods. Test coverage (unit + coverage guard + 8 integration cases, registered in Junit5Suite1) is thorough.
New Issues
🟡 Medium: dotCMS/src/main/java/com/dotcms/jobs/business/api/JobQueueEntryPoints.java:88,110 — the genericCoreQueues field is populated in the constructor but never read. In the index-present branch of resolve() (lines 153–161), a core queue that has @Queue but declares neither genericEntry = true nor a @JobQueueEntryPoint is not in dedicatedRoutes, is in coreQueues (so no warning), and falls through to return EntryPoint.GENERIC — i.e. a misconfigured core queue falls open at runtime. The only thing catching that is the build-time JobQueueEntryPointsCoverageTest; the runtime does not enforce it, which is weaker than the class javadoc's "fails closed" framing and is the same hole as the acknowledged "start() fails startup — not implemented" gap. Either consult genericCoreQueues to fail closed for undeclared core queues, or remove the dead field. Fix this →
🟡 Medium: dotCMS/src/main/java/com/dotcms/jobs/business/api/JobQueueEntryPoints.java:127-139 — getInstance() memoizes the first registry for the JVM lifetime. If the first resolve() call ever runs while JandexClassMetadataScanner.getJandexIndex() returns null, the fail-closed (index == null) registry is cached permanently, and every plugin queue that doesn't set genericEntry = true would get 403 for the rest of the process. Assumption: the index could momentarily be null early in startup. What to verify: that the index is always populated before the /api/v1/jobs/{queueName} endpoint can serve a request; if it cannot be guaranteed, avoid caching a null-index instance (only memoize when the index is non-null). If serving is always post-init, this is not reachable — hence Medium.
Notes (non-blocking, already acknowledged in the PR body)
The spec's "fails startup when a core queue has no/▸1 declaration" is not implemented — only the build-time coverage test guards it (ties into the first finding).
Minor: assertGenericEntry logs a warn (JobQueueHelper.java:66) and HttpStatusCodeException logs another warn when the exception is constructed, so each refusal logs twice. Harmless.
No new doc / ADR for the "every queue declares its entry point" rule, and /speckit-converge not yet run — both called out by the author.
No bugs that block merge; the two medium items are worth resolving (the first especially, since the dead field reflects enforcement that isn't actually happening at runtime).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed Changes
mainonce docs(spec): job queue domain queues bypass their dedicated endpoint (#37883) #37906 merges.POST /api/v1/jobs/{queueName}and/upload) refuses, with403naming the real route, the five queues whose entry is a dedicated endpoint:folderBulkDuplicate,folderBulkDelete,assetBulkUpload,maintenanceFixAssets,maintenanceCleanAssets. Nothing is created on refusal.@JobQueueEntryPoint("<queue>")on the dedicated REST method, or@Queue(genericEntry = true)for queues fed by the generic endpoint (importContentlets,bulkRefreshContentlets,failSuccess,demo).JobQueueEntryPointsreads this from the Jandex index and builds the route in the message from the annotated method's own@Path.openapi.yamlregenerated (the403description on both creation methods).Checklist
JobQueueEntryPointsTest(7),JobQueueEntryPointsCoverageTest(2, one declaration per core queue),JobQueueDomainQueuesBypassIntegrationTest(8, registered inJunit5Suite1). Regression green:FolderBulkDuplicateResourceIT,FolderBulkDeleteResourceIT,BulkUploadResourceIT,MaintenanceResourceIntegrationTest,JobQueueHelperIntegrationTest.main).Additional Info
Rollback: REST contract change,
200to403on five queues, no response shape added. Expected rollback safe (M-3 ofdocs/core/ROLLBACK_UNSAFE_CATEGORIES.md);@Queuegains a member with a default (M-4, compatible).Known gaps (details in the handover comment on #37883):
JobQueueManagerAPIImpl.start()that refuses to start the queue manager when a core queue does not have exactly one declaration is not implemented; only the build-time coverage test guards it. The spec still says "fails startup", so either implement it (checking first whatInitServlet.init()does whenstart()throws) or align the spec wording./speckit-convergehas not been run.Refs #37883
🤖 Generated with Claude Code