/* * 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.wildfly.security.http.util.sso; import org.wildfly.security.auth.callback.CachedIdentityAuthorizeCallback; import org.wildfly.security.auth.server.SecurityIdentity; import org.wildfly.security.cache.CachedIdentity; import org.wildfly.security.cache.IdentityCache; import org.wildfly.security.http.HttpAuthenticationException; import org.wildfly.security.http.HttpServerAuthenticationMechanism; import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory; import org.wildfly.security.http.HttpServerCookie; import org.wildfly.security.http.HttpServerMechanismsResponder; import org.wildfly.security.http.HttpServerRequest; import org.wildfly.security.http.HttpServerRequestWrapper; import org.wildfly.security.http.HttpServerResponse; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import java.security.Principal; import java.util.Map; import static org.wildfly.security._private.ElytronMessages.log; /** * <p>A {@link HttpServerAuthenticationMechanismFactory} which enables single sign-on to the mechanisms provided by a another * http mechanism factory. * * <p>The single sign-one capabilities provided by this factory is based on a HTTP Cookie to track SSO sessions and also an {@link IdentityCache} providing * a storage (eg.: using a shared or distributable cache/map) for these sessions and related data. * * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author Paul Ferraro */ public class SingleSignOnServerMechanismFactory implements HttpServerAuthenticationMechanismFactory { private final HttpServerAuthenticationMechanismFactory delegate; private final SingleSignOnConfiguration configuration; private final SingleSignOnSessionFactory singleSignOnSessionFactory; /** * Creates a new instance. * * @param delegate the factory holding the target mechanisms * @param singleSignOnSessionFactory a custom {@link SingleSignOnManager} * @param configuration the configuration related with the cookie representing user's session */ public SingleSignOnServerMechanismFactory(HttpServerAuthenticationMechanismFactory delegate, SingleSignOnSessionFactory singleSignOnSessionFactory, SingleSignOnConfiguration configuration) { this.delegate = delegate; this.configuration = configuration; this.singleSignOnSessionFactory = singleSignOnSessionFactory; } @Override public String[] getMechanismNames(Map<String, ?> properties) { return delegate.getMechanismNames(properties); } @Override public HttpServerAuthenticationMechanism createAuthenticationMechanism(String mechanismName, Map<String, ?> properties, CallbackHandler callbackHandler) throws HttpAuthenticationException { return new HttpServerAuthenticationMechanism() { @Override public String getMechanismName() { return mechanismName; } @Override public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException { @SuppressWarnings("resource") SingleSignOnSession singleSignOnSession = getSingleSignOnSession(request); if (singleSignOnSession.logout()) { singleSignOnSession.close(); return; } HttpServerAuthenticationMechanism mechanism = getTargetMechanism(mechanismName, singleSignOnSession); if (mechanism == null) { throw log.httpServerAuthenticationMechanismNotFound(mechanismName); } mechanism.evaluateRequest(createHttpServerRequest(request, singleSignOnSession)); } private SingleSignOnSession getSingleSignOnSession(HttpServerRequest request) { HttpServerCookie cookie = getCookie(request); String signOnSessionId = (cookie != null) ? cookie.getValue() : null; SingleSignOnSession singleSignOnSession = (signOnSessionId != null) ? singleSignOnSessionFactory.find(signOnSessionId, request) : null; return (singleSignOnSession == null) ? singleSignOnSessionFactory.create(request, mechanismName) : singleSignOnSession; } private HttpServerAuthenticationMechanism getTargetMechanism(String mechanismName, SingleSignOnSession singleSignOnSession) throws HttpAuthenticationException { return delegate.createAuthenticationMechanism(mechanismName, properties, createCallbackHandler(callbackHandler, mechanismName, singleSignOnSession)); } private HttpServerRequest createHttpServerRequest(final HttpServerRequest request, SingleSignOnSession singleSignOnSession) { HttpServerRequest httpServerRequest = new HttpServerRequestWrapper(request) { @Override public void noAuthenticationInProgress(HttpServerMechanismsResponder responder) { request.noAuthenticationInProgress(response -> { try { clearCookie(request, response, singleSignOnSession); if (responder != null) { responder.sendResponse(response); } } finally { singleSignOnSession.close(); } }); } @Override public void authenticationInProgress(HttpServerMechanismsResponder responder) { request.authenticationInProgress(response -> { try { clearCookie(request, response, singleSignOnSession); if (responder != null) { responder.sendResponse(response); } } finally { singleSignOnSession.close(); } }); } @Override public void authenticationComplete(HttpServerMechanismsResponder responder) { request.authenticationComplete(response -> { try { String id = singleSignOnSession.getId(); if (id != null) { HttpServerCookie cookie = getCookie(request); if (cookie == null) { response.setResponseCookie(createCookie(id, -1)); } } if (responder != null) { responder.sendResponse(response); } } finally { singleSignOnSession.close(); } }); } @Override public void authenticationComplete(HttpServerMechanismsResponder responder, Runnable logoutHandler) { request.authenticationComplete(response -> { try { String id = singleSignOnSession.getId(); if (id != null) { HttpServerCookie cookie = getCookie(request); if (cookie == null) { response.setResponseCookie(createCookie(id, -1)); } } if (responder != null) { responder.sendResponse(response); } } finally { singleSignOnSession.close(); } }, logoutHandler); } @Override public void authenticationFailed(String message, HttpServerMechanismsResponder responder) { request.authenticationFailed(message, response -> { try { clearCookie(request, response, singleSignOnSession); if (responder != null) { responder.sendResponse(response); } } finally { singleSignOnSession.close(); } }); } @Override public void badRequest(HttpAuthenticationException failure, HttpServerMechanismsResponder responder) { try { request.badRequest(failure, responder); } finally { singleSignOnSession.close(); } } }; return httpServerRequest; } private void clearCookie(HttpServerRequest request, HttpServerResponse response, IdentityCache identityCache) { identityCache.remove(); if (getCookie(request) != null) { response.setResponseCookie(createCookie(null, 0)); } } HttpServerCookie getCookie(HttpServerRequest request) { return request.getCookies().stream().filter(current -> configuration.getCookieName().equals(current.getName())).findFirst().orElse(null); } HttpServerCookie createCookie(String value, int maxAge) { return new HttpServerCookie() { @Override public String getName() { return configuration.getCookieName(); } @Override public String getValue() { return value; } @Override public String getDomain() { return configuration.getDomain(); } @Override public int getMaxAge() { return maxAge; } @Override public String getPath() { return configuration.getPath(); } @Override public boolean isSecure() { return configuration.isSecure(); } @Override public int getVersion() { return 0; } @Override public boolean isHttpOnly() { return configuration.isHttpOnly(); } }; } }; } private CallbackHandler createCallbackHandler(CallbackHandler callbackHandler, String mechanismName, SingleSignOnSession singleSignOnSession) { return callbacks -> { CachedIdentity cachedIdentity = singleSignOnSession.get(); if (cachedIdentity == null || mechanismName.equals(cachedIdentity.getMechanismName())) { for (int i = 0; i < callbacks.length; i++) { Callback current = callbacks[i]; if (current instanceof CachedIdentityAuthorizeCallback) { CachedIdentityAuthorizeCallback delegate = (CachedIdentityAuthorizeCallback) current; if (delegate.isLocalCache()) { continue; } Principal principal = delegate.getAuthorizationPrincipal(); if (principal != null) { callbacks[i] = new CachedIdentityAuthorizeCallback(principal, singleSignOnSession) { @Override public void setAuthorized(SecurityIdentity securityIdentity) { super.setAuthorized(securityIdentity); delegate.setAuthorized(securityIdentity); } }; } else { callbacks[i] = new CachedIdentityAuthorizeCallback(singleSignOnSession, delegate.isLocalCache()) { @Override public void setAuthorized(SecurityIdentity securityIdentity) { super.setAuthorized(securityIdentity); delegate.setAuthorized(securityIdentity); } }; } } } } callbackHandler.handle(callbacks); }; } public static final class SingleSignOnConfiguration { private final String cookieName; private final String domain; private final String path; private final boolean httpOnly; private final boolean secure; public SingleSignOnConfiguration(String cookieName, String domain, String path, boolean httpOnly, boolean secure) { this.cookieName = cookieName; this.domain = domain; this.path = path; this.httpOnly = httpOnly; this.secure = secure; } public String getCookieName() { return cookieName; } public String getDomain() { return domain; } public String getPath() { return path; } public boolean isSecure() { return secure; } public boolean isHttpOnly() { return httpOnly; } } }