Understanding the Jenkins Permission System: PermissionGroup and Scopes Explained

The Jenkins permission system organizes access control through PermissionGroup containers that sort permissions by owner class, PermissionScope constants that define where permissions apply across the Jenkins model hierarchy, and Permission objects that represent individual rights with optional implication and enablement flags.

The jenkinsci/jenkins repository implements a hierarchical access control layer that governs every user interaction, from global system configuration to individual build logs. Understanding the Jenkins permission system with PermissionGroup and scopes is essential for plugin developers and security administrators who need to implement fine-grained authorization strategies.

Core Architecture: Groups, Scopes, and Permissions

Jenkins controls access through three tightly coupled constructs that form an extensible ACL framework:

  • PermissionGroup – A logical container that groups related permissions by their owning class.
  • Permission – An individual privilege (e.g., Read, Configure) that belongs to exactly one group.
  • PermissionScope – Defines the level of the Jenkins model (global, item-group, item, run, or computer) where a permission can be configured.

When a Permission is instantiated, its constructor automatically registers itself with its parent PermissionGroup, which in turn adds the permission to the global Permission.ALL registry.

How PermissionGroup Organizes Access Rights

Every permission belongs to a PermissionGroup whose owner is the class that defines the permissions (e.g., Jenkins.class or a plugin class). In core/src/main/java/hudson/security/PermissionGroup.java, the class maintains a sorted set of its Permission objects and registers itself in the global PermissionGroup.PERMISSIONS registry upon instantiation.

Key implementation details include:

  • owner – The class that owns the group, used for sorting and uniqueness comparisons.
  • title – A human-readable title displayed in the security configuration UI.
  • add(Permission) – A private registration method invoked during permission construction that throws an exception if a duplicate permission ID is added.
  • getAll() and get(Class) – Static utility methods to enumerate all loaded groups or locate a specific group by its owner class.
  • compareTo / equals / hashCode – Implemented so groups remain comparable and usable in sorted sets.

When you define a new permission, the constructor automatically calls group.add(this), ensuring the permission appears in both its group and the global list.

PermissionScope and the Hierarchy of Application

The PermissionScope class in core/src/main/java/hudson/security/PermissionScope.java defines where a permission may be granted within the Jenkins object model. Scopes form a containment hierarchy where narrower scopes are contained within broader ones.

Built-in scopes include:

  • JENKINS – Global Jenkins instance (top-level).
  • ITEM_GROUP – Containers of items, such as folders.
  • ITEM – Individual jobs or projects.
  • RUN – Specific builds or runs of a job.
  • COMPUTER – Individual nodes or agents.

The isContainedBy(PermissionScope) method walks this container hierarchy, allowing a permission declared for a finer scope (e.g., RUN) to be considered valid when checking a broader scope (e.g., ITEM). This enables cascading authorization where a user with item-level permissions implicitly holds rights to the runs within that item.

The Permission Class: Tying Groups and Scopes Together

The Permission class in core/src/main/java/hudson/security/Permission.java serves as the concrete representation of an access right. Each permission carries:

  • Owner – The class that declared the permission.
  • Group – The PermissionGroup instance it belongs to.
  • Name – A unique identifier within its group (e.g., "Read").
  • Description – Tooltip text for UI rendering.
  • impliedBy – An optional broader permission that automatically grants this one (e.g., ADMINISTER implies CONFIGURE).
  • enabled – A runtime flag that controls visibility in the security matrix UI.
  • scopes – One or more PermissionScope values indicating where the permission is configurable.

Critical methods include:

  • isContainedBy(PermissionScope) – Verifies whether the permission is applicable for a given scope.
  • getId() – Returns a stable string identifier like hudson.model.Hudson.Read.
  • fromId(String) – A static lookup that loads the owning class via Jenkins’ classloader and retrieves the permission from its group.

The static fields Permission.ALL and Permission.ALL_VIEW expose all loaded permissions for programmatic queries and UI rendering in authorization strategies like PermissionMatrixAuthorizationStrategy.

