Merge pull request #921 from patriot1burke/master

logout via redirect - step 1
This commit is contained in:
Bill Burke 2015-01-20 10:57:04 -05:00
commit f454e5ae12
31 changed files with 801 additions and 63 deletions

View file

@ -32,6 +32,9 @@
<constraints nullable="false"/>
</column>
</createTable>
<addColumn tableName="CLIENT">
<column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false"/>
</addColumn>
<addPrimaryKey columnNames="INTERNAL_ID" constraintName="CONSTRAINT_2B" tableName="IDENTITY_PROVIDER"/>
<addPrimaryKey columnNames="IDENTITY_PROVIDER, USER_ID" constraintName="CONSTRAINT_40" tableName="FEDERATED_IDENTITY"/>
<addPrimaryKey columnNames="IDENTITY_PROVIDER_ID, NAME" constraintName="CONSTRAINT_D" tableName="IDENTITY_PROVIDER_CONFIG"/>

View file

@ -23,6 +23,7 @@
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UserSessionEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity</class>

View file

@ -69,6 +69,9 @@ public interface ClientModel {
String getAttribute(String name);
Map<String, String> getAttributes();
boolean isFrontchannelLogout();
void setFrontchannelLogout(boolean flag);
boolean isPublicClient();
void setPublicClient(boolean flag);

View file

@ -49,7 +49,8 @@ public interface ClientSessionModel {
UPDATE_PASSWORD,
RECOVER_PASSWORD,
AUTHENTICATE,
SOCIAL_CALLBACK
SOCIAL_CALLBACK,
LOGGED_OUT
}
}

View file

@ -27,4 +27,18 @@ public interface UserSessionModel {
List<ClientSessionModel> getClientSessions();
public String getNote(String name);
public void setNote(String name, String value);
public void removeNote(String name);
State getState();
void setState(State state);
public static enum State {
LOGGING_IN,
LOGGED_IN,
LOGGING_OUT,
LOGGED_OUT
}
}

View file

@ -18,6 +18,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
private int notBefore;
private boolean publicClient;
private boolean fullScopeAllowed;
private boolean frontchannelLogout;
private String realmId;
private Map<String, String> attributes = new HashMap<String, String>();
@ -130,4 +131,12 @@ public class ClientEntity extends AbstractIdentifiableEntity {
public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}
public boolean isFrontchannelLogout() {
return frontchannelLogout;
}
public void setFrontchannelLogout(boolean frontchannelLogout) {
this.frontchannelLogout = frontchannelLogout;
}
}

View file

