package org.pac4j.vertx.handler.impl;
import io.vertx.core.Vertx;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.impl.AuthHandlerImpl;
import org.pac4j.core.client.IndirectClient;
import org.pac4j.core.config.Config;
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.core.engine.DefaultSecurityLogic;
import org.pac4j.core.engine.SecurityLogic;
import org.pac4j.core.exception.TechnicalException;
import org.pac4j.core.http.HttpActionAdapter;
import org.pac4j.core.profile.CommonProfile;
import org.pac4j.core.util.CommonHelper;
import org.pac4j.vertx.VertxProfileManager;
import org.pac4j.vertx.VertxWebContext;
import org.pac4j.vertx.auth.Pac4jAuthProvider;
import org.pac4j.vertx.auth.Pac4jUser;
import org.pac4j.vertx.http.DefaultHttpActionAdapter;
import java.util.Map;
import static java.util.stream.Collectors.toMap;
/**
* @author Jeremy Prime
* @since 2.0.0
*/
public class SecurityHandler extends AuthHandlerImpl {
private static final Logger LOG = LoggerFactory.getLogger(SecurityHandler.class);
protected final Config config;
protected final String clientNames;
protected final String authorizerName;
protected final String matcherName;
protected final boolean multiProfile;
protected final Vertx vertx;
private final SessionStore<VertxWebContext> sessionStore;
protected final HttpActionAdapter<Void, VertxWebContext> httpActionAdapter = new DefaultHttpActionAdapter();
private final SecurityLogic<Void, VertxWebContext> securityLogic;
public SecurityHandler(final Vertx vertx,
final SessionStore<VertxWebContext> sessionStore,
final Config config, final Pac4jAuthProvider authProvider,
final SecurityHandlerOptions options) {
super(authProvider);
CommonHelper.assertNotNull("vertx", vertx);
CommonHelper.assertNotNull("sessionStore", sessionStore);
CommonHelper.assertNotNull("config", config);
CommonHelper.assertNotNull("config.getClients()", config.getClients());
CommonHelper.assertNotNull("authProvider", authProvider);
CommonHelper.assertNotNull("options", options);
clientNames = options.getClients();
authorizerName = options.getAuthorizers();
matcherName = options.getMatchers();
multiProfile = options.isMultiProfile();
this.vertx = vertx;
this.sessionStore = sessionStore;
this.config = config;
final DefaultSecurityLogic<Void, VertxWebContext> securityLogic = new DefaultSecurityLogic<>();
securityLogic.setProfileManagerFactory(VertxProfileManager::new);
this.securityLogic = securityLogic;
}
// Port of Pac4J auth to a handler in vert.x 3.
@Override
public void handle(final RoutingContext routingContext) {
// No longer sufficient to check whether we have a user, as the user must have an appropriate pac4j profile
// so a user != null check is insufficient, so we'll go straight to the pac4j logic check
// Note that at present the security logic call is blocking (and authorization contained within can also
// be blocking) so we have to wrap the following call in an executeBlocking call to avoid blocking the
// event loop
final VertxWebContext webContext = new VertxWebContext(routingContext, sessionStore);
// Strip out DirectClients from pac4j user at this point
final Pac4jUser pac4jUser = (Pac4jUser) routingContext.user();
if (pac4jUser != null) {
final Map<String, CommonProfile> indirectProfiles = pac4jUser.pac4jUserProfiles().entrySet().stream()
.filter(e -> {
final String clientName = e.getValue().getClientName();
return (config.getClients().findClient(clientName) instanceof IndirectClient);
})
.collect(toMap(e -> e.getKey(), e -> e.getValue()));
if (!indirectProfiles.isEmpty()) {
pac4jUser.pac4jUserProfiles().clear();
pac4jUser.pac4jUserProfiles().putAll(indirectProfiles);
} else {
routingContext.clearUser();
}
}
vertx.executeBlocking(future -> securityLogic.perform(webContext, config,
(ctx, parameters) -> {
// This is what should occur if we are authenticated and authorized to view the requested
// resource
LOG.info("Authorised to view resource " + routingContext.request().path());
routingContext.next();
return null;
},
httpActionAdapter,
clientNames,
authorizerName,
matcherName,
multiProfile),
asyncResult -> {
// If we succeeded we're all good here, the job is done either through approving, or redirect, or
// forbidding
// However, if an error occurred we need to handle this here
if (asyncResult.failed()) {
unexpectedFailure(routingContext, asyncResult.cause());
}
});
}
protected void unexpectedFailure(final RoutingContext context, Throwable failure) {
context.fail(toTechnicalException(failure));
}
protected final TechnicalException toTechnicalException(final Throwable t) {
return (t instanceof TechnicalException) ? (TechnicalException) t : new TechnicalException(t);
}
}