dotCMS / dotCMS/core

[Task] Gate POST /api/vtl/dynamic on license + admin + portlet permission

Open
#35,854 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

stale Team : Modernization
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:500processRequest initialises with this.webResource.init(null, request, response, false, null). The false is rejectWhenNoUser, and the trailing null is requiredPortlet. No portlet permission is checked.
  • dotCMS/src/main/java/com/dotcms/rest/AnonymousAccess.java:37–38systemSetting() returns READ by default (config CONTENT_APIS_ALLOW_ANONYMOUS, default "READ"). With rejectWhenNoUser=false, the endpoint is reachable without authentication on a vanilla instance.
  • VTLResource.java has no LicenseUtil / LicenseLevel reference; ScriptingUtil#validateBodyMap only checks the identifier field 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/dynamic and POST /api/vtl/dynamic/{pathParam} reject requests when LicenseUtil.getLevel() < LicenseLevel.STANDARD.level (mirror the JSP gate).
  • webResource.init(...) is updated so rejectWhenNoUser=true and requiredPortlet="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 / dynamicDelete siblings on /api/vtl/dynamic[/{pathParam}] (they share processRequest).
  • @Operation annotations are added to the dynamic* methods, and the class-level @Tag is corrected from Templates to a dedicated Velocity tag so the OpenAPI doc reflects the admin-only contract.
  • dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml is regenerated and committed (./mvnw compile -pl :dotcms-core -DskipTests).
  • VTLResourceIntegrationTest covers: anonymous request → 401/403, authenticated non-admin → 403, sub-STANDARD license → 403, admin on STANDARD+ → 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 @Deprecated GET /api/vtl/dynamic body-bearing variants — track separately if needed.

Notes for the FE migration (#35810)

Once these gates land, the Angular Velocity Playground should expect:

  • 401 when the user session is invalid → redirect to login (standard portlet behaviour).
  • 403 when the license/role gate fails → render the "not licensed / forbidden" portlet state instead of the editor.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.