@ -125,6 +125,16 @@ public abstract class ClientAdapter implements ClientModel {
updatedClient.setPublicClient(flag);
}
public boolean isFrontchannelLogout() {
if (updatedClient != null) return updatedClient.isPublicClient();
return cachedClient.isFrontchannelLogout();
}
public void setFrontchannelLogout(boolean flag) {
getDelegateForUpdate();
updatedClient.setFrontchannelLogout(flag);
}
@Override
public boolean isFullScopeAllowed() {
if (updatedClient != null) return updatedClient.isFullScopeAllowed();

View file

@ -28,6 +28,7 @@ public class CachedClient {
protected boolean publicClient;
protected boolean fullScopeAllowed;
protected boolean directGrantsOnly;
protected boolean frontchannelLogout;
protected int notBefore;
protected Set<String> scope = new HashSet<String>();
protected Set<String> webOrigins = new HashSet<String>();
@ -42,6 +43,7 @@ public class CachedClient {
attributes.putAll(model.getAttributes());
notBefore = model.getNotBefore();
directGrantsOnly = model.isDirectGrantsOnly();
frontchannelLogout = model.isFrontchannelLogout();
publicClient = model.isPublicClient();
allowedClaimsMask = model.getAllowedClaimsMask();
fullScopeAllowed = model.isFullScopeAllowed();
@ -112,4 +114,12 @@ public class CachedClient {
public Map<String, String> getAttributes() {
return attributes;
}
public boolean isFrontchannelLogout() {
return frontchannelLogout;
}
public void setFrontchannelLogout(boolean frontchannelLogout) {
this.frontchannelLogout = frontchannelLogout;
}
}

View file

@ -80,6 +80,16 @@ public abstract class ClientAdapter implements ClientModel {
entity.setPublicClient(flag);
}
@Override
public boolean isFrontchannelLogout() {
return entity.isFrontchannelLogout();
}
@Override
public void setFrontchannelLogout(boolean flag) {
entity.setFrontchannelLogout(flag);
}
@Override
public boolean isFullScopeAllowed() {
return entity.isFullScopeAllowed();

View file

@ -43,6 +43,8 @@ public abstract class ClientEntity {
private boolean publicClient;
@Column(name="PROTOCOL")
private String protocol;
@Column(name="FRONTCHANNEL_LOGOUT")
private boolean frontchannelLogout;
@Column(name="FULL_SCOPE_ALLOWED")
private boolean fullScopeAllowed;
@ -169,4 +171,12 @@ public abstract class ClientEntity {
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public boolean isFrontchannelLogout() {
return frontchannelLogout;
}
public void setFrontchannelLogout(boolean frontchannelLogout) {
this.frontchannelLogout = frontchannelLogout;
}
}

View file

@ -159,6 +159,18 @@ public abstract class ClientAdapter<T extends MongoIdentifiableEntity> extends A
updateMongoEntity();
}
@Override
public boolean isFrontchannelLogout() {
return getMongoEntityAsClient().isFrontchannelLogout();
}
@Override
public void setFrontchannelLogout(boolean flag) {
getMongoEntityAsClient().setFrontchannelLogout(flag);
updateMongoEntity();
}
@Override
public boolean isFullScopeAllowed() {
return getMongoEntityAsClient().isFullScopeAllowed();

View file

@ -11,6 +11,7 @@ import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@ -77,6 +78,39 @@ public class UserSessionAdapter implements UserSessionModel {
update();
}
@Override
public String getNote(String name) {
return entity.getNotes() != null ? entity.getNotes().get(name) : null;
}
@Override
public void setNote(String name, String value) {
if (entity.getNotes() == null) {
entity.setNotes(new HashMap<String, String>());
}
entity.getNotes().put(name, value);
update();
}
@Override
public void removeNote(String name) {
if (entity.getNotes() != null) {
entity.getNotes().remove(name);
update();
}
}
@Override
public State getState() {
return entity.getState();
}
@Override
public void setState(State state) {
entity.setState(state);
update();
}
@Override
public List<ClientSessionModel> getClientSessions() {
if (entity.getClientSessions() != null) {

View file

@ -1,5 +1,8 @@
package org.keycloak.models.sessions.infinispan.entities;
import org.keycloak.models.UserSessionModel;
import java.util.Map;
import java.util.Set;
/**
@ -23,6 +26,10 @@ public class UserSessionEntity extends SessionEntity {
private Set<String> clientSessions;
private UserSessionModel.State state;
private Map<String, String> notes;
public String getUser() {
return user;
}
@ -86,4 +93,20 @@ public class UserSessionEntity extends SessionEntity {
public void setClientSessions(Set<String> clientSessions) {
this.clientSessions = clientSessions;
}
public Map<String, String> getNotes() {
return notes;
}
public void setNotes(Map<String, String> notes) {
this.notes = notes;
}
public UserSessionModel.State getState() {
return state;
}
public void setState(UserSessionModel.State state) {
this.state = state;
}
}

View file

@ -173,6 +173,10 @@ public class JpaUserSessionProvider implements UserSessionProvider {
.setParameter("realmId", realm.getId())
.setParameter("userId", user.getId())
.executeUpdate();
em.createNamedQuery("removeUserSessionNoteByUser")
.setParameter("realmId", realm.getId())
.setParameter("userId", user.getId())
.executeUpdate();
em.createNamedQuery("removeUserSessionByUser")
.setParameter("realmId", realm.getId())
.setParameter("userId", user.getId())
@ -211,6 +215,11 @@ public class JpaUserSessionProvider implements UserSessionProvider {
.setParameter("maxTime", maxTime)
.setParameter("idleTime", idleTime)
.executeUpdate();
em.createNamedQuery("removeUserSessionNoteByExpired")
.setParameter("realmId", realm.getId())
.setParameter("maxTime", maxTime)
.setParameter("idleTime", idleTime)
.executeUpdate();
em.createNamedQuery("removeUserSessionByExpired")
.setParameter("realmId", realm.getId())
.setParameter("maxTime", maxTime)
@ -223,6 +232,7 @@ public class JpaUserSessionProvider implements UserSessionProvider {
em.createNamedQuery("removeClientSessionNoteByRealm").setParameter("realmId", realm.getId()).executeUpdate();
em.createNamedQuery("removeClientSessionRoleByRealm").setParameter("realmId", realm.getId()).executeUpdate();
em.createNamedQuery("removeClientSessionByRealm").setParameter("realmId", realm.getId()).executeUpdate();
em.createNamedQuery("removeUserSessionNoteByRealm").setParameter("realmId", realm.getId()).executeUpdate();
em.createNamedQuery("removeUserSessionByRealm").setParameter("realmId", realm.getId()).executeUpdate();
}

View file

@ -7,8 +7,10 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
import org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity;
import javax.persistence.EntityManager;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@ -78,6 +80,55 @@ public class UserSessionAdapter implements UserSessionModel {
entity.setLastSessionRefresh(seconds);
}
@Override
public void setNote(String name, String value) {
for (UserSessionNoteEntity attr : entity.getNotes()) {
if (attr.getName().equals(name)) {
attr.setValue(value);
return;
}
}
UserSessionNoteEntity attr = new UserSessionNoteEntity();
attr.setName(name);
attr.setValue(value);
attr.setUserSession(entity);
em.persist(attr);
entity.getNotes().add(attr);
}
@Override
public void removeNote(String name) {
Iterator<UserSessionNoteEntity> it = entity.getNotes().iterator();
while (it.hasNext()) {
UserSessionNoteEntity attr = it.next();
if (attr.getName().equals(name)) {
it.remove();
em.remove(attr);
}
}
}
@Override
public String getNote(String name) {
for (UserSessionNoteEntity attr : entity.getNotes()) {
if (attr.getName().equals(name)) {
return attr.getValue();
}
}
return null;
}
@Override
public State getState() {
return entity.getState();
}
@Override
public void setState(State state) {
entity.setState(state);
}
@Override
public List<ClientSessionModel> getClientSessions() {
List<ClientSessionModel> clientSessions = new LinkedList<ClientSessionModel>();

View file

@ -1,5 +1,7 @@
package org.keycloak.models.sessions.jpa.entities;
import org.keycloak.models.UserSessionModel;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
@ -54,9 +56,15 @@ public class UserSessionEntity {
@Column(name="LAST_SESSION_REFRESH")
protected int lastSessionRefresh;
@Column(name="USER_SESSION_STATE")
protected UserSessionModel.State state;
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="session")
protected Collection<ClientSessionEntity> clientSessions = new ArrayList<ClientSessionEntity>();
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="userSession")
protected Collection<UserSessionNoteEntity> notes = new ArrayList<UserSessionNoteEntity>();
public String getId() {
return id;
}
@ -133,4 +141,19 @@ public class UserSessionEntity {
return clientSessions;
}
public UserSessionModel.State getState() {
return state;
}
public void setState(UserSessionModel.State state) {
this.state = state;
}
public Collection<UserSessionNoteEntity> getNotes() {
return notes;
}
public void setNotes(Collection<UserSessionNoteEntity> notes) {
this.notes = notes;
}
}

View file

@ -0,0 +1,107 @@
package org.keycloak.models.sessions.jpa.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import java.io.Serializable;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@NamedQueries({
@NamedQuery(name = "removeUserSessionNoteByUser", query="delete from UserSessionNoteEntity r where r.userSession IN (select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId)"),
@NamedQuery(name = "removeUserSessionNoteByRealm", query="delete from UserSessionNoteEntity r where r.userSession IN (select c from UserSessionEntity c where c.realmId = :realmId)"),
@NamedQuery(name = "removeUserSessionNoteByExpired", query = "delete from UserSessionNoteEntity r where r.userSession IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime))")
})
@Table(name="USER_SESSION_NOTE")
@Entity
@IdClass(UserSessionNoteEntity.Key.class)
public class UserSessionNoteEntity {
@Id
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name = "USER_SESSION")
protected UserSessionEntity userSession;
@Id
@Column(name = "NAME")
protected String name;
@Column(name = "VALUE")
protected String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public UserSessionEntity getUserSession() {
return userSession;
}
public void setUserSession(UserSessionEntity userSession) {
this.userSession = userSession;
}
public static class Key implements Serializable {
protected UserSessionEntity userSession;
protected String name;
public Key() {
}
public Key(UserSessionEntity clientSession, String name) {
this.userSession = clientSession;
this.name = name;
}
public UserSessionEntity getUserSession() {
return userSession;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (name != null ? !name.equals(key.name) : key.name != null) return false;
if (userSession != null ? !userSession.getId().equals(key.userSession != null ? key.userSession.getId() : null) : key.userSession != null) return false;
return true;
}
@Override
public int hashCode() {
int result = userSession != null ? userSession.getId().hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
}
}

View file

@ -81,6 +81,17 @@ public class UserSessionAdapter implements UserSessionModel {
entity.setLastSessionRefresh(lastSessionRefresh);
}
@Override
public State getState() {
return entity.getState();
}
@Override
public void setState(State state) {
entity.setState(state);
}
@Override
public List<ClientSessionModel> getClientSessions() {
List<ClientSessionModel> clientSessionModels = new LinkedList<ClientSessionModel>();
@ -106,4 +117,22 @@ public class UserSessionAdapter implements UserSessionModel {
return getId().hashCode();
}
@Override
public String getNote(String name) {
return entity.getNotes().get(name);
}
@Override
public void setNote(String name, String value) {
entity.getNotes().put(name, value);
}
@Override
public void removeNote(String name) {
entity.getNotes().remove(name);
}
}

View file

@ -1,8 +1,12 @@
package org.keycloak.models.sessions.mem.entities;
import org.keycloak.models.UserSessionModel;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -18,6 +22,8 @@ public class UserSessionEntity {
private boolean rememberMe;
private int started;
private int lastSessionRefresh;
private UserSessionModel.State state;
private Map<String, String> notes = new HashMap<String, String>();
private List<ClientSessionEntity> clientSessions = Collections.synchronizedList(new LinkedList<ClientSessionEntity>());
public String getId() {
@ -109,4 +115,15 @@ public class UserSessionEntity {
return clientSessions;
}
public Map<String, String> getNotes() {
return notes;
}
public UserSessionModel.State getState() {
return state;
}
public void setState(UserSessionModel.State state) {
this.state = state;
}
}

View file

@ -82,6 +82,18 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
updateMongoEntity();
}
@Override
public State getState() {
return entity.getState();
}
@Override
public void setState(State state) {
entity.setState(state);
updateMongoEntity();
}
@Override
public List<ClientSessionModel> getClientSessions() {
List<ClientSessionModel> sessions = new LinkedList<ClientSessionModel>();
@ -97,6 +109,23 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
return sessions;
}
@Override
public String getNote(String name) {
return entity.getNotes().get(name);
}
@Override
public void setNote(String name, String value) {
entity.getNotes().put(name, value);
updateMongoEntity();
}
@Override
public void removeNote(String name) {
entity.getNotes().remove(name);
updateMongoEntity();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -5,10 +5,13 @@ import com.mongodb.QueryBuilder;
import org.keycloak.connections.mongo.api.MongoCollection;
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.entities.AbstractIdentifiableEntity;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -34,6 +37,10 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
private List<String> clientSessions = new ArrayList<String>();
private Map<String, String> notes = new HashMap<String, String>();
private UserSessionModel.State state;
public String getRealmId() {
return realmId;
}
@ -114,4 +121,19 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
context.getMongoStore().removeEntities(MongoClientSessionEntity.class, query, context);
}
public Map<String, String> getNotes() {
return notes;
}
public void setNotes(Map<String, String> notes) {
this.notes = notes;
}
public UserSessionModel.State getState() {
return state;
}
public void setState(UserSessionModel.State state) {
this.state = state;
}
}

View file

@ -140,11 +140,14 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
}
public String htmlResponse() throws ProcessingException, ConfigurationException, IOException {
return buildHtml(encoded());
return buildHtml(encoded(), destination);
}
public Response response() throws ConfigurationException, ProcessingException, IOException {
return buildResponse(document);
return buildResponse(document, destination);
}
public Response response(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
return buildResponse(document, actionUrl);
}
}
@ -162,11 +165,15 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
public Document getDocument() {
return document;
}
public URI responseUri() throws ConfigurationException, ProcessingException, IOException {
return generateRedirectUri("SAMLResponse", document);
public URI responseUri(String redirectUri) throws ConfigurationException, ProcessingException, IOException {
return generateRedirectUri("SAMLResponse", redirectUri, document);
}
public Response response() throws ProcessingException, ConfigurationException, IOException {
URI uri = responseUri();
return response(destination);
}
public Response response(String redirectUri) throws ProcessingException, ConfigurationException, IOException {
URI uri = responseUri(redirectUri);
CacheControl cacheControl = new CacheControl();
cacheControl.setNoCache(true);
@ -259,8 +266,8 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
}
protected Response buildResponse(Document responseDoc) throws ProcessingException, ConfigurationException, IOException {
String str = buildHtmlPostResponse(responseDoc);
protected Response buildResponse(Document responseDoc, String actionUrl) throws ProcessingException, ConfigurationException, IOException {
String str = buildHtmlPostResponse(responseDoc, actionUrl);
CacheControl cacheControl = new CacheControl();
cacheControl.setNoCache(true);
@ -269,14 +276,14 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
.header("Cache-Control", "no-cache, no-store").build();
}
protected String buildHtmlPostResponse(Document responseDoc) throws ProcessingException, ConfigurationException, IOException {
protected String buildHtmlPostResponse(Document responseDoc, String actionUrl) throws ProcessingException, ConfigurationException, IOException {
byte[] responseBytes = DocumentUtil.getDocumentAsString(responseDoc).getBytes("UTF-8");
String samlResponse = PostBindingUtil.base64Encode(new String(responseBytes));
return buildHtml(samlResponse);
return buildHtml(samlResponse, actionUrl);
}
protected String buildHtml(String samlResponse) {
protected String buildHtml(String samlResponse, String actionUrl) {
if (destination == null) {
throw SALM2LoginResponseBuilder.logger.nullValueError("Destination is null");
}
@ -291,7 +298,7 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
builder.append("</HEAD>");
builder.append("<BODY Onload=\"document.forms[0].submit()\">");
builder.append("<FORM METHOD=\"POST\" ACTION=\"" + destination + "\">");
builder.append("<FORM METHOD=\"POST\" ACTION=\"" + actionUrl + "\">");
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"" + key + "\"" + " VALUE=\"" + samlResponse + "\"/>");
if (isNotNull(relayState)) {
@ -315,8 +322,8 @@ public class SAML2BindingBuilder<T extends SAML2BindingBuilder> {
}
protected URI generateRedirectUri(String samlParameterName, Document document) throws ConfigurationException, ProcessingException, IOException {
UriBuilder builder = UriBuilder.fromUri(destination)
protected URI generateRedirectUri(String samlParameterName, String redirectUri, Document document) throws ConfigurationException, ProcessingException, IOException {
UriBuilder builder = UriBuilder.fromUri(redirectUri)
.replaceQuery(null)
.queryParam(samlParameterName, base64Encoded(document));
if (relayState != null) {

View file

@ -0,0 +1,81 @@
package org.keycloak.protocol.saml;
import org.picketlink.common.constants.JBossSAMLURIConstants;
import org.picketlink.common.exceptions.ConfigurationException;
import org.picketlink.common.exceptions.ParsingException;
import org.picketlink.common.exceptions.ProcessingException;
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
import org.picketlink.identity.federation.core.saml.v2.factories.JBossSAMLAuthnResponseFactory;
import org.picketlink.identity.federation.core.saml.v2.holders.IDPInfoHolder;
import org.picketlink.identity.federation.core.saml.v2.holders.IssuerInfoHolder;
import org.picketlink.identity.federation.core.saml.v2.holders.SPInfoHolder;
import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
import org.picketlink.identity.federation.saml.v2.protocol.StatusCodeType;
import org.picketlink.identity.federation.saml.v2.protocol.StatusResponseType;
import org.picketlink.identity.federation.saml.v2.protocol.StatusType;
import org.w3c.dom.Document;
import java.net.URI;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SAML2LogoutResponseBuilder extends SAML2BindingBuilder<SAML2LogoutResponseBuilder> {
protected String logoutRequestID;
public SAML2LogoutResponseBuilder logoutRequestID(String logoutRequestID) {
this.logoutRequestID = logoutRequestID;
return this;
}
public RedirectBindingBuilder redirectBinding() throws ConfigurationException, ProcessingException {
Document samlResponseDocument = buildDocument();
return new RedirectBindingBuilder(samlResponseDocument);
}
public PostBindingBuilder postBinding() throws ConfigurationException, ProcessingException {
Document samlResponseDocument = buildDocument();
return new PostBindingBuilder(samlResponseDocument);
}
public Document buildDocument() throws ProcessingException {
Document samlResponse = null;
try {
StatusResponseType statusResponse = new StatusResponseType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant());
// Status
StatusType statusType = new StatusType();
StatusCodeType statusCodeType = new StatusCodeType();
statusCodeType.setValue(URI.create(JBossSAMLURIConstants.STATUS_SUCCESS.get()));
statusType.setStatusCode(statusCodeType);
statusResponse.setStatus(statusType);
statusResponse.setInResponseTo(logoutRequestID);
NameIDType issuer = new NameIDType();
issuer.setValue(responseIssuer);
statusResponse.setIssuer(issuer);
statusResponse.setDestination(destination);
SAML2Response saml2Response = new SAML2Response();
samlResponse = saml2Response.convert(statusResponse);
} catch (ConfigurationException e) {
throw new ProcessingException(e);
} catch (ParsingException e) {
throw new ProcessingException(e);
}
if (encrypt) encryptDocument(samlResponse);
return samlResponse;
}
}

View file

@ -21,12 +21,16 @@ import org.keycloak.services.resources.admin.ClientAttributeCertificateResource;
import org.keycloak.services.resources.flows.Flows;
import org.picketlink.common.constants.GeneralConstants;
import org.picketlink.common.constants.JBossSAMLURIConstants;
import org.picketlink.common.exceptions.ConfigurationException;
import org.picketlink.common.exceptions.ParsingException;
import org.picketlink.common.exceptions.ProcessingException;
import org.picketlink.identity.federation.core.saml.v2.constants.X500SAMLProfileConstants;
import org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.security.PublicKey;
import java.util.UUID;
@ -55,6 +59,12 @@ public class SamlProtocol implements LoginProtocol {
public static final String SAML_ENCRYPT = "saml.encrypt";
public static final String SAML_FORCE_POST_BINDING = "saml.force.post.binding";
public static final String SAML_REQUEST_ID = "SAML_REQUEST_ID";
public static final String SAML_LOGOUT_BINDING = "saml.logout.binding";
public static final String SAML_LOGOUT_ISSUER = "saml.logout.issuer";
public static final String SAML_LOGOUT_REQUEST_ID = "SAML_LOGOUT_REQUEST_ID";
public static final String SAML_LOGOUT_RELAY_STATE = "SAML_LOGOUT_RELAY_STATE";
public static final String SAML_LOGOUT_BINDING_URI = "SAML_LOGOUT_BINDING_URI";
public static final String SAML_LOGOUT_SIGNATURE_ALGORITHM = "saml.logout.signature.algorithm";
public static final String SAML_NAME_ID = "SAML_NAME_ID";
public static final String SAML_NAME_ID_FORMAT = "SAML_NAME_ID_FORMAT";
public static final String SAML_DEFAULT_NAMEID_FORMAT = JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get();
@ -122,6 +132,15 @@ public class SamlProtocol implements LoginProtocol {
return SamlProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SamlProtocol.SAML_BINDING)) || "true".equals(client.getAttribute(SAML_FORCE_POST_BINDING));
}
protected boolean isLogoutPostBindingForInitiator(UserSessionModel session) {
String note = session.getNote(SamlProtocol.SAML_LOGOUT_BINDING);
return SamlProtocol.SAML_POST_BINDING.equals(note);
}
protected boolean isLogoutPostBindingForClient(ClientModel client) {
return SamlProtocol.SAML_POST_BINDING.equals(client.getAttribute(SamlProtocol.SAML_LOGOUT_BINDING));
}
protected String getNameIdFormat(ClientSessionModel clientSession) {
String nameIdFormat = clientSession.getNote(GeneralConstants.NAMEID_FORMAT);
if(nameIdFormat == null) return SAML_DEFAULT_NAMEID_FORMAT;
@ -222,19 +241,19 @@ public class SamlProtocol implements LoginProtocol {
}
}
private boolean requiresRealmSignature(ClientModel client) {
public static boolean requiresRealmSignature(ClientModel client) {
return "true".equals(client.getAttribute(SAML_SERVER_SIGNATURE));
}
private boolean requiresAssertionSignature(ClientModel client) {
public static boolean requiresAssertionSignature(ClientModel client) {
return "true".equals(client.getAttribute(SAML_ASSERTION_SIGNATURE));
}
private boolean includeAuthnStatement(ClientModel client) {
public static boolean includeAuthnStatement(ClientModel client) {
return "true".equals(client.getAttribute(SAML_AUTHNSTATEMENT));
}
private boolean multivaluedRoles(ClientModel client) {
public static boolean multivaluedRoles(ClientModel client) {
return "true".equals(client.getAttribute(SAML_MULTIVALUED_ROLES));
}
@ -271,34 +290,77 @@ public class SamlProtocol implements LoginProtocol {
return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
}
protected String getBindingUri(ClientModel client) {
String bindingUri = client.getAttribute(SamlProtocol.SAML_LOGOUT_BINDING_URI);
if (bindingUri == null ) bindingUri = ((ApplicationModel)client).getManagementUrl();
return ResourceAdminManager.resolveUri(uriInfo.getRequestUri(), bindingUri);
}
@Override
public Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
ClientModel client = clientSession.getClient();
if (!(client instanceof ApplicationModel)) return null;
ApplicationModel app = (ApplicationModel)client;
String bindingUri = getBindingUri(client);
if (bindingUri == null) return null;
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(clientSession, client);
try {
if (isLogoutPostBindingForClient(app)) {
return logoutBuilder.postBinding().response(bindingUri);
} else {
return logoutBuilder.redirectBinding().response(bindingUri);
}
} catch (ConfigurationException e) {
throw new RuntimeException(e);
} catch (ProcessingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ParsingException e) {
throw new RuntimeException(e);
}
}
@Override
public Response finishLogout(UserSessionModel userSession) {
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
builder.logoutRequestID(userSession.getNote(SAML_LOGOUT_REQUEST_ID));
builder.destination(userSession.getNote(SAML_LOGOUT_ISSUER));
String signingAlgorithm = userSession.getNote(SAML_LOGOUT_SIGNATURE_ALGORITHM);
if (signingAlgorithm != null) {
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(signingAlgorithm);
builder.signatureAlgorithm(algorithm)
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
.signDocument();
}
try {
if (isLogoutPostBindingForInitiator(userSession)) {
return builder.postBinding().response(userSession.getNote(SAML_LOGOUT_BINDING_URI));
} else {
return builder.redirectBinding().response(userSession.getNote(SAML_LOGOUT_BINDING_URI));
}
} catch (ConfigurationException e) {
throw new RuntimeException(e);
} catch (ProcessingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
ClientModel client = clientSession.getClient();
if (!(client instanceof ApplicationModel)) return;
ApplicationModel app = (ApplicationModel)client;
if (app.getManagementUrl() == null) return;
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(clientSession, client);
// build userPrincipal with subject used at login
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
.userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT))
.destination(client.getClientId());
if (requiresRealmSignature(client)) {
logoutBuilder.signatureAlgorithm(getSignatureAlgorithm(client))
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
.signDocument();
}
/*
if (requiresEncryption(client)) {
PublicKey publicKey = null;
try {
publicKey = PemUtils.decodePublicKey(client.getAttribute(ClientModel.PUBLIC_KEY));
} catch (Exception e) {
logger.error("failed", e);
return;
}
logoutBuilder.encrypt(publicKey);
}
*/
String logoutRequestString = null;
try {
@ -344,6 +406,31 @@ public class SamlProtocol implements LoginProtocol {
}
protected SAML2LogoutRequestBuilder createLogoutRequest(ClientSessionModel clientSession, ClientModel client) {
// build userPrincipal with subject used at login
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
.userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT))
.destination(client.getClientId());
if (requiresRealmSignature(client)) {
logoutBuilder.signatureAlgorithm(getSignatureAlgorithm(client))
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
.signDocument();
}
/*
if (requiresEncryption(client)) {
PublicKey publicKey = null;
try {
publicKey = PemUtils.decodePublicKey(client.getAttribute(ClientModel.PUBLIC_KEY));
} catch (Exception e) {
logger.error("failed", e);
return;
}
logoutBuilder.encrypt(publicKey);
}
*/
return logoutBuilder;
}
@Override
public void close() {

View file

@ -20,6 +20,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OpenIDConnectService;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.util.StreamUtil;
@ -118,10 +119,24 @@ public class SamlService {
return null;
}
protected Response handleSamlResponse(String samleResponse, String relayState) {
event.event(EventType.LOGIN);
event.error(Errors.INVALID_TOKEN);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
protected Response handleSamlResponse(String samlResponse, String relayState) {
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
if (authResult == null) {
logger.warn("Unknown saml response.");
event.event(EventType.LOGIN);
event.error(Errors.INVALID_TOKEN);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
}
// assume this is a logout response
UserSessionModel userSession = authResult.getSession();
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
logger.warn("Unknown saml response.");
logger.warn("UserSession is not tagged as logging out.");
event.event(EventType.LOGIN);
event.error(Errors.INVALID_TOKEN);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
}
return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection);
}
protected Response handleSamlRequest(String samlRequest, String relayState) {
@ -176,7 +191,7 @@ public class SamlService {
} else if (samlObject instanceof LogoutRequestType) {
event.event(EventType.LOGOUT);
LogoutRequestType logout = (LogoutRequestType) samlObject;
return logoutRequest(logout, client);
return logoutRequest(logout, client, relayState);
} else {
event.event(EventType.LOGIN);
@ -255,13 +270,32 @@ public class SamlService {
protected abstract String getBindingType();
protected Response logoutRequest(LogoutRequestType requestAbstractType, ClientModel client) {
protected Response logoutRequest(LogoutRequestType logoutRequest, ClientModel client, String relayState) {
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
if (authResult != null) {
logout(authResult.getSession());
String bindingUri = client.getAttribute(SamlProtocol.SAML_LOGOUT_BINDING_URI);
if (bindingUri == null ) bindingUri = ((ApplicationModel)client).getManagementUrl();
bindingUri = ResourceAdminManager.resolveUri(uriInfo.getRequestUri(), bindingUri);
UserSessionModel userSession = authResult.getSession();
userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING_URI, bindingUri);
if (SamlProtocol.requiresRealmSignature(client)) {
userSession.setNote(SamlProtocol.SAML_LOGOUT_SIGNATURE_ALGORITHM, SamlProtocol.getSignatureAlgorithm(client).toString());
}
if (relayState != null) userSession.setNote(SamlProtocol.SAML_LOGOUT_RELAY_STATE, relayState);
userSession.setNote(SamlProtocol.SAML_LOGOUT_REQUEST_ID, logoutRequest.getID());
String logoutBinding = client.getAttribute(SamlProtocol.SAML_LOGOUT_BINDING);
if (logoutBinding == null) logoutBinding = getBindingType();
userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING, logoutBinding);
userSession.setNote(SamlProtocol.SAML_LOGOUT_ISSUER, logoutRequest.getIssuer().getValue());
userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection);
}
String redirectUri = null;
if (client instanceof ApplicationModel) {
@ -269,20 +303,23 @@ public class SamlService {
}
if (redirectUri != null) {
String validatedRedirect = OpenIDConnectService.verifyRedirectUri(uriInfo, redirectUri, realm, client);;
if (validatedRedirect == null) {
redirectUri = OpenIDConnectService.verifyRedirectUri(uriInfo, redirectUri, realm, client);
if (redirectUri == null) {
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
}
return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
}
if (redirectUri != null) {
return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
} else {
return Response.ok().build();
}
}
private void logout(UserSessionModel userSession) {
authManager.logout(session, realm, userSession, uriInfo, clientConnection);
event.user(userSession.getUser()).session(userSession).success();
private Response logout(UserSessionModel userSession) {
Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection);
if (response == null) event.user(userSession.getUser()).session(userSession).success();
return response;
}
private boolean checkSsl() {

View file

@ -30,4 +30,6 @@ public interface LoginProtocol extends Provider {
Response consentDenied(ClientSessionModel clientSession);
void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
Response finishLogout(UserSessionModel userSession);
}

View file

@ -146,6 +146,17 @@ public class OpenIDConnect implements LoginProtocol {
}
}
@Override
public Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
// todo oidc redirect support
throw new RuntimeException("NOT IMPLEMENTED");
}
@Override
public Response finishLogout(UserSessionModel userSession) {
throw new RuntimeException("NOT IMPLEMENTED");
}
@Override
public void close() {

View file

@ -42,7 +42,6 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* Stateless object that manages authentication
@ -58,6 +57,7 @@ public class AuthenticationManager {
// used solely to determine is user is logged in
public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
public static final String KEYCLOAK_REMEMBER_ME = "KEYCLOAK_REMEMBER_ME";
public static final String KEYCLOAK_LOGOUT_PROTOCOL = "KEYCLOAK_LOGOUT_PROTOCOL";
protected BruteForceProtector protector;
@ -81,6 +81,7 @@ public class AuthenticationManager {
public static void logout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) {
if (userSession == null) return;
UserModel user = userSession.getUser();
userSession.setState(UserSessionModel.State.LOGGING_OUT);
logger.debugv("Logging out: {0} ({1})", user.getUsername(), userSession.getId());
expireIdentityCookie(realm, uriInfo, connection);
@ -88,17 +89,85 @@ public class AuthenticationManager {
for (ClientSessionModel clientSession : userSession.getClientSessions()) {
ClientModel client = clientSession.getClient();
if (client instanceof ApplicationModel) {
if (client instanceof ApplicationModel && !client.isFrontchannelLogout() && clientSession.getAction() != ClientSessionModel.Action.LOGGED_OUT) {
String authMethod = clientSession.getAuthMethod();
if (authMethod == null) continue; // must be a keycloak service like account
LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
protocol.setRealm(realm)
.setUriInfo(uriInfo);
protocol.backchannelLogout(userSession, clientSession);
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
}
}
userSession.setState(UserSessionModel.State.LOGGED_OUT);
session.sessions().removeUserSession(realm, userSession);
}
public static Response browserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) {
if (userSession == null) return null;
UserModel user = userSession.getUser();
logger.debugv("Logging out: {0} ({1})", user.getUsername(), userSession.getId());
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
userSession.setState(UserSessionModel.State.LOGGING_OUT);
}
List<ClientSessionModel> redirectClients = new LinkedList<ClientSessionModel>();
for (ClientSessionModel clientSession : userSession.getClientSessions()) {
ClientModel client = clientSession.getClient();
if (client.isFrontchannelLogout()) {
String authMethod = clientSession.getAuthMethod();
if (authMethod == null) continue; // must be a keycloak service like account
redirectClients.add(clientSession);
continue;
}
if (client instanceof ApplicationModel && !client.isFrontchannelLogout() && clientSession.getAction() != ClientSessionModel.Action.LOGGED_OUT) {
String authMethod = clientSession.getAuthMethod();
if (authMethod == null) continue; // must be a keycloak service like account
LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
protocol.setRealm(realm)
.setUriInfo(uriInfo);
try {
protocol.backchannelLogout(userSession, clientSession);
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
} catch (Exception e) {
logger.warn("Failed to logout client, continuing", e);
}
}
}
if (redirectClients.size() == 0) {
return finishBrowserLogout(session, realm, userSession, uriInfo, connection);
}
for (ClientSessionModel nextRedirectClient : redirectClients) {
String authMethod = nextRedirectClient.getAuthMethod();
LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
protocol.setRealm(realm)
.setUriInfo(uriInfo);
// setting this to logged out cuz I"m not sure protocols can always verify that the client was logged out or not
nextRedirectClient.setAction(ClientSessionModel.Action.LOGGED_OUT);
try {
Response response = protocol.frontchannelLogout(userSession, nextRedirectClient);
if (response != null) return response;
} catch (Exception e) {
logger.warn("Failed to logout client, continuing", e);
}
}
return finishBrowserLogout(session, realm, userSession, uriInfo, connection);
}
protected static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) {
expireIdentityCookie(realm, uriInfo, connection);
expireRememberMeCookie(realm, uriInfo, connection);
userSession.setState(UserSessionModel.State.LOGGED_OUT);
String method = userSession.getNote(KEYCLOAK_LOGOUT_PROTOCOL);
LoginProtocol protocol = session.getProvider(LoginProtocol.class, method);
protocol.setRealm(realm)
.setUriInfo(uriInfo);
Response response = protocol.finishLogout(userSession);
session.sessions().removeUserSession(realm, userSession);
return response;
}

View file

@ -52,6 +52,12 @@ public class ResourceAdminManager {
return new ApacheHttpClient4Executor(client);
}
public static String resolveUri(URI requestUri, String uri) {
String absoluteURI = ResolveRelative.resolveRelativeUri(requestUri, uri);
return StringPropertyReplacer.replaceProperties(absoluteURI);
}
public static String getManagementUrl(URI requestUri, ApplicationModel application) {
String mgmtUrl = application.getManagementUrl();
if (mgmtUrl == null || mgmtUrl.equals("")) {

View file

@ -79,6 +79,11 @@ public class SamlBindingTest {
Thread.sleep(10000000);
}
protected void checkLoggedOut() {
Assert.assertTrue(driver.getPageSource().contains("request-path: /logout.jsp"));
Assert.assertTrue(driver.getPageSource().contains("principal=null"));
}
@Test
public void testPostSimpleLoginLogout() {
@ -89,8 +94,7 @@ public class SamlBindingTest {
System.out.println(driver.getPageSource());
Assert.assertTrue(driver.getPageSource().contains("bburke"));
driver.navigate().to("http://localhost:8081/sales-post?GLO=true");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
checkLoggedOut();
}
@Test
public void testPostSignedLoginLogout() {
@ -100,7 +104,7 @@ public class SamlBindingTest {
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/sales-post-sig/");
Assert.assertTrue(driver.getPageSource().contains("bburke"));
driver.navigate().to("http://localhost:8081/sales-post-sig?GLO=true");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
checkLoggedOut();
}
@Test
@ -113,7 +117,7 @@ public class SamlBindingTest {
Assert.assertFalse(driver.getPageSource().contains("bburke"));
Assert.assertTrue(driver.getPageSource().contains("principal=G-"));
driver.navigate().to("http://localhost:8081/sales-post-sig-transient?GLO=true");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
checkLoggedOut();
}
@Test
@ -126,7 +130,7 @@ public class SamlBindingTest {
Assert.assertFalse(driver.getPageSource().contains("bburke"));
Assert.assertTrue(driver.getPageSource().contains("principal=G-"));
driver.navigate().to("http://localhost:8081/sales-post-sig-persistent?GLO=true");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
checkLoggedOut();
}
@Test
@ -138,7 +142,7 @@ public class SamlBindingTest {
System.out.println(driver.getPageSource());
Assert.assertTrue(driver.getPageSource().contains("principal=bburke@redhat.com"));
driver.navigate().to("http://localhost:8081/sales-post-sig-email?GLO=true");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
checkLoggedOut();
}
@Test
@ -149,7 +153,7 @@ public class SamlBindingTest {
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/employee-sig/");
Assert.assertTrue(driver.getPageSource().contains("bburke"));
driver.navigate().to("http://localhost:8081/employee-sig?GLO=true");
Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/demo/protocol/saml"));
checkLoggedOut();
}
@ -161,7 +165,7 @@ public class SamlBindingTest {
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/sales-post-enc/");
Assert.assertTrue(driver.getPageSource().contains("bburke"));
driver.navigate().to("http://localhost:8081/sales-post-enc?GLO=true");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
checkLoggedOut();
}
@Test
@ -209,7 +213,7 @@ public class SamlBindingTest {
String pageSource = driver.getPageSource();
Assert.assertTrue(pageSource.contains("bburke"));
driver.navigate().to("http://localhost:8081/sales-metadata?GLO=true");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/auth/realms/demo/protocol/saml");
checkLoggedOut();
}

View file

@ -36,6 +36,9 @@ public abstract class SamlKeycloakRule extends AbstractKeycloakRule {
resp.setContentType("text/plain");
OutputStream stream = resp.getOutputStream();
Principal principal = req.getUserPrincipal();
stream.write("request-path: ".getBytes());
stream.write(req.getPathInfo().getBytes());
stream.write("\n".getBytes());
stream.write("principal=".getBytes());
if (principal == null) {
stream.write("null".getBytes());
@ -49,6 +52,9 @@ public abstract class SamlKeycloakRule extends AbstractKeycloakRule {
resp.setContentType("text/plain");
OutputStream stream = resp.getOutputStream();
Principal principal = req.getUserPrincipal();
stream.write("request-path: ".getBytes());
stream.write(req.getPathInfo().getBytes());
stream.write("\n".getBytes());
stream.write("principal=".getBytes());
if (principal == null) {
stream.write("null".getBytes());