Skip to content

Commit ab7d6fe

Browse files
authored
Merge pull request #243 from ironcerocloudbees/GettingRBACpermissions
Getting RBAC permissions for user and item with groups path
2 parents 17d3716 + dbe3c9a commit ab7d6fe

1 file changed

Lines changed: 254 additions & 0 deletions

File tree

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/*
2+
* Author: Cristian Gonzalez & Ignacio Roncero
3+
* Since: July 2025
4+
* Description: This Groovy script is designed to perform a comprehensive audit of a specific user's
5+
* **Role-Based Access Control (RBAC)** permissions within a CloudBees Core CI environment,
6+
* tracing those permissions from a specified Jenkins item up through all inherited contexts.
7+
*
8+
* It effectively answers the question: "What permissions does 'username' have on 'itemPath',
9+
* and where do those permissions come from?"
10+
*
11+
* Key Functions:
12+
* ---------------------------------------------------------------------------------------
13+
* 1. Role and Group Tracing:
14+
* - The script iterates from the target item (`itemPath`) up to the Jenkins root,
15+
* examining the RBAC Group Container at every level (folder, job, root).
16+
* 2. User-to-Group Resolution:
17+
* - It identifies all groups the specified `username` belongs to at the current context level.
18+
* 3. Ancestry Resolution:
19+
* - It traces and resolves the full **nested hierarchy** of groups to ensure all
20+
* inherited roles (from parent groups) are captured.
21+
* 4. Permission Collection:
22+
* - For every inherited role, it retrieves the associated **explicit permissions**
23+
* (e.g., Job/Read, View/Configure).
24+
* 5. Deduplication:
25+
* - It includes logic to prevent listing the same role's permissions multiple times
26+
* if it is inherited through multiple group paths or contexts.
27+
*
28+
* The final output provides a clear, categorized summary of every unique RBAC role applied to the
29+
* user, detailing the group they belong to, the full group path, and the context (Jenkins root or specific item)
30+
* where that role was assigned.
31+
*/
32+
33+
34+
import jenkins.model.Jenkins
35+
import hudson.model.Item
36+
import nectar.plugins.rbac.strategy.RoleMatrixAuthorizationPlugin
37+
import nectar.plugins.rbac.groups.GroupContainerLocator
38+
import nectar.plugins.rbac.roles.Role
39+
import nectar.plugins.rbac.groups.Group
40+
import hudson.security.Permission
41+
42+
// Set your Jenkins username and item path
43+
def username = "username"
44+
def itemPath = "/"
45+
46+
def checkingItems(username, itemPath){
47+
def item = Jenkins.instance.getItemByFullName(itemPath)
48+
if (item == null) {
49+
println "❌ Item not found: '${itemPath}'"
50+
return
51+
}
52+
53+
def roleDetails = [] // list of [roleName, groupName, path, contextName, permissions]
54+
def roleNameDetails = []
55+
def seenRoleKeys = [] as Set // To avoid duplicates: roleName + context + groupName
56+
57+
def current = item
58+
while (current != null) {
59+
def contextLabel = (current == Jenkins.instance) ? "<root>" : current.fullName
60+
def groupContainer = (current == Jenkins.instance)
61+
? RoleMatrixAuthorizationPlugin.getInstance().getRootProxyGroupContainer()
62+
: GroupContainerLocator.locate(current)
63+
64+
if (groupContainer == null) {
65+
println "⚠️ No group container found at '${contextLabel}'"
66+
current = (current instanceof Item) ? current.getParent() : null
67+
continue
68+
}
69+
70+
// Build group and parent mappings
71+
def groupMap = groupContainer.getGroups().collectEntries { [(it.name): it] }
72+
def parentMap = [:].withDefault { [] }
73+
74+
groupContainer.getGroups().each { group ->
75+
group.getGroups().each { nested ->
76+
parentMap[nested] << group.name
77+
}
78+
}
79+
80+
// Find user's direct groups
81+
def userGroups = groupContainer.getGroups().findAll { g ->
82+
g.metaClass.respondsTo(g, "getUsers") && g.getUsers().contains(username)
83+
}
84+
85+
// Resolve full nested paths
86+
def visitedPaths = [] as Set
87+
userGroups.each { group ->
88+
def start = group.name
89+
def stack = [[start]]
90+
91+
while (!stack.isEmpty()) {
92+
def path = stack.pop()
93+
def currentGroup = path[-1]
94+
visitedPaths << path
95+
96+
parentMap[currentGroup].each { parent ->
97+
if (!path.contains(parent)) {
98+
stack.push(path + [parent])
99+
}
100+
}
101+
}
102+
}
103+
104+
// Collect roles and permissions
105+
visitedPaths.each { path ->
106+
def reversed = path.reverse()
107+
reversed.each { groupName ->
108+
def group = groupMap[groupName]
109+
if (group) {
110+
def roles = group.getAllRoles()
111+
roles.each { roleName ->
112+
def key = "${roleName}@${contextLabel}@${groupName}"
113+
if (seenRoleKeys.contains(key)) return
114+
seenRoleKeys << key
115+
116+
try {
117+
if(!roleNameDetails.contains(roleName)){
118+
def role = new Role(roleName)
119+
def perms = role.getPermissionProxies().collect { "${it.group.title} / ${it.name}" }
120+
roleDetails << [
121+
roleName : roleName,
122+
groupName : groupName,
123+
path : reversed.join(""),
124+
context : contextLabel,
125+
permissions: perms
126+
]
127+
roleNameDetails << roleName
128+
}
129+
} catch (Exception e) {
130+
println "⚠️ Could not resolve role '${roleName}': ${e.message}"
131+
}
132+
}
133+
}
134+
}
135+
}
136+
137+
// Move up
138+
current = (current instanceof Item) ? current.getParent() : null
139+
if (current == Jenkins.instance) break
140+
}
141+
142+
// ✅ Final output
143+
if (roleDetails.isEmpty()) {
144+
println "❌ No roles found for user '${username}' in '${item.fullName}' or inherited contexts."
145+
return
146+
}
147+
148+
println "\nRBAC Role & Permission Summary for '${username}' (including inherited scopes):\n"
149+
150+
roleDetails.each { entry ->
151+
println "🔹 Role: ${entry.roleName}"
152+
println " • From Group: ${entry.groupName}"
153+
println " • Group Path: ${entry.path}"
154+
println " • Context: ${entry.context}"
155+
println " • Permissions:"
156+
entry.permissions.each { println " - ${it}" }
157+
println ""
158+
}
159+
}
160+
161+
def checkRoot(username) {
162+
def config = RoleMatrixAuthorizationPlugin.getConfig()
163+
def allGroups = config.getGroups()
164+
165+
// Build lookup maps
166+
def groupMap = allGroups.collectEntries { [(it.name): it] }
167+
def parentMap = [:].withDefault { [] }
168+
allGroups.each { group ->
169+
group.getGroups().each { nested ->
170+
parentMap[nested] << group.name
171+
}
172+
}
173+
174+
// Find all directly assigned groups
175+
def userGroups = allGroups.findAll { g ->
176+
g.metaClass.respondsTo(g, "getUsers") && g.getUsers().contains(username)
177+
}
178+
179+
if (userGroups.isEmpty()) {
180+
println "❌ User '${username}' is not a member of any configured group."
181+
return
182+
}
183+
184+
// Traverse group ancestry and record roles and permissions
185+
def visitedPaths = [] as Set
186+
def roleDetails = [] // list of [roleName, groupName, fullPath, permissionList]
187+
def roleNameDetails = []
188+
189+
userGroups.each { group ->
190+
def start = group.name
191+
def stack = [[start]]
192+
193+
while (!stack.isEmpty()) {
194+
def path = stack.pop()
195+
def currentGroupName = path[-1]
196+
197+
visitedPaths << path
198+
199+
parentMap[currentGroupName].each { parent ->
200+
if (!path.contains(parent)) {
201+
stack.push(path + [parent])
202+
}
203+
}
204+
}
205+
}
206+
207+
// Collect roles + permissions per path
208+
visitedPaths.each { path ->
209+
def reversed = path.reverse() // from top-level → direct group
210+
reversed.each { groupName ->
211+
def group = groupMap[groupName]
212+
if (group) {
213+
def roles = group.getAllRoles()
214+
roles.each { roleName ->
215+
try {
216+
if(!roleNameDetails.contains(roleName)){
217+
def role = new Role(roleName)
218+
def perms = role.getPermissionProxies().collect { "${it.group.title} / ${it.name}" }
219+
roleDetails << [
220+
roleName : roleName,
221+
fromGroup : groupName,
222+
fullPath : reversed.join(""),
223+
permissions : perms
224+
]
225+
roleNameDetails << roleName
226+
}
227+
} catch (Exception e) {
228+
println "⚠️ Could not resolve role '${roleName}': ${e.message}"
229+
}
230+
}
231+
}
232+
}
233+
}
234+
235+
// Print summary
236+
roleDetails.each { entry ->
237+
println "🔹 Role: ${entry.roleName}"
238+
println " • From Group: ${entry.fromGroup}"
239+
println " • Group Path: ${entry.fullPath}"
240+
println " • Context: Jenkins"
241+
println " • Permissions:"
242+
entry.permissions.each { println " - ${it}" }
243+
println ""
244+
}
245+
}
246+
247+
println "Gathering RBAC roles for user '${username}' in item '${itemPath}' and all inherited scopes..."
248+
249+
if(itemPath!=null && !itemPath.isEmpty() && !itemPath.equals("/")){
250+
checkingItems(username, itemPath)
251+
}
252+
checkRoot(username)
253+
254+
return

0 commit comments

Comments
 (0)