001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.shiro.authz;
020
021import org.apache.shiro.authz.permission.PermissionResolver;
022import org.apache.shiro.authz.permission.PermissionResolverAware;
023import org.apache.shiro.authz.permission.RolePermissionResolver;
024import org.apache.shiro.authz.permission.RolePermissionResolverAware;
025import org.apache.shiro.realm.Realm;
026import org.apache.shiro.subject.PrincipalCollection;
027
028import java.util.Collection;
029import java.util.List;
030
031
032/**
033 * A <tt>ModularRealmAuthorizer</tt> is an <tt>Authorizer</tt> implementation that consults one or more configured
034 * {@link Realm Realm}s during an authorization operation.
035 *
036 * @since 0.2
037 */
038public class ModularRealmAuthorizer implements Authorizer, PermissionResolverAware, RolePermissionResolverAware {
039
040    /**
041     * The realms to consult during any authorization check.
042     */
043    protected Collection<Realm> realms;
044
045    /**
046     * A PermissionResolver to be used by <em>all</em> configured realms.  Leave <code>null</code> if you wish
047     * to configure different resolvers for different realms.
048     */
049    protected PermissionResolver permissionResolver;
050
051    /**
052     * A RolePermissionResolver to be used by <em>all</em> configured realms.  Leave <code>null</code> if you wish
053     * to configure different resolvers for different realms.
054     */
055    protected RolePermissionResolver rolePermissionResolver;
056
057    /**
058     * Default no-argument constructor, does nothing.
059     */
060    public ModularRealmAuthorizer() {
061    }
062
063    /**
064     * Constructor that accepts the <code>Realm</code>s to consult during an authorization check.  Immediately calls
065     * {@link #setRealms setRealms(realms)}.
066     *
067     * @param realms the realms to consult during an authorization check.
068     */
069    public ModularRealmAuthorizer(Collection<Realm> realms) {
070        setRealms(realms);
071    }
072
073    /**
074     * Returns the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check.
075     *
076     * @return the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check.
077     */
078    public Collection<Realm> getRealms() {
079        return this.realms;
080    }
081
082    /**
083     * Sets the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check.
084     *
085     * @param realms the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check.
086     */
087    public void setRealms(Collection<Realm> realms) {
088        this.realms = realms;
089        applyPermissionResolverToRealms();
090        applyRolePermissionResolverToRealms();
091    }
092
093    /**
094     * Returns the PermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default)
095     * if all realm instances will each configure their own permission resolver.
096     *
097     * @return the PermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default)
098     * if realm instances will each configure their own permission resolver.
099     * @since 1.0
100     */
101    public PermissionResolver getPermissionResolver() {
102        return this.permissionResolver;
103    }
104
105    /**
106     * Sets the specified {@link PermissionResolver PermissionResolver} on <em>all</em> of the wrapped realms that
107     * implement the {@link org.apache.shiro.authz.permission.PermissionResolverAware PermissionResolverAware} interface.
108     * <p/>
109     * Only call this method if you want the permission resolver to be passed to all realms that implement the
110     * <code>PermissionResolver</code> interface.  If you do not want this to occur, the realms must
111     * configure themselves individually (or be configured individually).
112     *
113     * @param permissionResolver the permissionResolver to set on all the wrapped realms that implement the
114     *                           {@link org.apache.shiro.authz.permission.PermissionResolverAware PermissionResolverAware}
115     *                           interface.
116     */
117    public void setPermissionResolver(PermissionResolver permissionResolver) {
118        this.permissionResolver = permissionResolver;
119        applyPermissionResolverToRealms();
120    }
121
122    /**
123     * Sets the internal {@link #getPermissionResolver} on any internal configured
124     * {@link #getRealms Realms} that implement
125     * the {@link org.apache.shiro.authz.permission.PermissionResolverAware PermissionResolverAware}interface.
126     * <p/>
127     * This method is called after setting a permissionResolver on this ModularRealmAuthorizer via the
128     * {@link #setPermissionResolver(org.apache.shiro.authz.permission.PermissionResolver) setPermissionResolver} method.
129     * <p/>
130     * It is also called after setting one or more realms via the {@link #setRealms setRealms} method to allow these
131     * newly available realms to be given the <code>PermissionResolver</code> already in use.
132     *
133     * @since 1.0
134     */
135    protected void applyPermissionResolverToRealms() {
136        PermissionResolver resolver = getPermissionResolver();
137        Collection<Realm> realms = getRealms();
138        if (resolver != null && realms != null && !realms.isEmpty()) {
139            for (Realm realm : realms) {
140                if (realm instanceof PermissionResolverAware) {
141                    ((PermissionResolverAware) realm).setPermissionResolver(resolver);
142                }
143            }
144        }
145    }
146
147    /**
148     * Returns the RolePermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default)
149     * if all realm instances will each configure their own permission resolver.
150     *
151     * @return the RolePermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default)
152     * if realm instances will each configure their own role permission resolver.
153     * @since 1.0
154     */
155    public RolePermissionResolver getRolePermissionResolver() {
156        return this.rolePermissionResolver;
157    }
158
159    /**
160     * Sets the specified {@link RolePermissionResolver RolePermissionResolver} on <em>all</em> of the wrapped realms that
161     * implement the {@link org.apache.shiro.authz.permission.RolePermissionResolverAware PermissionResolverAware} interface.
162     * <p/>
163     * Only call this method if you want the permission resolver to be passed to all realms that implement the
164     * <code>RolePermissionResolver</code> interface.  If you do not want this to occur, the realms must
165     * configure themselves individually (or be configured individually).
166     *
167     * @param rolePermissionResolver the rolePermissionResolver to set on all the wrapped realms that implement the
168     *                          {@link org.apache.shiro.authz.permission.RolePermissionResolverAware RolePermissionResolverAware}
169     *                          interface.
170     */
171    public void setRolePermissionResolver(RolePermissionResolver rolePermissionResolver) {
172        this.rolePermissionResolver = rolePermissionResolver;
173        applyRolePermissionResolverToRealms();
174    }
175
176
177    /**
178     * Sets the internal {@link #getRolePermissionResolver} on any internal configured
179     * {@link #getRealms Realms} that implement the
180     * {@link org.apache.shiro.authz.permission.RolePermissionResolverAware RolePermissionResolverAware} interface.
181     * <p/>
182     * This method is called after setting a rolePermissionResolver on this ModularRealmAuthorizer via the
183     * {@link #setRolePermissionResolver(org.apache.shiro.authz.permission.RolePermissionResolver) setRolePermissionResolver}
184     * method.
185     * <p/>
186     * It is also called after setting one or more realms via the {@link #setRealms setRealms} method to allow these
187     * newly available realms to be given the <code>RolePermissionResolver</code> already in use.
188     *
189     * @since 1.0
190     */
191    protected void applyRolePermissionResolverToRealms() {
192        RolePermissionResolver resolver = getRolePermissionResolver();
193        Collection<Realm> realms = getRealms();
194        if (resolver != null && realms != null && !realms.isEmpty()) {
195            for (Realm realm : realms) {
196                if (realm instanceof RolePermissionResolverAware) {
197                    ((RolePermissionResolverAware) realm).setRolePermissionResolver(resolver);
198                }
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() throws IllegalStateException {
211        Collection<Realm> realms = getRealms();
212        if (realms == null || realms.isEmpty()) {
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(org.apache.shiro.subject.PrincipalCollection, String)} returns <code>true</code>,
222     * <code>false</code> otherwise.
223     */
224    public boolean isPermitted(PrincipalCollection principals, String permission) {
225        assertRealmsConfigured();
226        for (Realm realm : getRealms()) {
227            if (!(realm instanceof Authorizer)) {
228                continue;
229            }
230            if (((Authorizer) realm).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(org.apache.shiro.subject.PrincipalCollection, Permission)} call returns <code>true</code>,
240     * <code>false</code> otherwise.
241     */
242    public boolean isPermitted(PrincipalCollection principals, Permission permission) {
243        assertRealmsConfigured();
244        for (Realm realm : getRealms()) {
245            if (!(realm instanceof Authorizer)) {
246                continue;
247            }
248            if (((Authorizer) realm).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(org.apache.shiro.subject.PrincipalCollection, String...)} call returns
258     * <code>true</code>, <code>false</code> otherwise.
259     */
260    public boolean[] isPermitted(PrincipalCollection principals, String... permissions) {
261        assertRealmsConfigured();
262        if (permissions != null && permissions.length > 0) {
263            boolean[] isPermitted = new boolean[permissions.length];
264            for (int i = 0; i < permissions.length; i++) {
265                isPermitted[i] = isPermitted(principals, permissions[i]);
266            }
267            return isPermitted;
268        }
269        return new boolean[0];
270    }
271
272    /**
273     * Returns <code>true</code> if any of the configured realms'
274     * {@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, List)} call returns <code>true</code>,
275     * <code>false</code> otherwise.
276     */
277    public boolean[] isPermitted(PrincipalCollection principals, List<Permission> permissions) {
278        assertRealmsConfigured();
279        if (permissions != null && !permissions.isEmpty()) {
280            boolean[] isPermitted = new boolean[permissions.size()];
281            int i = 0;
282            for (Permission p : permissions) {
283                isPermitted[i++] = isPermitted(principals, p);
284            }
285            return isPermitted;
286        }
287
288        return new boolean[0];
289    }
290
291    /**
292     * Returns <code>true</code> if any of the configured realms'
293     * {@link #isPermitted(org.apache.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    public boolean isPermittedAll(PrincipalCollection principals, String... permissions) {
297        assertRealmsConfigured();
298        if (permissions != null && permissions.length > 0) {
299            for (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(org.apache.shiro.subject.PrincipalCollection, Permission)} call returns <code>true</code>
311     * for <em>all</em> of the specified Permissions, <code>false</code> otherwise.
312     */
313    public boolean isPermittedAll(PrincipalCollection principals, Collection<Permission> permissions) {
314        assertRealmsConfigured();
315        if (permissions != null && !permissions.isEmpty()) {
316            for (Permission permission : permissions) {
317                if (!isPermitted(principals, permission)) {
318                    return false;
319                }
320            }
321        }
322        return true;
323    }
324
325    /**
326     * If !{@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, String) isPermitted(permission)}, throws
327     * an <code>UnauthorizedException</code> otherwise returns quietly.
328     */
329    public void checkPermission(PrincipalCollection principals, String permission) throws AuthorizationException {
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(org.apache.shiro.subject.PrincipalCollection, Permission) isPermitted(permission)}, throws
338     * an <code>UnauthorizedException</code> otherwise returns quietly.
339     */
340    public void checkPermission(PrincipalCollection principals, Permission permission) throws AuthorizationException {
341        assertRealmsConfigured();
342        if (!isPermitted(principals, permission)) {
343            throw new UnauthorizedException("Subject does not have permission [" + permission + "]");
344        }
345    }
346
347    /**
348     * If !{@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, String...) isPermitted(permission)},
349     * throws an <code>UnauthorizedException</code> otherwise returns quietly.
350     */
351    public void checkPermissions(PrincipalCollection principals, String... permissions) throws AuthorizationException {
352        assertRealmsConfigured();
353        if (permissions != null && permissions.length > 0) {
354            for (String perm : permissions) {
355                checkPermission(principals, perm);
356            }
357        }
358    }
359
360    /**
361     * If !{@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, Permission) isPermitted(permission)} for
362     * <em>all</em> the given Permissions, throws
363     * an <code>UnauthorizedException</code> otherwise returns quietly.
364     */
365    public void checkPermissions(PrincipalCollection principals, Collection<Permission> permissions)
366            throws AuthorizationException {
367        assertRealmsConfigured();
368        if (permissions != null) {
369            for (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(org.apache.shiro.subject.PrincipalCollection, String)} call returns <code>true</code>,
378     * <code>false</code> otherwise.
379     */
380    public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
381        assertRealmsConfigured();
382        for (Realm realm : getRealms()) {
383            if (!(realm instanceof Authorizer)) {
384                continue;
385            }
386            if (((Authorizer) realm).hasRole(principals, roleIdentifier)) {
387                return true;
388            }
389        }
390        return false;
391    }
392
393    /**
394     * Calls {@link #hasRole(org.apache.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    public boolean[] hasRoles(PrincipalCollection principals, List<String> roleIdentifiers) {
398        assertRealmsConfigured();
399        if (roleIdentifiers != null && !roleIdentifiers.isEmpty()) {
400            boolean[] hasRoles = new boolean[roleIdentifiers.size()];
401            int i = 0;
402            for (String roleId : roleIdentifiers) {
403                hasRoles[i++] = hasRole(principals, roleId);
404            }
405            return hasRoles;
406        }
407
408        return new boolean[0];
409    }
410
411    /**
412     * Returns <code>true</code> iff any of the configured realms'
413     * {@link #hasRole(org.apache.shiro.subject.PrincipalCollection, String)} call returns <code>true</code> for
414     * <em>all</em> roles specified, <code>false</code> otherwise.
415     */
416    public boolean hasAllRoles(PrincipalCollection principals, Collection<String> roleIdentifiers) {
417        assertRealmsConfigured();
418        for (String roleIdentifier : roleIdentifiers) {
419            if (!hasRole(principals, roleIdentifier)) {
420                return false;
421            }
422        }
423        return true;
424    }
425
426    /**
427     * If !{@link #hasRole(org.apache.shiro.subject.PrincipalCollection, String) hasRole(role)}, throws
428     * an <code>UnauthorizedException</code> otherwise returns quietly.
429     */
430    public void checkRole(PrincipalCollection principals, String role) throws AuthorizationException {
431        assertRealmsConfigured();
432        if (!hasRole(principals, role)) {
433            throw new UnauthorizedException("Subject does not have role [" + role + "]");
434        }
435    }
436
437    /**
438     * Calls {@link #checkRoles(PrincipalCollection principals, String... roles)
439     * checkRoles(PrincipalCollection principals, String... roles) }.
440     */
441    public void checkRoles(PrincipalCollection principals, Collection<String> roles) throws AuthorizationException {
442        //SHIRO-234 - roles.toArray() -> roles.toArray(new String[roles.size()])
443        if (roles != null && !roles.isEmpty()) {
444            checkRoles(principals, roles.toArray(new String[roles.size()]));
445        }
446    }
447
448    /**
449     * Calls {@link #checkRole(org.apache.shiro.subject.PrincipalCollection, String) checkRole} for each role specified.
450     */
451    public void checkRoles(PrincipalCollection principals, String... roles) throws AuthorizationException {
452        assertRealmsConfigured();
453        if (roles != null) {
454            for (String role : roles) {
455                checkRole(principals, role);
456            }
457        }
458    }
459}