package org.pac4j.play.deadbolt2;
import be.objectify.deadbolt.java.DeadboltHandler;
import be.objectify.deadbolt.java.DynamicResourceHandler;
import be.objectify.deadbolt.java.models.Permission;
import be.objectify.deadbolt.java.models.Subject;
import org.pac4j.core.client.Client;
import org.pac4j.core.config.Config;
import org.pac4j.core.engine.DefaultSecurityLogic;
import org.pac4j.core.exception.HttpAction;
import org.pac4j.core.exception.TechnicalException;
import org.pac4j.core.http.HttpActionAdapter;
import org.pac4j.core.profile.CommonProfile;
import org.pac4j.core.profile.ProfileManager;
import org.pac4j.core.util.CommonHelper;
import org.pac4j.play.PlayWebContext;
import org.pac4j.play.store.PlaySessionStore;
import play.libs.concurrent.HttpExecutionContext;
import play.mvc.Http;
import play.mvc.Result;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
/**
* This is the deadbolt handler for pac4j: the deadbolt subject is built from the pac4j user profile.
* If no pac4j profile exists, the user is redirected to the identity provider for login for indirect clients; otherwise, a 401 error is returned.
* If the subject is not allowed, a 403 error is returned.
*
* @author Jerome Leleu
* @since 2.6.0
*/
public class Pac4jHandler extends DefaultSecurityLogic<Result, PlayWebContext> implements DeadboltHandler {
private final Config config;
private final HttpExecutionContext httpExecutionContext;
private final String clients;
private final PlaySessionStore playSessionStore;
private final Pac4jRoleHandler rolePermissionsHandler;
public Pac4jHandler(final Config config, final HttpExecutionContext httpExecutionContext, final String clients, final PlaySessionStore playSessionStore, final Pac4jRoleHandler rolePermissionsHandler) {
CommonHelper.assertNotNull("config", config);
CommonHelper.assertNotNull("httpExecutionContext", httpExecutionContext);
CommonHelper.assertNotNull("playSessionStore", playSessionStore);
this.config = config;
this.httpExecutionContext = httpExecutionContext;
this.clients = clients;
this.playSessionStore = playSessionStore;
this.rolePermissionsHandler = rolePermissionsHandler;
}
@Override
public CompletionStage<Optional<Result>> beforeAuthCheck(final Http.Context context) {
return CompletableFuture.supplyAsync(() -> {
final Optional<CommonProfile> profile = getProfile(context);
if (profile.isPresent()) {
logger.debug("no profile found -> returning empty");
return Optional.empty();
} else {
final PlayWebContext playWebContext = new PlayWebContext(context, playSessionStore);
final HttpActionAdapter<Result, PlayWebContext> httpActionAdapter = config.getHttpActionAdapter();
final List<Client> currentClients = getClientFinder().find(config.getClients(), playWebContext, clients);
logger.debug("currentClients: {}", currentClients);
HttpAction action;
try {
if (startAuthentication(playWebContext, currentClients)) {
logger.debug("Starting authentication");
saveRequestedUrl(playWebContext, currentClients);
action = redirectToIdentityProvider(playWebContext, currentClients);
} else {
logger.debug("unauthorized");
action = unauthorized(playWebContext, currentClients);
}
} catch (final HttpAction e) {
action = e;
}
return Optional.of(httpActionAdapter.adapt(action.getCode(), playWebContext));
}
}, httpExecutionContext.current());
}
@Override
public CompletionStage<Optional<? extends Subject>> getSubject(final Http.Context context) {
return CompletableFuture.supplyAsync(() -> {
final Optional<CommonProfile> profile = getProfile(context);
if (profile.isPresent()) {
logger.debug("profile found: {} -> building a subject", profile);
return Optional.of(new Pac4jSubject(profile.get()));
} else {
logger.debug("no profile found -> returning empty");
return Optional.empty();
}
}, httpExecutionContext.current());
}
@Override
public CompletionStage<List<? extends Permission>> getPermissionsForRole(String roleName) {
return rolePermissionsHandler.getPermissionsForRole(clients, roleName, httpExecutionContext);
}
private Optional<CommonProfile> getProfile(final Http.Context context) {
final PlayWebContext playWebContext = new PlayWebContext(context, playSessionStore);
final ProfileManager manager = new ProfileManager(playWebContext);
return manager.get(true);
}
@Override
public CompletionStage<Result> onAuthFailure(final Http.Context context, final Optional<String> content) {
return CompletableFuture.supplyAsync(() -> {
final PlayWebContext playWebContext = new PlayWebContext(context, playSessionStore);
final HttpActionAdapter<Result, PlayWebContext> httpActionAdapter = config.getHttpActionAdapter();
return httpActionAdapter.adapt(403, playWebContext);
}, httpExecutionContext.current());
}
@Override
public CompletionStage<Optional<DynamicResourceHandler>> getDynamicResourceHandler(final Http.Context context) {
throw new TechnicalException("getDynamicResourceHandler() not supported in Pac4jHandler");
}
}