package org.pac4j.jax.rs.jersey.features; import java.util.List; import java.util.Optional; import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Singleton; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.ext.Providers; import org.glassfish.hk2.api.Factory; import org.glassfish.hk2.api.InjectionResolver; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.hk2.api.TypeLiteral; import org.glassfish.hk2.utilities.binding.AbstractBinder; import org.glassfish.jersey.internal.util.ReflectionHelper; import org.glassfish.jersey.internal.util.collection.ClassTypePair; import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory; import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider; import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider; import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver; import org.glassfish.jersey.server.model.Parameter; import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider; import org.pac4j.core.profile.CommonProfile; import org.pac4j.core.profile.ProfileManager; import org.pac4j.jax.rs.annotations.Pac4JProfile; import org.pac4j.jax.rs.annotations.Pac4JProfileManager; import org.pac4j.jax.rs.helpers.RequestJaxRsContext; import org.pac4j.jax.rs.helpers.RequestCommonProfile; import org.pac4j.jax.rs.helpers.RequestProfileManager; import org.pac4j.jax.rs.helpers.RequestPac4JSecurityContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * {@link Pac4JProfile @Pac4JProfile} injection value factory provider. * * Register a new {@link Binder} in order to enable this. * * @author Victor Noel - Linagora * @since 1.0.0 * */ public class Pac4JValueFactoryProvider { private static Logger LOG = LoggerFactory.getLogger(Pac4JValueFactoryProvider.class); static class Pac4JProfileValueFactoryProvider extends AbstractValueFactoryProvider { private final ProfileManagerFactoryBuilder manager; private final OptionalProfileFactoryBuilder optProfile; private final ProfileFactoryBuilder profile; @Inject protected Pac4JProfileValueFactoryProvider(ProfileManagerFactoryBuilder manager, OptionalProfileFactoryBuilder opt, ProfileFactoryBuilder profile, MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) { super(mpep, locator, Parameter.Source.UNKNOWN); this.manager = manager; this.optProfile = opt; this.profile = profile; } @Override protected Factory<?> createValueFactory(Parameter parameter) { if (parameter.isAnnotationPresent(Pac4JProfileManager.class)) { if (ProfileManager.class.isAssignableFrom(parameter.getRawType())) { return manager.get(); } throw new IllegalStateException("Cannot inject a Pac4J profile manager into a parameter of type " + parameter.getRawType().getName()); } if (parameter.isAnnotationPresent(Pac4JProfile.class)) { if (CommonProfile.class.isAssignableFrom(parameter.getRawType())) { return profile.get(); } if (Optional.class.isAssignableFrom(parameter.getRawType())) { List<ClassTypePair> ctps = ReflectionHelper.getTypeArgumentAndClass(parameter.getRawType()); ClassTypePair ctp = (ctps.size() == 1) ? ctps.get(0) : null; if (ctp == null || CommonProfile.class.isAssignableFrom(ctp.rawClass())) { return optProfile.get(); } } throw new IllegalStateException( "Cannot inject a Pac4J profile into a parameter of type " + parameter.getRawType().getName()); } return null; } } static class ProfileManagerInjectionResolver extends ParamInjectionResolver<Pac4JProfileManager> { ProfileManagerInjectionResolver() { super(Pac4JProfileValueFactoryProvider.class); } } static class ProfileInjectionResolver extends ParamInjectionResolver<Pac4JProfile> { ProfileInjectionResolver() { super(Pac4JProfileValueFactoryProvider.class); } } public interface OptionalProfileFactory extends Factory<Optional<CommonProfile>> { @Override default void dispose(Optional<CommonProfile> instance) { // do nothing } } public interface ProfileFactory extends Factory<CommonProfile> { @Override default void dispose(CommonProfile instance) { // do nothing } } public interface ProfileManagerFactory extends Factory<ProfileManager<CommonProfile>> { @Override default void dispose(ProfileManager<CommonProfile> instance) { // do nothing } } public interface OptionalProfileFactoryBuilder extends Supplier<OptionalProfileFactory> { } public interface ProfileFactoryBuilder extends Supplier<ProfileFactory> { } public interface ProfileManagerFactoryBuilder extends Supplier<ProfileManagerFactory> { } public static class Binder extends AbstractBinder { private final ProfileFactoryBuilder profile; private final OptionalProfileFactoryBuilder optProfile; private final ProfileManagerFactoryBuilder manager; /** * Use this in your applications */ public Binder() { this(null, null, null); } /** * Use this if you want to mock the {@link CommonProfile} or the {@link ProfileManager}. * * @param profile * a builder for a {@link CommonProfile}, can be <code>null</code> and default will be used. * @param optProfile * a builder for an {@link Optional} of {@link CommonProfile}, can be <code>null</code> and default * will be used. * @param manager * a builder for a {@link ProfileManager}, can be <code>null</code> and default will be used. */ public Binder(ProfileFactoryBuilder profile, OptionalProfileFactoryBuilder optProfile, ProfileManagerFactoryBuilder manager) { this.profile = profile == null ? ProfileValueFactory::new : profile; this.optProfile = optProfile == null ? OptionalProfileValueFactory::new : optProfile; this.manager = manager == null ? ProfileManagerValueFactory::new : manager; } /** * Use this if you want to always return the same {@link CommonProfile} (or none with <code>null</code>). * * Note that it won't mock the profile coming out of {@link ProfileManager}! * * @param profile * a profile, can be <code>null</code>. */ public Binder(CommonProfile profile) { this(() -> () -> profile, () -> () -> Optional.ofNullable(profile), null); } /** * Use this if you want to return a dynamically supplied {@link CommonProfile} (or none with <code>null</code>). * * Note that it won't mock the profile coming out of {@link ProfileManager}! * * @param profile * a profile supplier, can return <code>null</code>. */ public Binder(Supplier<CommonProfile> profile) { this(() -> () -> profile.get(), () -> () -> Optional.ofNullable(profile.get()), null); } @Override protected void configure() { bind(profile).to(ProfileFactoryBuilder.class); bind(optProfile).to(OptionalProfileFactoryBuilder.class); bind(manager).to(ProfileManagerFactoryBuilder.class); bind(Pac4JProfileValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class); bind(ProfileInjectionResolver.class).to(new TypeLiteral<InjectionResolver<Pac4JProfile>>() { }).in(Singleton.class); bind(ProfileManagerInjectionResolver.class).to(new TypeLiteral<InjectionResolver<Pac4JProfileManager>>() { }).in(Singleton.class); } } static class ProfileManagerValueFactory extends AbstractContainerRequestValueFactory<ProfileManager<CommonProfile>> implements ProfileManagerFactory { @Context Providers providers; @Override public ProfileManager<CommonProfile> provide() { return new RequestProfileManager(new RequestJaxRsContext(providers, getContainerRequest())) .profileManager(); } } static class ProfileValueFactory extends AbstractContainerRequestValueFactory<CommonProfile> implements ProfileFactory { @Override public CommonProfile provide() { return new RequestCommonProfile(new RequestPac4JSecurityContext(getContainerRequest())).profile() .orElseThrow(() -> { LOG.debug("Cannot inject a Pac4j profile into an unauthenticated request, responding with 401"); return new WebApplicationException(401); }); } } static class OptionalProfileValueFactory extends AbstractContainerRequestValueFactory<Optional<CommonProfile>> implements OptionalProfileFactory { @Override public Optional<CommonProfile> provide() { return new RequestCommonProfile(new RequestPac4JSecurityContext(getContainerRequest())).profile(); } } }