Class GrantRequestService

java.lang.Object
io.jenkins.plugins.batchcontrol.policy.GrantRequestService

@Restricted(org.kohsuke.accmod.restrictions.NoExternalUse.class) public final class GrantRequestService extends Object
The single entry point for every GrantRequest state transition (SPEC items 3 and 8). No other class may change a grant request's status.

Concurrency (D-20 discipline, same as RunRequestService): every load→validate→transition→persist sequence runs under one ReentrantLock, which makes each transition an effective compare-and-set together with the store's atomic rewrite. Approval creates and registers the Grant inside the same critical section, so a request can never yield two grants.

Failure families: IllegalArgumentException for input validation, IllegalStateException for wrong-state transitions, AccessDeniedException (403 on the web layer) for authorization refusals.

S-15: create(io.jenkins.plugins.batchcontrol.model.GrantScope, java.util.List<io.jenkins.plugins.batchcontrol.model.GrantAction>, int, java.lang.String, java.lang.String) and approve(java.lang.String, java.lang.String) refuse outright while change control is off, because the switch is a kill switch and a window approved while it is off would otherwise take effect unreviewed the moment it was turned back on. See checkChangeControlEnabled.

  • Method Details

    • get

      public static GrantRequestService get()
    • load

      public GrantRequest load(String id)
      Loads a grant request by id, or null.
    • list

      public List<GrantRequest> list()
      All stored grant requests, in creation order.
    • countPendingFor

      public PendingCount countPendingFor(org.springframework.security.core.Authentication auth)
      D-61: the PENDING grant requests that concern auth, read from the open-request index only (no history scan): those awaiting their decision as a designated approver holding Jenkins-level BatchControl/Approve, else their own. Every counted request is visible to auth under P-09. The single source for the tab badge and the section.
    • create

      public GrantRequest create(GrantScope scope, List<GrantAction> actions, int durationMinutes, String reason, String approver)
      Creates a PENDING grant request. The requester is the current authentication; validation failures throw IllegalArgumentException.

      Rules: at least one action; duration in (0, maxGrantMinutes]; non-empty reason of at most 4000 characters; the approver must be on the global list and must not be the requester (admin exception per the self-approval policy); the scope item must exist and be visible to the requester, and each action must apply to its kind (D-71: CREATE only on a regular folder, DELETE only on a job).

    • create

      public GrantRequest create(GrantScope scope, List<GrantAction> actions, int durationMinutes, String reason, List<String> approvers)
      Approver-set form without a name restriction (D-37).
    • create

      public GrantRequest create(GrantScope scope, List<GrantAction> actions, int durationMinutes, String reason, List<String> approvers, String createNamePattern)
      Creates a PENDING grant request designating an approver set (D-37) and, for CREATE, an optional name restriction (D-40): an exact item name or a /regex/ matched against the whole name of the new item. A blank restriction means none; an invalid one (bad regex, invalid item name, over 1000 characters) is refused with IllegalArgumentException, as is a restriction on a request without CREATE.
    • findScopeItem

      @CheckForNull public static Item findScopeItem(@CheckForNull String fullName)
      D-71: the item a window would name, as the current user sees it, or null when no item exists at fullName or the user may not read it (an item visible only through Item/Discover counts as not visible). The single lookup used for requests and approvals.
    • approve

      public Grant approve(String id, String comment)
      Approves a PENDING grant request and creates the grant. The caller must be the designated approver holding the Approve permission (or the admin self-approval path). The grant window is [now, now + durationMinutes) on the BatchClock; SPEC item 8: the requester holds the permissions immediately.

      The stored scope is re-validated here (S-13, D-71): approval fails with IllegalStateException if no item exists at the scope name any more or its kind (descriptor id) differs from the one recorded at creation, and with IllegalArgumentException for a root scope or an item the approver cannot see (T-SEC-18): the approver must be able to see the scope item to approve a window on it.

      D-71c (3): if the item is deleted after it was checked and before the window is registered, the window ends at once; the request stays APPROVED and its APPROVED notice says that the window ended at once and why (owner decision 2026-10-06).

      Returns:
      the created, immediately effective Grant, or the window that ended at once
    • reject

      public GrantRequest reject(String id, String comment)
      Rejects a PENDING grant request; the comment is mandatory (SPEC item 5 rule reused).
    • changeApprovers

      public GrantRequest changeApprovers(String id, List<String> newApprovers)
      Replaces the designated approver set of a PENDING grant request; requester only (SPEC item 3 rules reused, D-26, D-37). Recorded as (previous set, new set, changed by, time).
    • cancel

      public GrantRequest cancel(String id)
      Cancels a PENDING grant request; requester or a Manage holder only (SPEC item 7 rule).
    • expireOverduePending

      public void expireOverduePending()
      Expires PENDING grant requests past pendingTimeoutHours. Called by the periodic work; expiry truth stays the clock comparison (a late approval attempt expires the request on its own, see approve(java.lang.String, java.lang.String)).
    • notifyExpiring

      public void notifyExpiring()
      D-36: sends NotificationEvent.EXPIRING once for every PENDING grant request whose pending timeout falls within notifyBeforeExpiryMinutes, and NotificationEvent.GRANT_EXPIRING once for every active window ending within it. The "notified" flags are persisted before dispatch, so a restart never resends.

      One request or window that cannot be read or written never keeps the others from their notice: the pending requests and the windows are handled apart, each request and each window on its own, and a failure is logged.

    • refuseRequestWhileChangeControlOff

      public void refuseRequestWhileChangeControlOff(String scopeFullName)
      e2e-03 DEF-27: the refusal of a grant request that the HTTP layer turns away before it reaches create(io.jenkins.plugins.batchcontrol.model.GrantScope, java.util.List<io.jenkins.plugins.batchcontrol.model.GrantAction>, int, java.lang.String, java.lang.String) (the Grants screen closes with the change-control switch). Records the refused request exactly as create(io.jenkins.plugins.batchcontrol.model.GrantScope, java.util.List<io.jenkins.plugins.batchcontrol.model.GrantAction>, int, java.lang.String, java.lang.String) would and throws the same IllegalStateException; returns normally while change control is on.
      Parameters:
      scopeFullName - the scope the request named, or null when none was given
      Throws:
      IllegalStateException - if change control is off