KEYCLOAK-7207 Check session expiration for SAML session
This commit is contained in:
parent
bf33cb0cf9
commit
4b18c6a117
19 changed files with 440 additions and 44 deletions
|
@ -19,6 +19,7 @@ package org.keycloak.adapters.saml;
|
||||||
|
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
import org.keycloak.adapters.spi.KeycloakAccount;
|
||||||
|
|
||||||
|
import javax.xml.datatype.XMLGregorianCalendar;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -30,14 +31,16 @@ public class SamlSession implements Serializable, KeycloakAccount {
|
||||||
private SamlPrincipal principal;
|
private SamlPrincipal principal;
|
||||||
private Set<String> roles;
|
private Set<String> roles;
|
||||||
private String sessionIndex;
|
private String sessionIndex;
|
||||||
|
private XMLGregorianCalendar sessionNotOnOrAfter;
|
||||||
|
|
||||||
public SamlSession() {
|
public SamlSession() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public SamlSession(SamlPrincipal principal, Set<String> roles, String sessionIndex) {
|
public SamlSession(SamlPrincipal principal, Set<String> roles, String sessionIndex, XMLGregorianCalendar sessionNotOnOrAfter) {
|
||||||
this.principal = principal;
|
this.principal = principal;
|
||||||
this.roles = roles;
|
this.roles = roles;
|
||||||
this.sessionIndex = sessionIndex;
|
this.sessionIndex = sessionIndex;
|
||||||
|
this.sessionNotOnOrAfter = sessionNotOnOrAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SamlPrincipal getPrincipal() {
|
public SamlPrincipal getPrincipal() {
|
||||||
|
@ -52,6 +55,10 @@ public class SamlSession implements Serializable, KeycloakAccount {
|
||||||
return sessionIndex;
|
return sessionIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public XMLGregorianCalendar getSessionNotOnOrAfter() {
|
||||||
|
return sessionNotOnOrAfter;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
if (this == other)
|
if (this == other)
|
||||||
|
|
|
@ -17,13 +17,17 @@
|
||||||
|
|
||||||
package org.keycloak.adapters.saml;
|
package org.keycloak.adapters.saml;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.saml.BaseSAML2BindingBuilder;
|
import org.keycloak.saml.BaseSAML2BindingBuilder;
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||||
|
import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
|
import javax.xml.datatype.DatatypeConstants;
|
||||||
|
import javax.xml.datatype.XMLGregorianCalendar;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,6 +35,9 @@ import java.io.IOException;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class SamlUtil {
|
public class SamlUtil {
|
||||||
|
|
||||||
|
protected static Logger log = Logger.getLogger(SamlUtil.class);
|
||||||
|
|
||||||
public static void sendSaml(boolean asRequest, HttpFacade httpFacade, String actionUrl,
|
public static void sendSaml(boolean asRequest, HttpFacade httpFacade, String actionUrl,
|
||||||
BaseSAML2BindingBuilder binding, Document document,
|
BaseSAML2BindingBuilder binding, Document document,
|
||||||
SamlDeployment.Binding samlBinding) throws ProcessingException, ConfigurationException, IOException {
|
SamlDeployment.Binding samlBinding) throws ProcessingException, ConfigurationException, IOException {
|
||||||
|
@ -87,4 +94,31 @@ public class SamlUtil {
|
||||||
redirectTo = baseUri + "/" + redirectTo;
|
redirectTo = baseUri + "/" + redirectTo;
|
||||||
return redirectTo;
|
return redirectTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SamlSession validateSamlSession(Object potentialSamlSession, SamlDeployment deployment) {
|
||||||
|
if (potentialSamlSession == null) {
|
||||||
|
log.debug("SamlSession was not found in the session");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(potentialSamlSession instanceof SamlSession)) {
|
||||||
|
log.debug("Provided samlSession was not SamlSession type");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
SamlSession samlSession = (SamlSession) potentialSamlSession;
|
||||||
|
|
||||||
|
XMLGregorianCalendar sessionNotOnOrAfter = samlSession.getSessionNotOnOrAfter();
|
||||||
|
if (sessionNotOnOrAfter != null) {
|
||||||
|
XMLGregorianCalendar now = XMLTimeUtil.getIssueInstant();
|
||||||
|
|
||||||
|
XMLTimeUtil.add(sessionNotOnOrAfter, deployment.getIDP().getAllowedClockSkew()); // add clockSkew
|
||||||
|
|
||||||
|
if (now.compare(sessionNotOnOrAfter) != DatatypeConstants.LESSER) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return samlSession;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,7 @@ import java.security.Signature;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.xml.datatype.XMLGregorianCalendar;
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
import org.keycloak.dom.saml.v2.SAML2Object;
|
import org.keycloak.dom.saml.v2.SAML2Object;
|
||||||
|
@ -488,9 +489,9 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
||||||
URI nameFormat = subjectNameID == null ? null : subjectNameID.getFormat();
|
URI nameFormat = subjectNameID == null ? null : subjectNameID.getFormat();
|
||||||
String nameFormatString = nameFormat == null ? JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString();
|
String nameFormatString = nameFormat == null ? JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString();
|
||||||
final SamlPrincipal principal = new SamlPrincipal(assertion, principalName, principalName, nameFormatString, attributes, friendlyAttributes);
|
final SamlPrincipal principal = new SamlPrincipal(assertion, principalName, principalName, nameFormatString, attributes, friendlyAttributes);
|
||||||
String index = authn == null ? null : authn.getSessionIndex();
|
final String sessionIndex = authn == null ? null : authn.getSessionIndex();
|
||||||
final String sessionIndex = index;
|
final XMLGregorianCalendar sessionNotOnOrAfter = authn == null ? null : authn.getSessionNotOnOrAfter();
|
||||||
SamlSession account = new SamlSession(principal, roles, sessionIndex);
|
SamlSession account = new SamlSession(principal, roles, sessionIndex, sessionNotOnOrAfter);
|
||||||
sessionStore.saveAccount(account);
|
sessionStore.saveAccount(account);
|
||||||
onCreateSession.onSessionCreated(account);
|
onCreateSession.onSessionCreated(account);
|
||||||
|
|
||||||
|
|
|
@ -132,14 +132,12 @@ public class JettySamlSessionStore implements SamlSessionStore {
|
||||||
@Override
|
@Override
|
||||||
public boolean isLoggedIn() {
|
public boolean isLoggedIn() {
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
if (session == null) return false;
|
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
log.debug("session was null, returning null");
|
log.debug("session was null, returning false");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
|
SamlSession samlSession = SamlUtil.validateSamlSession(session.getAttribute(SamlSession.class.getName()), deployment);
|
||||||
if (samlSession == null) {
|
if (samlSession == null) {
|
||||||
log.debug("SamlSession was not in session, returning null");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.adapters.saml.servlet;
|
package org.keycloak.adapters.saml.servlet;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.adapters.saml.SamlDeployment;
|
||||||
import org.keycloak.adapters.saml.SamlSession;
|
import org.keycloak.adapters.saml.SamlSession;
|
||||||
import org.keycloak.adapters.saml.SamlSessionStore;
|
import org.keycloak.adapters.saml.SamlSessionStore;
|
||||||
import org.keycloak.adapters.saml.SamlUtil;
|
import org.keycloak.adapters.saml.SamlUtil;
|
||||||
|
@ -41,10 +42,12 @@ import java.util.Set;
|
||||||
public class FilterSamlSessionStore extends FilterSessionStore implements SamlSessionStore {
|
public class FilterSamlSessionStore extends FilterSessionStore implements SamlSessionStore {
|
||||||
protected static Logger log = Logger.getLogger(SamlSessionStore.class);
|
protected static Logger log = Logger.getLogger(SamlSessionStore.class);
|
||||||
protected final SessionIdMapper idMapper;
|
protected final SessionIdMapper idMapper;
|
||||||
|
private final SamlDeployment deployment;
|
||||||
|
|
||||||
public FilterSamlSessionStore(HttpServletRequest request, HttpFacade facade, int maxBuffer, SessionIdMapper idMapper) {
|
public FilterSamlSessionStore(HttpServletRequest request, HttpFacade facade, int maxBuffer, SessionIdMapper idMapper, SamlDeployment deployment) {
|
||||||
super(request, facade, maxBuffer);
|
super(request, facade, maxBuffer);
|
||||||
this.idMapper = idMapper;
|
this.idMapper = idMapper;
|
||||||
|
this.deployment = deployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -118,12 +121,11 @@ public class FilterSamlSessionStore extends FilterSessionStore implements SamlSe
|
||||||
@Override
|
@Override
|
||||||
public boolean isLoggedIn() {
|
public boolean isLoggedIn() {
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
if (session == null) return false;
|
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
log.debug("session was null, returning null");
|
log.debug("session was null, returning false");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
|
final SamlSession samlSession = SamlUtil.validateSamlSession(session.getAttribute(SamlSession.class.getName()), deployment);
|
||||||
if (samlSession == null) {
|
if (samlSession == null) {
|
||||||
log.debug("SamlSession was not in session, returning null");
|
log.debug("SamlSession was not in session, returning null");
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -135,7 +135,7 @@ public class SamlFilter implements Filter {
|
||||||
log.fine("deployment not configured");
|
log.fine("deployment not configured");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
FilterSamlSessionStore tokenStore = new FilterSamlSessionStore(request, facade, 100000, idMapper);
|
FilterSamlSessionStore tokenStore = new FilterSamlSessionStore(request, facade, 100000, idMapper, deployment);
|
||||||
boolean isEndpoint = request.getRequestURI().substring(request.getContextPath().length()).endsWith("/saml");
|
boolean isEndpoint = request.getRequestURI().substring(request.getContextPath().length()).endsWith("/saml");
|
||||||
SamlAuthenticator authenticator;
|
SamlAuthenticator authenticator;
|
||||||
if (isEndpoint) {
|
if (isEndpoint) {
|
||||||
|
|
|
@ -152,9 +152,8 @@ public class CatalinaSamlSessionStore implements SamlSessionStore {
|
||||||
log.debug("session was null, returning null");
|
log.debug("session was null, returning null");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final SamlSession samlSession = (SamlSession)session.getSession().getAttribute(SamlSession.class.getName());
|
final SamlSession samlSession = SamlUtil.validateSamlSession(session.getSession().getAttribute(SamlSession.class.getName()), deployment);
|
||||||
if (samlSession == null) {
|
if (samlSession == null) {
|
||||||
log.debug("SamlSession was not in session, returning null");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,10 +36,13 @@ import org.keycloak.adapters.undertow.SavedRequest;
|
||||||
import org.keycloak.adapters.undertow.ServletHttpFacade;
|
import org.keycloak.adapters.undertow.ServletHttpFacade;
|
||||||
import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
|
import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
import javax.xml.datatype.DatatypeConstants;
|
||||||
|
import javax.xml.datatype.XMLGregorianCalendar;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -162,9 +165,8 @@ public class ServletSamlSessionStore implements SamlSessionStore {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
|
final SamlSession samlSession = SamlUtil.validateSamlSession(session.getAttribute(SamlSession.class.getName()), deployment);
|
||||||
if (samlSession == null) {
|
if (samlSession == null) {
|
||||||
log.debug("SamlSession was not found in the session");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +194,6 @@ public class ServletSamlSessionStore implements SamlSessionStore {
|
||||||
sessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
|
sessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
|
||||||
String sessionId = changeSessionId(session);
|
String sessionId = changeSessionId(session);
|
||||||
idMapperUpdater.map(idMapper, account.getSessionIndex(), account.getPrincipal().getSamlSubject(), sessionId);
|
idMapperUpdater.map(idMapper, account.getSessionIndex(), account.getPrincipal().getSamlSubject(), sessionId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String changeSessionId(HttpSession session) {
|
protected String changeSessionId(HttpSession session) {
|
||||||
|
|
|
@ -31,9 +31,13 @@ import org.keycloak.adapters.saml.SamlUtil;
|
||||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||||
import org.keycloak.adapters.spi.SessionIdMapperUpdater;
|
import org.keycloak.adapters.spi.SessionIdMapperUpdater;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
|
||||||
import org.wildfly.security.http.HttpScope;
|
import org.wildfly.security.http.HttpScope;
|
||||||
import org.wildfly.security.http.Scope;
|
import org.wildfly.security.http.Scope;
|
||||||
|
|
||||||
|
import javax.xml.datatype.DatatypeConstants;
|
||||||
|
import javax.xml.datatype.XMLGregorianCalendar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
|
@ -150,9 +154,8 @@ public class ElytronSamlSessionStore implements SamlSessionStore, ElytronTokeSto
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final SamlSession samlSession = (SamlSession)session.getAttachment(SamlSession.class.getName());
|
final SamlSession samlSession = SamlUtil.validateSamlSession(session.getAttachment(SamlSession.class.getName()), deployment);
|
||||||
if (samlSession == null) {
|
if (samlSession == null) {
|
||||||
log.debug("SamlSession was not in session, returning null");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,13 @@ package org.keycloak.testsuite.adapter.servlet;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.keycloak.adapters.saml.SamlAuthenticationError;
|
import org.keycloak.adapters.saml.SamlAuthenticationError;
|
||||||
import org.keycloak.adapters.saml.SamlPrincipal;
|
import org.keycloak.adapters.saml.SamlPrincipal;
|
||||||
|
import org.keycloak.adapters.saml.SamlSession;
|
||||||
import org.keycloak.adapters.spi.AuthenticationError;
|
import org.keycloak.adapters.spi.AuthenticationError;
|
||||||
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
||||||
|
|
||||||
import javax.servlet.RequestDispatcher;
|
import javax.servlet.RequestDispatcher;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
|
@ -35,6 +37,7 @@ import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.xml.datatype.XMLGregorianCalendar;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -149,7 +152,6 @@ public class SendUsernameServlet {
|
||||||
return "These roles will be checked: " + checkRolesList.toString();
|
return "These roles will be checked: " + checkRolesList.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean checkRoles() {
|
private boolean checkRoles() {
|
||||||
for (String role : checkRolesList) {
|
for (String role : checkRolesList) {
|
||||||
System.out.println("In checkRoles() checking role " + role + " for user " + httpServletRequest.getUserPrincipal().getName());
|
System.out.println("In checkRoles() checking role " + role + " for user " + httpServletRequest.getUserPrincipal().getName());
|
||||||
|
@ -175,7 +177,29 @@ public class SendUsernameServlet {
|
||||||
|
|
||||||
sentPrincipal = principal;
|
sentPrincipal = principal;
|
||||||
|
|
||||||
return output + principal.getName();
|
output += principal.getName() + "\n";
|
||||||
|
output += getSessionInfo() + "\n";
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSessionInfo() {
|
||||||
|
HttpSession session = httpServletRequest.getSession(false);
|
||||||
|
|
||||||
|
if (session != null) {
|
||||||
|
final SamlSession samlSession = (SamlSession) httpServletRequest.getSession(false).getAttribute(SamlSession.class.getName());
|
||||||
|
|
||||||
|
if (samlSession != null) {
|
||||||
|
String output = "Session ID: " + samlSession.getSessionIndex() + "\n";
|
||||||
|
XMLGregorianCalendar sessionNotOnOrAfter = samlSession.getSessionNotOnOrAfter();
|
||||||
|
output += "SessionNotOnOrAfter: " + (sessionNotOnOrAfter == null ? "null" : sessionNotOnOrAfter.toString());
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "SamlSession doesn't exists";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Session doesn't exists";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getErrorOutput(Integer statusCode) {
|
private String getErrorOutput(Integer statusCode) {
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.keycloak.testsuite.util;
|
package org.keycloak.testsuite.util;
|
||||||
|
|
||||||
import org.keycloak.dom.saml.v2.SAML2Object;
|
import org.keycloak.dom.saml.v2.SAML2Object;
|
||||||
|
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||||
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
|
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
|
||||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||||
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
|
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
|
||||||
|
@ -142,6 +143,13 @@ public class Matchers {
|
||||||
new SamlLogoutRequestTypeMatcher(URI.create(destination))
|
new SamlLogoutRequestTypeMatcher(URI.create(destination))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Matches when the type of a SAML object is instance of {@link AuthnRequestType}.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static <T> Matcher<SAML2Object> isSamlAuthnRequest() {
|
||||||
|
return instanceOf(AuthnRequestType.class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches when the SAML status of a {@link StatusResponseType} instance is equal to the given code.
|
* Matches when the SAML status of a {@link StatusResponseType} instance is equal to the given code.
|
||||||
|
|
|
@ -122,6 +122,14 @@ public class SamlClientBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SamlClientBuilder assertResponse(Consumer<HttpResponse> consumer) {
|
||||||
|
steps.add((client, currentURI, currentResponse, context) -> {
|
||||||
|
consumer.accept(currentResponse);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When executing the {@link HttpUriRequest} obtained from the previous step,
|
* When executing the {@link HttpUriRequest} obtained from the previous step,
|
||||||
* do not to follow HTTP redirects but pass the first response immediately
|
* do not to follow HTTP redirects but pass the first response immediately
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.jboss.shrinkwrap.api.asset.UrlAsset;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
|
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
|
||||||
|
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
||||||
|
|
||||||
public abstract class AbstractServletsAdapterTest extends AbstractAdapterTest {
|
public abstract class AbstractServletsAdapterTest extends AbstractAdapterTest {
|
||||||
|
|
||||||
|
@ -132,7 +133,7 @@ public abstract class AbstractServletsAdapterTest extends AbstractAdapterTest {
|
||||||
} else {
|
} else {
|
||||||
deployment.addAsWebInfResource(keycloakSAMLConfig, "keycloak-saml.xml");
|
deployment.addAsWebInfResource(keycloakSAMLConfig, "keycloak-saml.xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -211,7 +212,7 @@ public abstract class AbstractServletsAdapterTest extends AbstractAdapterTest {
|
||||||
.build().toString();
|
.build().toString();
|
||||||
|
|
||||||
DroneUtils.getCurrentDriver().navigate().to(timeOffsetUri);
|
DroneUtils.getCurrentDriver().navigate().to(timeOffsetUri);
|
||||||
WaitUtils.waitUntilElement(By.tagName("body")).is().visible();
|
waitForPageToLoad();
|
||||||
String pageSource = DroneUtils.getCurrentDriver().getPageSource();
|
String pageSource = DroneUtils.getCurrentDriver().getPageSource();
|
||||||
System.out.println(pageSource);
|
System.out.println(pageSource);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package org.keycloak.testsuite.adapter.servlet;
|
||||||
|
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
|
||||||
|
import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
|
||||||
|
import org.keycloak.testsuite.utils.io.IOUtil;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static org.keycloak.testsuite.auth.page.AuthRealm.SAMLSERVLETDEMO;
|
||||||
|
|
||||||
|
public abstract class AbstractSAMLServletAdapterTest extends AbstractServletsAdapterTest {
|
||||||
|
|
||||||
|
public static final String WEB_XML_WITH_ACTION_FILTER = "web-with-action-filter.xml";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDefaultPageUriParameters() {
|
||||||
|
super.setDefaultPageUriParameters();
|
||||||
|
testRealmPage.setAuthRealm(SAMLSERVLETDEMO);
|
||||||
|
testRealmSAMLRedirectLoginPage.setAuthRealm(SAMLSERVLETDEMO);
|
||||||
|
testRealmSAMLPostLoginPage.setAuthRealm(SAMLSERVLETDEMO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
|
||||||
|
testRealms.add(IOUtil.loadRealm("/adapter-test/keycloak-saml/testsaml.json"));
|
||||||
|
testRealms.add(IOUtil.loadRealm("/adapter-test/keycloak-saml/tenant1-realm.json"));
|
||||||
|
testRealms.add(IOUtil.loadRealm("/adapter-test/keycloak-saml/tenant2-realm.json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setAdapterAndServerTimeOffset(int timeOffset, String... servletUris) {
|
||||||
|
setTimeOffset(timeOffset);
|
||||||
|
|
||||||
|
Arrays.stream(servletUris)
|
||||||
|
.map(url -> url += "unsecured")
|
||||||
|
.forEach(servletUri -> {
|
||||||
|
String url = UriBuilder.fromUri(servletUri)
|
||||||
|
.queryParam(AdapterActionsFilter.TIME_OFFSET_PARAM, timeOffset)
|
||||||
|
.build().toString();
|
||||||
|
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
|
||||||
|
HttpUriRequest request = new HttpGet(url);
|
||||||
|
CloseableHttpResponse httpResponse = client.execute(request);
|
||||||
|
|
||||||
|
System.out.println(EntityUtils.toString(httpResponse.getEntity()));
|
||||||
|
httpResponse.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot change time on url " + url, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ import static org.keycloak.testsuite.util.SamlClient.Binding.POST;
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_JETTY92)
|
@AppServerContainer(ContainerConstants.APP_SERVER_JETTY92)
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_JETTY93)
|
@AppServerContainer(ContainerConstants.APP_SERVER_JETTY93)
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_JETTY94)
|
@AppServerContainer(ContainerConstants.APP_SERVER_JETTY94)
|
||||||
public class SAMLClockSkewAdapterTest extends AbstractServletsAdapterTest {
|
public class SAMLClockSkewAdapterTest extends AbstractSAMLServletAdapterTest {
|
||||||
|
|
||||||
@Page protected SalesPostClockSkewServlet salesPostClockSkewServletPage;
|
@Page protected SalesPostClockSkewServlet salesPostClockSkewServletPage;
|
||||||
private static final String DEPLOYMENT_NAME_3_SEC = SalesPostClockSkewServlet.DEPLOYMENT_NAME + "_3Sec";
|
private static final String DEPLOYMENT_NAME_3_SEC = SalesPostClockSkewServlet.DEPLOYMENT_NAME + "_3Sec";
|
||||||
|
@ -88,13 +88,13 @@ public class SAMLClockSkewAdapterTest extends AbstractServletsAdapterTest {
|
||||||
.login().user(bburkeUser).build()
|
.login().user(bburkeUser).build()
|
||||||
.processSamlResponse(POST)
|
.processSamlResponse(POST)
|
||||||
.transformDocument(doc -> {
|
.transformDocument(doc -> {
|
||||||
setAdapterAndServerTimeOffset(timeOffset, salesPostClockSkewServletPage.toString() + "unsecured");
|
setAdapterAndServerTimeOffset(timeOffset, salesPostClockSkewServletPage.toString());
|
||||||
return doc;
|
return doc;
|
||||||
}).build().executeAndTransform(resp -> EntityUtils.toString(resp.getEntity()));
|
}).build().executeAndTransform(resp -> EntityUtils.toString(resp.getEntity()));
|
||||||
|
|
||||||
Assert.assertThat(resultPage, matcher);
|
Assert.assertThat(resultPage, matcher);
|
||||||
} finally {
|
} finally {
|
||||||
setAdapterAndServerTimeOffset(0);
|
setAdapterAndServerTimeOffset(0, salesPostClockSkewServletPage.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.keycloak.testsuite.adapter.servlet;
|
||||||
|
|
||||||
|
import org.junit.Assume;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
|
import org.keycloak.testsuite.utils.annotation.UseServletFilter;
|
||||||
|
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author mhajas
|
||||||
|
*/
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY_DEPRECATED)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
|
||||||
|
@UseServletFilter(filterName = "saml-filter", filterClass = "org.keycloak.adapters.saml.servlet.SamlFilter",
|
||||||
|
filterDependency = "org.keycloak:keycloak-saml-servlet-filter-adapter")
|
||||||
|
public class SAMLFilterServletSessionTimeoutTest extends SAMLServletSessionTimeoutTest {
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void enabled() {
|
||||||
|
String appServerJavaHome = System.getProperty("app.server.java.home", "");
|
||||||
|
Assume.assumeFalse(appServerJavaHome.contains("1.7") || appServerJavaHome.contains("ibm-java-70"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.testsuite.adapter.servlet;
|
package org.keycloak.testsuite.adapter.servlet;
|
||||||
|
|
||||||
import static javax.ws.rs.core.Response.Status.OK;
|
import static javax.ws.rs.core.Response.Status.OK;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.keycloak.OAuth2Constants.PASSWORD;
|
import static org.keycloak.OAuth2Constants.PASSWORD;
|
||||||
import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
|
import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
|
||||||
|
@ -50,6 +51,8 @@ import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.ws.rs.client.Client;
|
import javax.ws.rs.client.Client;
|
||||||
|
@ -175,7 +178,7 @@ import org.xml.sax.SAXException;
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT7)
|
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT7)
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT8)
|
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT8)
|
||||||
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT9)
|
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT9)
|
||||||
public class SAMLServletAdapterTest extends AbstractServletsAdapterTest {
|
public class SAMLServletAdapterTest extends AbstractSAMLServletAdapterTest {
|
||||||
@Page
|
@Page
|
||||||
protected BadClientSalesPostSigServlet badClientSalesPostSigServletPage;
|
protected BadClientSalesPostSigServlet badClientSalesPostSigServletPage;
|
||||||
|
|
||||||
|
@ -432,21 +435,6 @@ public class SAMLServletAdapterTest extends AbstractServletsAdapterTest {
|
||||||
SendUsernameServlet.class, SamlMultiTenantResolver.class);
|
SendUsernameServlet.class, SamlMultiTenantResolver.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
|
|
||||||
testRealms.add(IOUtil.loadRealm("/adapter-test/keycloak-saml/testsaml.json"));
|
|
||||||
testRealms.add(IOUtil.loadRealm("/adapter-test/keycloak-saml/tenant1-realm.json"));
|
|
||||||
testRealms.add(IOUtil.loadRealm("/adapter-test/keycloak-saml/tenant2-realm.json"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setDefaultPageUriParameters() {
|
|
||||||
super.setDefaultPageUriParameters();
|
|
||||||
testRealmPage.setAuthRealm(SAMLSERVLETDEMO);
|
|
||||||
testRealmSAMLRedirectLoginPage.setAuthRealm(SAMLSERVLETDEMO);
|
|
||||||
testRealmSAMLPostLoginPage.setAuthRealm(SAMLSERVLETDEMO);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertForbidden(AbstractPage page, String expectedNotContains) {
|
private void assertForbidden(AbstractPage page, String expectedNotContains) {
|
||||||
page.navigateTo();
|
page.navigateTo();
|
||||||
waitUntilElement(By.xpath("//body")).text().not().contains(expectedNotContains);
|
waitUntilElement(By.xpath("//body")).text().not().contains(expectedNotContains);
|
||||||
|
@ -1583,7 +1571,7 @@ public class SAMLServletAdapterTest extends AbstractServletsAdapterTest {
|
||||||
|
|
||||||
.execute(r -> {
|
.execute(r -> {
|
||||||
Assert.assertThat(r, statusCodeIsHC(Response.Status.OK));
|
Assert.assertThat(r, statusCodeIsHC(Response.Status.OK));
|
||||||
Assert.assertThat(r, bodyHC(allOf(containsString("principal="), not(containsString("500")))));
|
Assert.assertThat(r, bodyHC(containsString("principal=")));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
package org.keycloak.testsuite.adapter.servlet;
|
||||||
|
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.jboss.arquillian.container.test.api.Deployment;
|
||||||
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
||||||
|
import org.keycloak.dom.saml.v2.SAML2Object;
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
|
||||||
|
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||||
|
import org.keycloak.models.utils.SessionTimeoutHelper;
|
||||||
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
|
import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
|
||||||
|
import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
|
||||||
|
import org.keycloak.testsuite.adapter.page.Employee2Servlet;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
|
import org.keycloak.testsuite.util.Matchers;
|
||||||
|
import org.keycloak.testsuite.util.SamlClient;
|
||||||
|
import org.keycloak.testsuite.util.SamlClientBuilder;
|
||||||
|
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
|
||||||
|
|
||||||
|
import javax.xml.datatype.XMLGregorianCalendar;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.allOf;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.keycloak.testsuite.util.Matchers.bodyHC;
|
||||||
|
|
||||||
|
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY_DEPRECATED)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT7)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT8)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT9)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_JETTY92)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_JETTY93)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_JETTY94)
|
||||||
|
public class SAMLServletSessionTimeoutTest extends AbstractSAMLServletAdapterTest {
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected Employee2Servlet employee2ServletPage;
|
||||||
|
|
||||||
|
@Deployment(name = Employee2Servlet.DEPLOYMENT_NAME)
|
||||||
|
protected static WebArchive employee2() {
|
||||||
|
return samlServletDeployment(Employee2Servlet.DEPLOYMENT_NAME, WEB_XML_WITH_ACTION_FILTER, SendUsernameServlet.class, AdapterActionsFilter.class, PublicKeyLocator.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int SESSION_LENGTH_IN_SECONDS = 120;
|
||||||
|
private static final int KEYCLOAK_SESSION_TIMEOUT = 1922; /** 1800 session max + 120 {@link SessionTimeoutHelper#IDLE_TIMEOUT_WINDOW_SECONDS} */
|
||||||
|
|
||||||
|
private AtomicReference<String> sessionNotOnOrAfter = new AtomicReference<>();
|
||||||
|
|
||||||
|
private SAML2Object addSessionNotOnOrAfter(SAML2Object ob) {
|
||||||
|
assertThat(ob, Matchers.isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
|
||||||
|
ResponseType resp = (ResponseType) ob;
|
||||||
|
|
||||||
|
Set<StatementAbstractType> statements = resp.getAssertions().get(0).getAssertion().getStatements();
|
||||||
|
|
||||||
|
AuthnStatementType authType = (AuthnStatementType) statements.stream()
|
||||||
|
.filter(statement -> statement instanceof AuthnStatementType)
|
||||||
|
.findFirst().orElse(new AuthnStatementType(XMLTimeUtil.getIssueInstant()));
|
||||||
|
XMLGregorianCalendar sessionTimeout = XMLTimeUtil.add(XMLTimeUtil.getIssueInstant(), SESSION_LENGTH_IN_SECONDS * 1000);
|
||||||
|
sessionNotOnOrAfter.set(sessionTimeout.toString());
|
||||||
|
authType.setSessionNotOnOrAfter(sessionTimeout);
|
||||||
|
resp.getAssertions().get(0).getAssertion().addStatement(authType);
|
||||||
|
|
||||||
|
return ob;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SamlClientBuilder beginAuthenticationAndLogin() {
|
||||||
|
return new SamlClientBuilder()
|
||||||
|
.navigateTo(employee2ServletPage.buildUri())
|
||||||
|
.processSamlResponse(SamlClient.Binding.POST) // Process AuthnResponse
|
||||||
|
.build()
|
||||||
|
|
||||||
|
.login().user(bburkeUser)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void employee2TestSAMLRefreshingSession() {
|
||||||
|
sessionNotOnOrAfter.set(null);
|
||||||
|
|
||||||
|
beginAuthenticationAndLogin()
|
||||||
|
.processSamlResponse(SamlClient.Binding.POST) // Update response with SessionNotOnOrAfter
|
||||||
|
.transformObject(this::addSessionNotOnOrAfter)
|
||||||
|
.build()
|
||||||
|
.addStep(() -> setAdapterAndServerTimeOffset(100, employee2ServletPage.toString())) // Move in time right before sessionNotOnOrAfter
|
||||||
|
.navigateTo(employee2ServletPage.buildUri())
|
||||||
|
.assertResponse(response -> // Check that session is still valid within sessionTimeout limit
|
||||||
|
assertThat(response, // Cannot use matcher as sessionNotOnOrAfter variable is not set in time of creating matcher
|
||||||
|
bodyHC(allOf(containsString("principal=bburke"),
|
||||||
|
containsString("SessionNotOnOrAfter: " + sessionNotOnOrAfter.get())))))
|
||||||
|
.addStep(() -> setAdapterAndServerTimeOffset(SESSION_LENGTH_IN_SECONDS, employee2ServletPage.toString())) // Move in time after sessionNotOnOrAfter
|
||||||
|
.navigateTo(employee2ServletPage.buildUri())
|
||||||
|
.processSamlResponse(SamlClient.Binding.POST) // AuthnRequest should be send
|
||||||
|
.transformObject(ob -> {
|
||||||
|
assertThat(ob, Matchers.isSamlAuthnRequest());
|
||||||
|
return ob;
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
|
||||||
|
.followOneRedirect() // There is a redirect on Keycloak side
|
||||||
|
.processSamlResponse(SamlClient.Binding.POST) // Process the response from keyclok, no login form should be present since session on keycloak side is still valid
|
||||||
|
.build()
|
||||||
|
.assertResponse(bodyHC(containsString("principal=bburke")))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
setAdapterAndServerTimeOffset(0, employee2ServletPage.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void employee2TestSAMLSessionTimeoutOnBothSides() {
|
||||||
|
sessionNotOnOrAfter.set(null);
|
||||||
|
|
||||||
|
beginAuthenticationAndLogin()
|
||||||
|
.processSamlResponse(SamlClient.Binding.POST) // Update response with SessionNotOnOrAfter
|
||||||
|
.transformObject(this::addSessionNotOnOrAfter)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
.navigateTo(employee2ServletPage.buildUri())
|
||||||
|
.assertResponse(response -> // Check that session is still valid within sessionTimeout limit
|
||||||
|
assertThat(response, // Cannot use matcher as sessionNotOnOrAfter variable is not set in time of creating matcher
|
||||||
|
bodyHC(allOf(containsString("principal=bburke"),
|
||||||
|
containsString("SessionNotOnOrAfter: " + sessionNotOnOrAfter.get())))))
|
||||||
|
.addStep(() -> setAdapterAndServerTimeOffset(KEYCLOAK_SESSION_TIMEOUT, employee2ServletPage.toString())) // Move in time after sessionNotOnOrAfter and keycloak session
|
||||||
|
.navigateTo(employee2ServletPage.buildUri())
|
||||||
|
.processSamlResponse(SamlClient.Binding.POST) // AuthnRequest should be send
|
||||||
|
.transformObject(ob -> {
|
||||||
|
assertThat(ob, Matchers.isSamlAuthnRequest());
|
||||||
|
return ob;
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
|
||||||
|
.followOneRedirect() // There is a redirect on Keycloak side
|
||||||
|
.assertResponse(Matchers.bodyHC(containsString("form id=\"kc-form-login\"")))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
setAdapterAndServerTimeOffset(0, employee2ServletPage.toString());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
~ and other 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||||
|
version="3.0">
|
||||||
|
|
||||||
|
<module-name>%CONTEXT_PATH%</module-name>
|
||||||
|
|
||||||
|
<servlet>
|
||||||
|
<servlet-name>javax.ws.rs.core.Application</servlet-name>
|
||||||
|
<load-on-startup>1</load-on-startup>
|
||||||
|
</servlet>
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>javax.ws.rs.core.Application</servlet-name>
|
||||||
|
<url-pattern>/*</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<filter-name>AdapterActionsFilter</filter-name>
|
||||||
|
<filter-class>org.keycloak.testsuite.adapter.filter.AdapterActionsFilter</filter-class>
|
||||||
|
</filter>
|
||||||
|
<filter-mapping>
|
||||||
|
<filter-name>AdapterActionsFilter</filter-name>
|
||||||
|
<url-pattern>/*</url-pattern>
|
||||||
|
</filter-mapping>
|
||||||
|
|
||||||
|
<error-page>
|
||||||
|
<location>/error.html</location>
|
||||||
|
</error-page>
|
||||||
|
|
||||||
|
<security-constraint>
|
||||||
|
<web-resource-collection>
|
||||||
|
<web-resource-name>Application</web-resource-name>
|
||||||
|
<url-pattern>/*</url-pattern>
|
||||||
|
</web-resource-collection>
|
||||||
|
<auth-constraint>
|
||||||
|
<role-name>manager</role-name>
|
||||||
|
</auth-constraint>
|
||||||
|
</security-constraint>
|
||||||
|
<security-constraint>
|
||||||
|
<web-resource-collection>
|
||||||
|
<web-resource-name>Unsecured-setCheckRoles</web-resource-name>
|
||||||
|
<url-pattern>/setCheckRoles/*</url-pattern>
|
||||||
|
</web-resource-collection>
|
||||||
|
</security-constraint>
|
||||||
|
<security-constraint>
|
||||||
|
<web-resource-collection>
|
||||||
|
<web-resource-name>Unsecured-uncheckRoles</web-resource-name>
|
||||||
|
<url-pattern>/uncheckRoles/*</url-pattern>
|
||||||
|
</web-resource-collection>
|
||||||
|
</security-constraint>
|
||||||
|
<security-constraint>
|
||||||
|
<web-resource-collection>
|
||||||
|
<web-resource-name>Unsecured</web-resource-name>
|
||||||
|
<url-pattern>/unsecured/*</url-pattern>
|
||||||
|
</web-resource-collection>
|
||||||
|
</security-constraint>
|
||||||
|
|
||||||
|
<login-config>
|
||||||
|
<auth-method>KEYCLOAK-SAML</auth-method>
|
||||||
|
<realm-name>demo</realm-name>
|
||||||
|
</login-config>
|
||||||
|
|
||||||
|
<security-role>
|
||||||
|
<role-name>manager</role-name>
|
||||||
|
</security-role>
|
||||||
|
</web-app>
|
Loading…
Reference in a new issue