[Task] Gate POST /api/vtl/dynamic on license + admin + portlet permission
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Context
Surfaced by the spike audit in #35809 (epic #34737).
POST /api/vtl/dynamic/ (and its GET/PUT/PATCH/DELETE siblings on the same path) evaluates arbitrary Velocity / VTL with no license check, no admin role check, and no portlet permission check at the API layer. The only enforcement today is the JSP-level license gate in dotCMS/src/main/webapp/WEB-INF/jsp/velocity_playground/render.jsp:6 (LicenseUtil.getLevel() < LicenseLevel.STANDARD.level), which protects the UI but not the endpoint.
Evidence
dotCMS/src/main/java/com/dotcms/rest/api/v1/vtl/VTLResource.java:500—processRequestinitialises withthis.webResource.init(null, request, response, false, null). ThefalseisrejectWhenNoUser, and the trailingnullisrequiredPortlet. No portlet permission is checked.dotCMS/src/main/java/com/dotcms/rest/AnonymousAccess.java:37–38—systemSetting()returnsREADby default (configCONTENT_APIS_ALLOW_ANONYMOUS, default"READ"). WithrejectWhenNoUser=false, the endpoint is reachable without authentication on a vanilla instance.VTLResource.javahas noLicenseUtil/LicenseLevelreference;ScriptingUtil#validateBodyMaponly checks theidentifierfield shape and never inspects roles or licenses.
Impact
Velocity in dotCMS has broad runtime access — view tools, $dotcontent, request/session/user objects, the content APIs — so an unauthenticated or non-admin caller can enumerate content, read site metadata, and exercise toolbox methods. This is the same execution environment the JSP playground uses, but without the license/admin gate the UI implies.
Acceptance criteria
-
POST /api/vtl/dynamicandPOST /api/vtl/dynamic/{pathParam}reject requests whenLicenseUtil.getLevel() < LicenseLevel.STANDARD.level(mirror the JSP gate). -
webResource.init(...)is updated sorejectWhenNoUser=trueandrequiredPortlet="velocity_playground"(snake_case — the legacy portlet-name is intentionally preserved to avoid the upgrade-task / starter-regeneration risk that hit #35428; only the Angular shell URL goes kebab-case via<portlet-url>. See https://github.com/dotCMS/core/issues/35809#issuecomment-4576971482 for the full rationale). - An explicit admin check (
user.isAdmin()) is added as defence-in-depth before evaluating the Velocity. - The same gates are applied to the
dynamicGet/dynamicPut/dynamicPatch/dynamicDeletesiblings on/api/vtl/dynamic[/{pathParam}](they shareprocessRequest). -
@Operationannotations are added to thedynamic*methods, and the class-level@Tagis corrected fromTemplatesto a dedicatedVelocitytag so the OpenAPI doc reflects the admin-only contract. -
dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yamlis regenerated and committed (./mvnw compile -pl :dotcms-core -DskipTests). -
VTLResourceIntegrationTestcovers: anonymous request → 401/403, authenticated non-admin → 403, sub-STANDARDlicense → 403, admin onSTANDARD+ → 200.
Out of scope
- The folder-based VTL endpoints (
/api/vtl/{folder}/…) — those are convention-based content APIs and have a different threat model; this task is scoped to/api/vtl/dynamic*. - Removing the already
@DeprecatedGET /api/vtl/dynamicbody-bearing variants — track separately if needed.
Notes for the FE migration (#35810)
Once these gates land, the Angular Velocity Playground should expect:
401when the user session is invalid → redirect to login (standard portlet behaviour).403when the license/role gate fails → render the "not licensed / forbidden" portlet state instead of the editor.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with VTLResource.java around processRequest and the dynamic* methods, then compare the license gate in render.jsp and authentication behavior in AnonymousAccess.java. Run VTLResourceIntegrationTest and regenerate openapi.yaml with ./mvnw compile -pl :dotcms-core -DskipTests. Done means all dynamic methods enforce the listed license, admin, and portlet gates, with the specified status-code tests and OpenAPI annotations committed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- api, authentication, authorization, backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 66/100