Practical Implementation Examples

Defining Custom Permissions in a Plugin

To create plugin-specific permissions, define a static PermissionGroup and add permissions with appropriate scopes:

// src/main/java/com/example/MyPlugin.java
package com.example;

import hudson.security.Permission;
import hudson.security.PermissionGroup;
import hudson.security.PermissionScope;
import org.jvnet.localizer.Localizable;

public class MyPlugin {
    // Create a group for the plugin
    public static final PermissionGroup PERMISSIONS =
        new PermissionGroup(MyPlugin.class,
            new Localizable("My Plugin Permissions"));

    // Define a permission configurable at Jenkins or Item level
    public static final Permission CONFIGURE_SPECIAL =
        new Permission(PERMISSIONS,
            "ConfigureSpecial",
            null,               // optional description
            Permission.CONFIGURE, // implied by generic Configure
            new PermissionScope[]{PermissionScope.JENKINS, PermissionScope.ITEM});
}

This registers the permission automatically with the global registry and its parent group.

Runtime Permission Checks

Validate user access by checking permissions against the appropriate model object:

import hudson.security.Permission;
import jenkins.model.Jenkins;

public boolean canConfigureSpecial(User user, Item item) {
    // Permission is scoped to ITEM, so we pass the item as context
    return user.hasPermission(item, MyPlugin.CONFIGURE_SPECIAL);
}

The User.hasPermission method internally calls Permission.isContainedBy to verify that the supplied item matches one of the permission's declared scopes.

Dynamic Permission Management

You can toggle permission visibility at runtime without restarting Jenkins:

// Hide the permission from the UI after a certain plugin version
MyPlugin.CONFIGURE_SPECIAL.setEnabled(false);

The enabled flag is consulted by the UI rendering code when building the security matrix, allowing gradual deprecation of legacy permissions.

Summary

  • PermissionGroup instances act as ownership containers that automatically register permissions in a global sorted set when instantiated.
  • PermissionScope defines five hierarchical levels (JENKINS, ITEM_GROUP, ITEM, RUN, COMPUTER) and provides isContainedBy logic for scope validation.
  • Permission objects encapsulate individual rights with unique IDs, implied-by hierarchies, enablement flags, and scope arrays.
  • The permission system is extensible: plugins define groups and permissions via static fields, and the core handles registration via constructors in PermissionGroup.java and Permission.java.
  • Runtime checks use isContainedBy to verify that a permission's scope matches the authorization context, supporting hierarchical inheritance from runs up to the global Jenkins instance.

Frequently Asked Questions

What is the difference between PermissionGroup and PermissionScope?

A PermissionGroup is a logical container that organizes permissions by their owning class (e.g., Jenkins.class), primarily for UI grouping and namespace management. A PermissionScope defines the level of the Jenkins model where a permission can be applied—such as global Jenkins settings, individual items, or specific runs. While groups categorize permissions horizontally by owner, scopes categorize them vertically by model depth.

How do I check if a user has a specific Jenkins permission?

Use the hasPermission method on the User object, passing the target model object and the Permission constant. For example, user.hasPermission(item, MyPlugin.CONFIGURE_SPECIAL) validates both the user's authorization strategy and whether the permission's scope contains the supplied item via Permission.isContainedBy.

Can permissions be dynamically disabled in Jenkins?

Yes. Each Permission object includes an enabled boolean flag that can be toggled at runtime using setEnabled(false). When disabled, the permission is hidden from the security matrix UI, though it remains in the system. This is useful for deprecating features or hiding permissions when specific plugin dependencies are unavailable.

How does permission implication work in the Jenkins permission system?

The impliedBy field in Permission.java allows a permission to specify a broader permission that automatically grants it. For example, if you set Permission.CONFIGURE as the impliedBy parameter when constructing your custom permission, any user with CONFIGURE rights automatically receives your permission without explicit assignment. This creates a hierarchical inheritance model that reduces administrative overhead.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →