1
1
/*
2
- * Copyright 2002-2016 the original author or authors.
2
+ * Copyright 2002-2019 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
15
15
*/
16
16
package org .springframework .security .access .hierarchicalroles ;
17
17
18
- import java .io .BufferedReader ;
19
- import java .io .IOException ;
20
- import java .io .StringReader ;
21
18
import java .util .ArrayList ;
22
19
import java .util .Collection ;
23
20
import java .util .HashMap ;
34
31
35
32
/**
36
33
* <p>
37
- * This class defines a role hierarchy for use with the UserDetailsServiceWrapper .
34
+ * This class defines a role hierarchy for use with various access checking components .
38
35
*
39
36
* <p>
40
- * Here is an example configuration of a role hierarchy (hint: read the ">" sign as
41
- * "includes"):
37
+ * Here is an example configuration of a role hierarchy (hint: read the ">" sign as "includes"):
42
38
*
43
39
* <pre>
44
- * <property name="hierarchy">
45
- * <value>
46
- * ROLE_A > ROLE_B
47
- * ROLE_B > ROLE_AUTHENTICATED
48
- * ROLE_AUTHENTICATED > ROLE_UNAUTHENTICATED
49
- * </value>
50
- * </property>
40
+ * <property name="hierarchy">
41
+ * <value>
42
+ * ROLE_A > ROLE_B
43
+ * ROLE_B > ROLE_AUTHENTICATED
44
+ * ROLE_AUTHENTICATED > ROLE_UNAUTHENTICATED
45
+ * </value>
46
+ * </property>
51
47
* </pre>
52
48
*
53
49
* <p>
54
- * Explanation of the above:<br>
55
- * In effect every user with ROLE_A also has ROLE_B, ROLE_AUTHENTICATED and
56
- * ROLE_UNAUTHENTICATED;<br>
57
- * every user with ROLE_B also has ROLE_AUTHENTICATED and ROLE_UNAUTHENTICATED;<br>
58
- * every user with ROLE_AUTHENTICATED also has ROLE_UNAUTHENTICATED.
50
+ * Explanation of the above:
51
+ * <ul>
52
+ * <li>In effect every user with ROLE_A also has ROLE_B, ROLE_AUTHENTICATED and ROLE_UNAUTHENTICATED;</li>
53
+ * <li>every user with ROLE_B also has ROLE_AUTHENTICATED and ROLE_UNAUTHENTICATED;</li>
54
+ * <li>every user with ROLE_AUTHENTICATED also has ROLE_UNAUTHENTICATED.</li>
55
+ * </ul>
59
56
*
60
57
* <p>
61
- * Hierarchical Roles will dramatically shorten your access rules (and also make the
62
- * access rules much more elegant).
58
+ * Hierarchical Roles will dramatically shorten your access rules (and also make the access rules
59
+ * much more elegant).
63
60
*
64
61
* <p>
65
- * Consider this access rule for Spring Security's RoleVoter (background: every user that
66
- * is authenticated should be able to log out):<br>
67
- * /logout.html=ROLE_A,ROLE_B,ROLE_AUTHENTICATED<br>
68
- * With hierarchical roles this can now be shortened to:<br>
69
- * /logout.html=ROLE_AUTHENTICATED<br>
70
- * In addition to shorter rules this will also make your access rules more readable and
71
- * your intentions clearer.
62
+ * Consider this access rule for Spring Security's RoleVoter (background: every user that is
63
+ * authenticated should be able to log out):
64
+ * <pre>/logout.html=ROLE_A,ROLE_B,ROLE_AUTHENTICATED</pre>
65
+ *
66
+ * With hierarchical roles this can now be shortened to:
67
+ * <pre>/logout.html=ROLE_AUTHENTICATED</pre>
68
+ *
69
+ * In addition to shorter rules this will also make your access rules more readable and your
70
+ * intentions clearer.
72
71
*
73
72
* @author Michael Mayr
74
73
*/
75
74
public class RoleHierarchyImpl implements RoleHierarchy {
76
75
77
76
private static final Log logger = LogFactory .getLog (RoleHierarchyImpl .class );
78
77
78
+ /**
79
+ * Raw hierarchy configuration where each line represents single or multiple level role chain.
80
+ */
79
81
private String roleHierarchyStringRepresentation = null ;
80
82
81
83
/**
82
- * rolesReachableInOneStepMap is a Map that under the key of a specific role name
84
+ * {@code rolesReachableInOneStepMap} is a Map that under the key of a specific role name
83
85
* contains a set of all roles reachable from this role in 1 step
86
+ * (i.e. parsed {@link #roleHierarchyStringRepresentation} grouped by the higher role)
84
87
*/
85
- private Map <GrantedAuthority , Set <GrantedAuthority >> rolesReachableInOneStepMap = null ;
88
+ private Map <String , Set <GrantedAuthority >> rolesReachableInOneStepMap = null ;
86
89
87
90
/**
88
- * rolesReachableInOneOrMoreStepsMap is a Map that under the key of a specific role
91
+ * {@code rolesReachableInOneOrMoreStepsMap} is a Map that under the key of a specific role
89
92
* name contains a set of all roles reachable from this role in 1 or more steps
93
+ * (i.e. fully resolved hierarchy from {@link #rolesReachableInOneStepMap})
90
94
*/
91
- private Map <GrantedAuthority , Set <GrantedAuthority >> rolesReachableInOneOrMoreStepsMap = null ;
95
+ private Map <String , Set <GrantedAuthority >> rolesReachableInOneOrMoreStepsMap = null ;
92
96
93
97
/**
94
98
* Set the role hierarchy and pre-calculate for every role the set of all reachable
@@ -102,27 +106,46 @@ public class RoleHierarchyImpl implements RoleHierarchy {
102
106
public void setHierarchy (String roleHierarchyStringRepresentation ) {
103
107
this .roleHierarchyStringRepresentation = roleHierarchyStringRepresentation ;
104
108
105
- logger .debug ("setHierarchy() - The following role hierarchy was set: "
106
- + roleHierarchyStringRepresentation );
109
+ if (logger .isDebugEnabled ()) {
110
+ logger .debug ("setHierarchy() - The following role hierarchy was set: "
111
+ + roleHierarchyStringRepresentation );
112
+ }
107
113
108
114
buildRolesReachableInOneStepMap ();
109
115
buildRolesReachableInOneOrMoreStepsMap ();
110
116
}
111
117
118
+ @ Override
112
119
public Collection <GrantedAuthority > getReachableGrantedAuthorities (
113
120
Collection <? extends GrantedAuthority > authorities ) {
114
121
if (authorities == null || authorities .isEmpty ()) {
115
122
return AuthorityUtils .NO_AUTHORITIES ;
116
123
}
117
124
118
125
Set <GrantedAuthority > reachableRoles = new HashSet <>();
126
+ Set <String > processedNames = new HashSet <>();
119
127
120
128
for (GrantedAuthority authority : authorities ) {
121
- addReachableRoles (reachableRoles , authority );
122
- Set <GrantedAuthority > additionalReachableRoles = getRolesReachableInOneOrMoreSteps (
123
- authority );
124
- if (additionalReachableRoles != null ) {
125
- reachableRoles .addAll (additionalReachableRoles );
129
+ // Do not process authorities without string representation
130
+ if (authority .getAuthority () == null ) {
131
+ reachableRoles .add (authority );
132
+ continue ;
133
+ }
134
+ // Do not process already processed roles
135
+ if (!processedNames .add (authority .getAuthority ())) {
136
+ continue ;
137
+ }
138
+ // Add original authority
139
+ reachableRoles .add (authority );
140
+ // Add roles reachable in one or more steps
141
+ Set <GrantedAuthority > lowerRoles = this .rolesReachableInOneOrMoreStepsMap .get (authority .getAuthority ());
142
+ if (lowerRoles == null ) {
143
+ continue ; // No hierarchy for the role
144
+ }
145
+ for (GrantedAuthority role : lowerRoles ) {
146
+ if (processedNames .add (role .getAuthority ())) {
147
+ reachableRoles .add (role );
148
+ }
126
149
}
127
150
}
128
151
@@ -132,75 +155,40 @@ public Collection<GrantedAuthority> getReachableGrantedAuthorities(
132
155
+ " in zero or more steps." );
133
156
}
134
157
135
- List <GrantedAuthority > reachableRoleList = new ArrayList <>(
136
- reachableRoles .size ());
158
+ List <GrantedAuthority > reachableRoleList = new ArrayList <>(reachableRoles .size ());
137
159
reachableRoleList .addAll (reachableRoles );
138
160
139
161
return reachableRoleList ;
140
162
}
141
163
142
- // SEC-863
143
- private void addReachableRoles (Set <GrantedAuthority > reachableRoles ,
144
- GrantedAuthority authority ) {
145
-
146
- for (GrantedAuthority testAuthority : reachableRoles ) {
147
- String testKey = testAuthority .getAuthority ();
148
- if ((testKey != null ) && (testKey .equals (authority .getAuthority ()))) {
149
- return ;
150
- }
151
- }
152
- reachableRoles .add (authority );
153
- }
154
-
155
- // SEC-863
156
- private Set <GrantedAuthority > getRolesReachableInOneOrMoreSteps (
157
- GrantedAuthority authority ) {
158
-
159
- if (authority .getAuthority () == null ) {
160
- return null ;
161
- }
162
-
163
- for (GrantedAuthority testAuthority : this .rolesReachableInOneOrMoreStepsMap
164
- .keySet ()) {
165
- String testKey = testAuthority .getAuthority ();
166
- if ((testKey != null ) && (testKey .equals (authority .getAuthority ()))) {
167
- return this .rolesReachableInOneOrMoreStepsMap .get (testAuthority );
168
- }
169
- }
170
-
171
- return null ;
172
- }
173
-
174
164
/**
175
165
* Parse input and build the map for the roles reachable in one step: the higher role
176
166
* will become a key that references a set of the reachable lower roles.
177
167
*/
178
168
private void buildRolesReachableInOneStepMap () {
179
169
this .rolesReachableInOneStepMap = new HashMap <>();
180
- try ( BufferedReader bufferedReader = new BufferedReader (
181
- new StringReader ( this . roleHierarchyStringRepresentation ))) {
182
- for ( String readLine ; ( readLine = bufferedReader . readLine ()) != null ;) {
183
- String [] roles = readLine . split ( " > " );
184
- for (int i = 1 ; i < roles .length ; i ++) {
185
- GrantedAuthority higherRole = new SimpleGrantedAuthority (
186
- roles [i - 1 ]. replaceAll ( "^ \\ s+| \\ s+$" , "" ) );
187
- GrantedAuthority lowerRole = new SimpleGrantedAuthority ( roles [ i ]. replaceAll ( "^ \\ s+| \\ s+$" , "" ));
188
- Set <GrantedAuthority > rolesReachableInOneStepSet ;
189
- if (!this .rolesReachableInOneStepMap .containsKey (higherRole )) {
190
- rolesReachableInOneStepSet = new HashSet <>();
191
- this .rolesReachableInOneStepMap .put (higherRole , rolesReachableInOneStepSet );
192
- } else {
193
- rolesReachableInOneStepSet = this .rolesReachableInOneStepMap .get (higherRole );
194
- }
195
- rolesReachableInOneStepSet .add (lowerRole );
196
- if ( logger . isDebugEnabled ()) {
197
- logger .debug ( "buildRolesReachableInOneStepMap() - From role " + higherRole
198
- + " one can reach role " + lowerRole + " in one step." );
199
- }
170
+ for ( String line : this . roleHierarchyStringRepresentation . split ( " \n " )) {
171
+ // Split on > and trim excessive whitespace
172
+ String [] roles = line . trim (). split ( " \\ s+> \\ s+" );
173
+
174
+ for (int i = 1 ; i < roles .length ; i ++) {
175
+ String higherRole = roles [ i - 1 ];
176
+ GrantedAuthority lowerRole = new SimpleGrantedAuthority ( roles [i ] );
177
+
178
+ Set <GrantedAuthority > rolesReachableInOneStepSet ;
179
+ if (!this .rolesReachableInOneStepMap .containsKey (higherRole )) {
180
+ rolesReachableInOneStepSet = new HashSet <>();
181
+ this .rolesReachableInOneStepMap .put (higherRole , rolesReachableInOneStepSet );
182
+ } else {
183
+ rolesReachableInOneStepSet = this .rolesReachableInOneStepMap .get (higherRole );
184
+ }
185
+ rolesReachableInOneStepSet .add (lowerRole );
186
+
187
+ if ( logger .isDebugEnabled ()) {
188
+ logger . debug ( "buildRolesReachableInOneStepMap() - From role " + higherRole
189
+ + " one can reach role " + lowerRole + " in one step." );
200
190
}
201
191
}
202
- } catch (IOException e ) {
203
- throw new IllegalStateException (e );
204
192
}
205
193
}
206
194
@@ -213,25 +201,25 @@ private void buildRolesReachableInOneStepMap() {
213
201
private void buildRolesReachableInOneOrMoreStepsMap () {
214
202
this .rolesReachableInOneOrMoreStepsMap = new HashMap <>();
215
203
// iterate over all higher roles from rolesReachableInOneStepMap
216
-
217
- for (GrantedAuthority role : this .rolesReachableInOneStepMap .keySet ()) {
218
- Set <GrantedAuthority > rolesToVisitSet = new HashSet <>(this .rolesReachableInOneStepMap .get (role ));
204
+ for (String roleName : this .rolesReachableInOneStepMap .keySet ()) {
205
+ Set <GrantedAuthority > rolesToVisitSet = new HashSet <>(this .rolesReachableInOneStepMap .get (roleName ));
219
206
Set <GrantedAuthority > visitedRolesSet = new HashSet <>();
220
207
221
208
while (!rolesToVisitSet .isEmpty ()) {
222
209
// take a role from the rolesToVisit set
223
- GrantedAuthority aRole = rolesToVisitSet .iterator ().next ();
224
- rolesToVisitSet .remove (aRole );
225
- if (!visitedRolesSet .add (aRole ) || !this .rolesReachableInOneStepMap .containsKey (aRole )) {
210
+ GrantedAuthority lowerRole = rolesToVisitSet .iterator ().next ();
211
+ rolesToVisitSet .remove (lowerRole );
212
+ if (!visitedRolesSet .add (lowerRole ) ||
213
+ !this .rolesReachableInOneStepMap .containsKey (lowerRole .getAuthority ())) {
226
214
continue ; // Already visited role or role with missing hierarchy
227
- } else if (role .equals (aRole )) {
215
+ } else if (roleName .equals (lowerRole . getAuthority () )) {
228
216
throw new CycleInRoleHierarchyException ();
229
217
}
230
- rolesToVisitSet .addAll (this .rolesReachableInOneStepMap .get (aRole ));
218
+ rolesToVisitSet .addAll (this .rolesReachableInOneStepMap .get (lowerRole . getAuthority () ));
231
219
}
232
- this .rolesReachableInOneOrMoreStepsMap .put (role , visitedRolesSet );
220
+ this .rolesReachableInOneOrMoreStepsMap .put (roleName , visitedRolesSet );
233
221
234
- logger .debug ("buildRolesReachableInOneOrMoreStepsMap() - From role " + role
222
+ logger .debug ("buildRolesReachableInOneOrMoreStepsMap() - From role " + roleName
235
223
+ " one can reach " + visitedRolesSet + " in one or more steps." );
236
224
}
237
225
0 commit comments