/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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 org.keycloak.adapters.elytron;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.security.auth.callback.CallbackHandler;
import org.jboss.logging.Logger;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.UserSessionManagement;
import org.wildfly.security.http.HttpAuthenticationException;
import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.HttpServerAuthenticationMechanism;
import org.wildfly.security.http.HttpServerRequest;
import org.wildfly.security.http.Scope;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticationMechanism {
static Logger LOGGER = Logger.getLogger(KeycloakHttpServerAuthenticationMechanismFactory.class);
static final String NAME = "KEYCLOAK";
private final Map<String, ?> properties;
private final CallbackHandler callbackHandler;
private final AdapterDeploymentContext deploymentContext;
public KeycloakHttpServerAuthenticationMechanism(Map<String, ?> properties, CallbackHandler callbackHandler, AdapterDeploymentContext deploymentContext) {
this.properties = properties;
this.callbackHandler = callbackHandler;
this.deploymentContext = deploymentContext;
}
@Override
public String getMechanismName() {
return NAME;
}
@Override
public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException {
LOGGER.debugf("Evaluating request for path [%s]", request.getRequestURI());
AdapterDeploymentContext deploymentContext = getDeploymentContext(request);
if (deploymentContext == null) {
LOGGER.debugf("Ignoring request for path [%s] from mechanism [%s]. No deployment context found.", request.getRequestURI());
request.noAuthenticationInProgress();
return;
}
ElytronHttpFacade httpFacade = new ElytronHttpFacade(request, deploymentContext, callbackHandler);
KeycloakDeployment deployment = httpFacade.getDeployment();
if (!deployment.isConfigured()) {
request.noAuthenticationInProgress();
return;
}
RequestAuthenticator authenticator = createRequestAuthenticator(request, httpFacade, deployment);
httpFacade.getTokenStore().checkCurrentToken();
if (preActions(httpFacade, deploymentContext)) {
LOGGER.debugf("Pre-actions has aborted the evaluation of [%s]", request.getRequestURI());
httpFacade.authenticationInProgress();
return;
}
AuthOutcome outcome = authenticator.authenticate();
if (AuthOutcome.AUTHENTICATED.equals(outcome)) {
if (new AuthenticatedActionsHandler(deployment, httpFacade).handledRequest()) {
httpFacade.authenticationInProgress();
} else {
httpFacade.authenticationComplete();
}
return;
}
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
httpFacade.noAuthenticationInProgress(challenge);
return;
}
if (AuthOutcome.FAILED.equals(outcome)) {
httpFacade.getResponse().setStatus(403);
httpFacade.authenticationFailed();
return;
}
httpFacade.noAuthenticationInProgress();
}
private ElytronRequestAuthenticator createRequestAuthenticator(HttpServerRequest request, ElytronHttpFacade httpFacade, KeycloakDeployment deployment) {
return new ElytronRequestAuthenticator(this.callbackHandler, httpFacade, deployment, getConfidentialPort(request));
}
private AdapterDeploymentContext getDeploymentContext(HttpServerRequest request) {
if (this.deploymentContext == null) {
return (AdapterDeploymentContext) request.getScope(Scope.APPLICATION).getAttachment(AdapterDeploymentContext.class.getName());
}
return this.deploymentContext;
}
private boolean preActions(ElytronHttpFacade httpFacade, AdapterDeploymentContext deploymentContext) {
NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
nodesRegistrationManagement.tryRegister(httpFacade.getDeployment());
PreAuthActionsHandler preActions = new PreAuthActionsHandler(new UserSessionManagement() {
@Override
public void logoutAll() {
Collection<String> sessions = httpFacade.getScopeIds(Scope.SESSION);
logoutHttpSessions(new ArrayList<>(sessions));
}
@Override
public void logoutHttpSessions(List<String> ids) {
for (String id : ids) {
HttpScope session = httpFacade.getScope(Scope.SESSION, id);
if (session != null) {
session.invalidate();
}
}
}
}, deploymentContext, httpFacade);
return preActions.handleRequest();
}
// TODO: obtain confidential port from Elytron
private int getConfidentialPort(HttpServerRequest request) {
return 8443;
}
}