1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 module hunt.shiro.authz.ModularRealmAuthorizer; 20 21 import hunt.shiro.authz.Authorizer; 22 import hunt.shiro.authz.permission.Permission; 23 import hunt.shiro.authz.permission.PermissionResolver; 24 import hunt.shiro.authz.permission.PermissionResolverAware; 25 import hunt.shiro.authz.permission.RolePermissionResolver; 26 import hunt.shiro.authz.permission.RolePermissionResolverAware; 27 import hunt.shiro.Exceptions; 28 import hunt.shiro.realm.Realm; 29 import hunt.shiro.subject.PrincipalCollection; 30 import hunt.shiro.util.CollectionUtils; 31 32 import hunt.Exceptions; 33 import hunt.collection; 34 import hunt.logging; 35 36 import std.conv; 37 import std.string; 38 import core.atomic; 39 import std.range; 40 41 42 /** 43 * A <tt>ModularRealmAuthorizer</tt> is an <tt>Authorizer</tt> implementation that consults one or more configured 44 * {@link Realm Realm}s during an authorization operation. 45 * 46 */ 47 class ModularRealmAuthorizer : Authorizer, PermissionResolverAware, RolePermissionResolverAware { 48 49 /** 50 * The realms to consult during any authorization check. 51 */ 52 protected Realm[] realms; 53 54 /** 55 * A PermissionResolver to be used by <em>all</em> configured realms. Leave <code>null</code> if you wish 56 * to configure different resolvers for different realms. 57 */ 58 protected PermissionResolver permissionResolver; 59 60 /** 61 * A RolePermissionResolver to be used by <em>all</em> configured realms. Leave <code>null</code> if you wish 62 * to configure different resolvers for different realms. 63 */ 64 protected RolePermissionResolver rolePermissionResolver; 65 66 /** 67 * Default no-argument constructor, does nothing. 68 */ 69 this() { 70 } 71 72 /** 73 * Constructor that accepts the <code>Realm</code>s to consult during an authorization check. Immediately calls 74 * {@link #setRealms setRealms(realms)}. 75 * 76 * @param realms the realms to consult during an authorization check. 77 */ 78 this(Realm[] realms) { 79 setRealms(realms); 80 } 81 82 /** 83 * Returns the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check. 84 * 85 * @return the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check. 86 */ 87 Realm[] getRealms() { 88 return this.realms; 89 } 90 91 /** 92 * Sets the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check. 93 * 94 * @param realms the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check. 95 */ 96 void setRealms(Realm[] realms) { 97 this.realms = realms; 98 applyPermissionResolverToRealms(); 99 applyRolePermissionResolverToRealms(); 100 } 101 102 /** 103 * Returns the PermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default) 104 * if all realm instances will each configure their own permission resolver. 105 * 106 * @return the PermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default) 107 * if realm instances will each configure their own permission resolver. 108 */ 109 PermissionResolver getPermissionResolver() { 110 return this.permissionResolver; 111 } 112 113 /** 114 * Sets the specified {@link PermissionResolver PermissionResolver} on <em>all</em> of the wrapped realms that 115 * implement the {@link hunt.shiro.authz.permission.PermissionResolverAware PermissionResolverAware} interface. 116 * <p/> 117 * Only call this method if you want the permission resolver to be passed to all realms that implement the 118 * <code>PermissionResolver</code> interface. If you do not want this to occur, the realms must 119 * configure themselves individually (or be configured individually). 120 * 121 * @param permissionResolver the permissionResolver to set on all of the wrapped realms that implement the 122 * {@link hunt.shiro.authz.permission.PermissionResolverAware PermissionResolverAware} interface. 123 */ 124 void setPermissionResolver(PermissionResolver permissionResolver) { 125 this.permissionResolver = permissionResolver; 126 applyPermissionResolverToRealms(); 127 } 128 129 /** 130 * Sets the internal {@link #getPermissionResolver} on any internal configured 131 * {@link #getRealms Realms} that implement the {@link hunt.shiro.authz.permission.PermissionResolverAware PermissionResolverAware} interface. 132 * <p/> 133 * This method is called after setting a permissionResolver on this ModularRealmAuthorizer via the 134 * {@link #setPermissionResolver(hunt.shiro.authz.permission.PermissionResolver) setPermissionResolver} method. 135 * <p/> 136 * It is also called after setting one or more realms via the {@link #setRealms setRealms} method to allow these 137 * newly available realms to be given the <code>PermissionResolver</code> already in use. 138 * 139 */ 140 protected void applyPermissionResolverToRealms() { 141 PermissionResolver resolver = getPermissionResolver(); 142 Realm[] realms = getRealms(); 143 if (resolver !is null && !realms.empty()) { 144 foreach (Realm realm; realms) { 145 auto realmCast = cast(PermissionResolverAware) realm; 146 if (realmCast !is null) { 147 realmCast.setPermissionResolver(resolver); 148 } 149 } 150 } 151 } 152 153 /** 154 * Returns the RolePermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default) 155 * if all realm instances will each configure their own permission resolver. 156 * 157 * @return the RolePermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default) 158 * if realm instances will each configure their own role permission resolver. 159 */ 160 RolePermissionResolver getRolePermissionResolver() { 161 return this.rolePermissionResolver; 162 } 163 164 /** 165 * Sets the specified {@link RolePermissionResolver RolePermissionResolver} on <em>all</em> of the wrapped realms that 166 * implement the {@link hunt.shiro.authz.permission.RolePermissionResolverAware PermissionResolverAware} interface. 167 * <p/> 168 * Only call this method if you want the permission resolver to be passed to all realms that implement the 169 * <code>RolePermissionResolver</code> interface. If you do not want this to occur, the realms must 170 * configure themselves individually (or be configured individually). 171 * 172 * @param rolePermissionResolver the rolePermissionResolver to set on all of the wrapped realms that implement the 173 * {@link hunt.shiro.authz.permission.RolePermissionResolverAware RolePermissionResolverAware} interface. 174 */ 175 void setRolePermissionResolver(RolePermissionResolver rolePermissionResolver) { 176 this.rolePermissionResolver = rolePermissionResolver; 177 applyRolePermissionResolverToRealms(); 178 } 179 180 /** 181 * Sets the internal {@link #getRolePermissionResolver} on any internal configured 182 * {@link #getRealms Realms} that implement the {@link hunt.shiro.authz.permission.RolePermissionResolverAware RolePermissionResolverAware} interface. 183 * <p/> 184 * This method is called after setting a rolePermissionResolver on this ModularRealmAuthorizer via the 185 * {@link #setRolePermissionResolver(hunt.shiro.authz.permission.RolePermissionResolver) setRolePermissionResolver} method. 186 * <p/> 187 * It is also called after setting one or more realms via the {@link #setRealms setRealms} method to allow these 188 * newly available realms to be given the <code>RolePermissionResolver</code> already in use. 189 * 190 */ 191 protected void applyRolePermissionResolverToRealms() { 192 RolePermissionResolver resolver = getRolePermissionResolver(); 193 Realm[] realms = getRealms(); 194 if (resolver !is null && realms.empty()) { 195 foreach (Realm realm; realms) { 196 auto realmCast = cast(RolePermissionResolverAware) realm; 197 if (realmCast !is null) { 198 realmCast.setRolePermissionResolver(resolver); 199 } 200 } 201 } 202 } 203 204 /** 205 * Used by the {@link Authorizer Authorizer} implementation methods to ensure that the {@link #setRealms realms} 206 * has been set. The default implementation ensures the property is not null and not empty. 207 * 208 * @throws IllegalStateException if the <tt>realms</tt> property is configured incorrectly. 209 */ 210 protected void assertRealmsConfigured() { 211 Realm[] realms = getRealms(); 212 if (realms.empty()) { 213 string msg = "Configuration error: No realms have been configured! One or more realms must be " 214 ~ "present to execute an authorization operation."; 215 throw new IllegalStateException(msg); 216 } 217 } 218 219 /** 220 * Returns <code>true</code> if any of the configured realms' 221 * {@link #isPermitted(hunt.shiro.subject.PrincipalCollection, string)} returns <code>true</code>, 222 * <code>false</code> otherwise. 223 */ 224 bool isPermitted(PrincipalCollection principals, string permission) { 225 assertRealmsConfigured(); 226 foreach (Realm realm; getRealms()) { 227 auto realmCast = cast(Authorizer) realm; 228 if (realmCast is null) 229 continue; 230 if (realmCast.isPermitted(principals, permission)) { 231 return true; 232 } 233 } 234 return false; 235 } 236 237 /** 238 * Returns <code>true</code> if any of the configured realms' 239 * {@link #isPermitted(hunt.shiro.subject.PrincipalCollection, Permission)} call returns <code>true</code>, 240 * <code>false</code> otherwise. 241 */ 242 bool isPermitted(PrincipalCollection principals, Permission permission) { 243 assertRealmsConfigured(); 244 foreach (Realm realm; getRealms()) { 245 auto realmCast = cast(Authorizer) realm; 246 if (realmCast is null) 247 continue; 248 if (realmCast.isPermitted(principals, permission)) { 249 return true; 250 } 251 } 252 return false; 253 } 254 255 /** 256 * Returns <code>true</code> if any of the configured realms' 257 * {@link #isPermittedAll(hunt.shiro.subject.PrincipalCollection, string...)} call returns 258 * <code>true</code>, <code>false</code> otherwise. 259 */ 260 bool[] isPermitted(PrincipalCollection principals, string[] permissions...) { 261 assertRealmsConfigured(); 262 if (permissions !is null && permissions.length > 0) { 263 bool[] r = new bool[permissions.length]; 264 for (int i = 0; i < permissions.length; i++) { 265 r[i] = isPermitted(principals, permissions[i]); 266 } 267 return r; 268 } 269 return new bool[0]; 270 } 271 272 /** 273 * Returns <code>true</code> if any of the configured realms' 274 * {@link #isPermitted(hunt.shiro.subject.PrincipalCollection, List)} call returns <code>true</code>, 275 * <code>false</code> otherwise. 276 */ 277 bool[] isPermitted(PrincipalCollection principals, List!(Permission) permissions) { 278 assertRealmsConfigured(); 279 if (permissions !is null && !permissions.isEmpty()) { 280 bool[] r = new bool[permissions.size()]; 281 int i = 0; 282 foreach (Permission p; permissions) { 283 r[i++] = isPermitted(principals, p); 284 } 285 return r; 286 } 287 288 return new bool[0]; 289 } 290 291 /** 292 * Returns <code>true</code> if any of the configured realms' 293 * {@link #isPermitted(hunt.shiro.subject.PrincipalCollection, string)} call returns <code>true</code> 294 * for <em>all</em> of the specified string permissions, <code>false</code> otherwise. 295 */ 296 bool isPermittedAll(PrincipalCollection principals, string[] permissions...) { 297 assertRealmsConfigured(); 298 if (permissions !is null && permissions.length > 0) { 299 foreach (string perm; permissions) { 300 if (!isPermitted(principals, perm)) { 301 return false; 302 } 303 } 304 } 305 return true; 306 } 307 308 /** 309 * Returns <code>true</code> if any of the configured realms' 310 * {@link #isPermitted(hunt.shiro.subject.PrincipalCollection, Permission)} call returns <code>true</code> 311 * for <em>all</em> of the specified Permissions, <code>false</code> otherwise. 312 */ 313 bool isPermittedAll(PrincipalCollection principals, Collection!(Permission) permissions) { 314 assertRealmsConfigured(); 315 if (permissions !is null && !permissions.isEmpty()) { 316 foreach (Permission permission; permissions) { 317 if (!isPermitted(principals, permission)) { 318 return false; 319 } 320 } 321 } 322 return true; 323 } 324 325 /** 326 * If !{@link #isPermitted(hunt.shiro.subject.PrincipalCollection, string) isPermitted(permission)}, throws 327 * an <code>UnauthorizedException</code> otherwise returns quietly. 328 */ 329 void checkPermission(PrincipalCollection principals, string permission) { 330 assertRealmsConfigured(); 331 if (!isPermitted(principals, permission)) { 332 throw new UnauthorizedException("Subject does not have permission [" ~ permission ~ "]"); 333 } 334 } 335 336 /** 337 * If !{@link #isPermitted(hunt.shiro.subject.PrincipalCollection, Permission) isPermitted(permission)}, throws 338 * an <code>UnauthorizedException</code> otherwise returns quietly. 339 */ 340 void checkPermission(PrincipalCollection principals, Permission permission) { 341 assertRealmsConfigured(); 342 if (!isPermitted(principals, permission)) { 343 throw new UnauthorizedException("Subject does not have permission [" ~ (cast(Object) permission) 344 .toString() ~ "]"); 345 } 346 } 347 348 /** 349 * If !{@link #isPermitted(hunt.shiro.subject.PrincipalCollection, string...) isPermitted(permission)}, 350 *<code>UnauthorizedException</code> otherwise returns quietly. 351 */ 352 void checkPermissions(PrincipalCollection principals, string[] permissions...) { 353 assertRealmsConfigured(); 354 if (permissions !is null && permissions.length > 0) { 355 foreach (string perm; permissions) { 356 checkPermission(principals, perm); 357 } 358 } 359 } 360 361 /** 362 * If !{@link #isPermitted(hunt.shiro.subject.PrincipalCollection, Permission) isPermitted(permission)} for 363 * <em>all</em> the given Permissions, throws 364 * an <code>UnauthorizedException</code> otherwise returns quietly. 365 */ 366 void checkPermissions(PrincipalCollection principals, Collection!(Permission) permissions) { 367 assertRealmsConfigured(); 368 if (permissions !is null) { 369 foreach (Permission permission; permissions) { 370 checkPermission(principals, permission); 371 } 372 } 373 } 374 375 /** 376 * Returns <code>true</code> if any of the configured realms' 377 * {@link #hasRole(hunt.shiro.subject.PrincipalCollection, string)} call returns <code>true</code>, 378 * <code>false</code> otherwise. 379 */ 380 bool hasRole(PrincipalCollection principals, string roleIdentifier) { 381 assertRealmsConfigured(); 382 foreach (Realm realm; getRealms()) { 383 Authorizer realmCast = cast(Authorizer) realm; 384 if (realmCast is null) 385 continue; 386 if (realmCast.hasRole(principals, roleIdentifier)) { 387 return true; 388 } 389 } 390 return false; 391 } 392 393 /** 394 * Calls {@link #hasRole(hunt.shiro.subject.PrincipalCollection, string)} for each role name in the specified 395 * collection and places the return value from each call at the respective location in the returned array. 396 */ 397 bool[] hasRoles(PrincipalCollection principals, List!(string) roleIdentifiers) { 398 if (roleIdentifiers is null || roleIdentifiers.isEmpty()) { 399 assertRealmsConfigured(); 400 return new bool[0]; 401 } 402 403 return hasRoles(principals, roleIdentifiers.toArray()); 404 405 } 406 407 /// ditto 408 bool[] hasRoles(PrincipalCollection principals, string[] roleIdentifiers) { 409 assertRealmsConfigured(); 410 411 if (roleIdentifiers.empty) { 412 return new bool[0]; 413 } 414 415 bool[] hasRoles = new bool[roleIdentifiers.length]; 416 int i = 0; 417 foreach (string roleId; roleIdentifiers) { 418 hasRoles[i++] = hasRole(principals, roleId); 419 } 420 return hasRoles; 421 } 422 423 /** 424 * Returns <code>true</code> iff any of the configured realms' 425 * {@link #hasRole(hunt.shiro.subject.PrincipalCollection, string)} call returns <code>true</code> for 426 * <em>all</em> roles specified, <code>false</code> otherwise. 427 */ 428 bool hasAllRoles(PrincipalCollection principals, Collection!(string) roleIdentifiers) { 429 return hasAllRoles(principals, roleIdentifiers.toArray()); 430 } 431 432 bool hasAllRoles(PrincipalCollection principals, string[] roleIdentifiers) { 433 assertRealmsConfigured(); 434 foreach (string roleIdentifier; roleIdentifiers) { 435 if (!hasRole(principals, roleIdentifier)) { 436 return false; 437 } 438 } 439 return true; 440 } 441 442 /** 443 * If !{@link #hasRole(hunt.shiro.subject.PrincipalCollection, string) hasRole(role)}, throws 444 * an <code>UnauthorizedException</code> otherwise returns quietly. 445 */ 446 void checkRole(PrincipalCollection principals, string role) { 447 assertRealmsConfigured(); 448 if (!hasRole(principals, role)) { 449 throw new UnauthorizedException("Subject does not have role [" ~ role ~ "]"); 450 } 451 } 452 453 /** 454 * Calls {@link #checkRoles(PrincipalCollection principals, string... roles) checkRoles(PrincipalCollection principals, string... roles) }. 455 */ 456 void checkRoles(PrincipalCollection principals, Collection!(string) roles) { 457 //SHIRO-234 - roles.toArray() -> roles.toArray(new string[roles.size()]) 458 if (roles !is null && !roles.isEmpty()) { 459 checkRoles(principals, roles.toArray()); 460 } 461 } 462 463 /** 464 * Calls {@link #checkRole(hunt.shiro.subject.PrincipalCollection, string) checkRole} for each role specified. 465 */ 466 void checkRoles(PrincipalCollection principals, string[] roles...) { 467 assertRealmsConfigured(); 468 if (roles !is null) { 469 foreach (string role; roles) { 470 checkRole(principals, role); 471 } 472 } 473 } 474 }