/* * * * Copyright (c) 2016. David Sowerby * * * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * * specific language governing permissions and limitations under the License. * */ package uk.q3c.krail.core.shiro.aop; import com.google.common.collect.ImmutableSet; import com.google.inject.AbstractModule; import com.google.inject.Provider; import com.google.inject.matcher.Matchers; import com.vaadin.server.VaadinSession; import org.aopalliance.intercept.MethodInterceptor; import org.apache.shiro.SecurityUtils; import org.apache.shiro.ShiroException; import org.apache.shiro.authz.annotation.*; import org.apache.shiro.guice.aop.ShiroAopModule; import org.apache.shiro.subject.Subject; import uk.q3c.krail.core.shiro.SubjectProvider; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; /** * A replacement of the {@link ShiroAopModule}. This module, together with its associated {@link MethodInterceptor} implementations, uses {@link * SubjectProvider} in place of {@link SecurityUtils#getSubject()} - Krail (and Vaadin generally) cannot use the latter, because the {@link Subject} must be * tied to a {@link VaadinSession} * <p> * By default this module only binds an interceptor for the {@link RequiresPermissions} annotation, but others can be added either by overriding {@link * #define}, or by calling {@link #select(Class)} or {@link #selectAll()} from your Binding Manager... for example: * <p> * Override<br> * protected Module shiroAopModule() {<br> * return new KrailShiroAopModule().selectAll();<br> * } <br> * <p> * using either:<br> * <p> * new KrailShiroAopModule().select(RequiresGuest.class) or<br> * new KrailShiroAopModule().selectAll() * <p> * Created by David Sowerby on 10/06/15. */ public class KrailShiroAopModule extends AbstractModule { @SuppressWarnings("unchecked") private static final Class<? extends Annotation> allowedAnnotations[] = new Class[]{RequiresPermissions.class, RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class, RequiresRoles.class}; private Set<Class<? extends Annotation>> selectedAnnotations = new HashSet<>(); /** * {@inheritDoc} */ @Override protected void configure() { bindResolver(); define(); final Provider<SubjectProvider> servicesModelProvider = this.getProvider(SubjectProvider.class); final Provider<AnnotationResolver> annotationResolverProvider = this.getProvider(AnnotationResolver.class); bindInterceptors(servicesModelProvider, annotationResolverProvider); } /** * Define the interceptors to use by calling {@link #select(Class)}. Default is to apply the {@link RequiresPermissions} only. You can also define * directly from your BindingManager by calling new KrailShiroAopModule().select(RequiresGuest.class), or new KrailShiroAopModule().selectAll() */ protected void define() { select(RequiresPermissions.class); } public KrailShiroAopModule select(Class<? extends Annotation> annotationClass) { List<Class<? extends Annotation>> allowed = Arrays.asList(allowedAnnotations); if (!allowed.contains(annotationClass)) { throw new ShiroException("Only Shiro annotations are valid"); } selectedAnnotations.add(annotationClass); return this; } protected void bindInterceptors(Provider<SubjectProvider> subjectProviderProvider, Provider<AnnotationResolver> annotationResolverProvider) { if (selectedAnnotations.contains(RequiresPermissions.class)) { bindMethodInterceptor((RequiresPermissions.class), permissionsInterceptor(subjectProviderProvider, annotationResolverProvider)); } if (selectedAnnotations.contains(RequiresUser.class)) { bindMethodInterceptor((RequiresUser.class), userInterceptor(subjectProviderProvider, annotationResolverProvider)); } if (selectedAnnotations.contains(RequiresGuest.class)) { bindMethodInterceptor((RequiresGuest.class), guestInterceptor(subjectProviderProvider, annotationResolverProvider)); } if (selectedAnnotations.contains(RequiresAuthentication.class)) { bindMethodInterceptor((RequiresAuthentication.class), authenticatedInterceptor(subjectProviderProvider, annotationResolverProvider)); } if (selectedAnnotations.contains(RequiresRoles.class)) { bindMethodInterceptor((RequiresRoles.class), rolesInterceptor(subjectProviderProvider, annotationResolverProvider)); } } private void bindMethodInterceptor(Class<? extends Annotation> shiroAnnotationClass, MethodInterceptor methodInterceptor) { // requestInjection(methodInterceptor); bindInterceptor(Matchers.any(), Matchers.annotatedWith(shiroAnnotationClass), methodInterceptor); } protected PermissionsMethodInterceptor permissionsInterceptor(Provider<SubjectProvider> subjectProviderProvider, Provider<AnnotationResolver> annotationResolverProvider) { return new PermissionsMethodInterceptor(subjectProviderProvider, annotationResolverProvider); } protected RolesMethodInterceptor rolesInterceptor(Provider<SubjectProvider> subjectProviderProvider, Provider<AnnotationResolver> annotationResolverProvider) { return new RolesMethodInterceptor(subjectProviderProvider, annotationResolverProvider); } protected AuthenticatedMethodInterceptor authenticatedInterceptor(Provider<SubjectProvider> subjectProviderProvider, Provider<AnnotationResolver> annotationResolverProvider) { return new AuthenticatedMethodInterceptor(subjectProviderProvider, annotationResolverProvider); } protected UserMethodInterceptor userInterceptor(Provider<SubjectProvider> subjectProviderProvider, Provider<AnnotationResolver> annotationResolverProvider) { return new UserMethodInterceptor(subjectProviderProvider, annotationResolverProvider); } protected GuestMethodInterceptor guestInterceptor(Provider<SubjectProvider> subjectProviderProvider, Provider<AnnotationResolver> annotationResolverProvider) { return new GuestMethodInterceptor(subjectProviderProvider, annotationResolverProvider); } protected void bindResolver() { bind(AnnotationResolver.class).to(DefaultAnnotationResolver.class); } public KrailShiroAopModule selectAll() { select(RequiresRoles.class); select(RequiresGuest.class); select(RequiresAuthentication.class); select(RequiresUser.class); return this; } /** * Useful only for testing */ public ImmutableSet<Class<? extends Annotation>> getSelectedAnnotations() { return ImmutableSet.copyOf(selectedAnnotations); } }