README.md
@@ -17,7 +17,6 @@ The following features are **missing**: * SAML request/response [CAS 3.0 - optional] * Proxy ticket service and proxy ticket validation [CAS 2.0] The following features are out of scope: * Long-Term Tickets - Remember-Me [CAS 3.0 - optional] src/main/java/org/keycloak/protocol/cas/CASLoginProtocol.java
@@ -6,23 +6,20 @@ import org.apache.http.HttpEntity; import org.jboss.logging.Logger; import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.Time; import org.keycloak.events.Details; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; import org.keycloak.forms.login.LoginFormsProvider; import org.keycloak.models.*; import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.cas.endpoints.AbstractValidateEndpoint; import org.keycloak.protocol.cas.utils.LogoutHelper; import org.keycloak.protocol.oidc.utils.OAuth2Code; import org.keycloak.protocol.oidc.utils.OAuth2CodeParser; import org.keycloak.services.ErrorPage; import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.sessions.AuthenticationSessionModel; import java.io.IOException; import java.net.URI; import java.util.UUID; public class CASLoginProtocol implements LoginProtocol { private static final Logger logger = Logger.getLogger(CASLoginProtocol.class); @@ -35,11 +32,17 @@ public static final String GATEWAY_PARAM = "gateway"; public static final String TICKET_PARAM = "ticket"; public static final String FORMAT_PARAM = "format"; public static final String PGTURL_PARAM = "pgtUrl"; public static final String TARGET_SERVICE_PARAM = "targetService"; public static final String PGT_PARAM = "pgt"; public static final String TICKET_RESPONSE_PARAM = "ticket"; public static final String SAMLART_RESPONSE_PARAM = "SAMLart"; public static final String SERVICE_TICKET_PREFIX = "ST-"; public static final String PROXY_GRANTING_TICKET_IOU_PREFIX = "PGTIOU-"; public static final String PROXY_GRANTING_TICKET_PREFIX = "PGT-"; public static final String PROXY_TICKET_PREFIX = "PT-"; public static final String SESSION_SERVICE_TICKET = "service_ticket"; public static final String LOGOUT_REDIRECT_URI = "CAS_LOGOUT_REDIRECT_URI"; @@ -98,15 +101,9 @@ String service = authSession.getRedirectUri(); //TODO validate service OAuth2Code codeData = new OAuth2Code(UUID.randomUUID().toString(), Time.currentTime() + userSession.getRealm().getAccessCodeLifespan(), null, null, authSession.getRedirectUri(), null, null, userSession.getId()); String code = OAuth2CodeParser.persistCode(session, clientSession, codeData); KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(service); String loginTicket = SERVICE_TICKET_PREFIX + code; String loginTicket = AbstractValidateEndpoint.getST(session, clientSession, service); if (authSession.getClientNotes().containsKey(CASLoginProtocol.TARGET_PARAM)) { // This was a SAML 1.1 auth request so return the ticket ID as "SAMLart" instead of "ticket" src/main/java/org/keycloak/protocol/cas/CASLoginProtocolService.java
@@ -1,8 +1,8 @@ package org.keycloak.protocol.cas; import jakarta.ws.rs.Path; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import org.keycloak.events.EventBuilder; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -51,13 +51,12 @@ @Path("proxyValidate") public Object proxyValidate() { //TODO implement return serviceValidate(); return new ProxyValidateEndpoint(session, realm, event); } @Path("proxy") public Object proxy() { return Response.serverError().entity("Not implemented").build(); return new ProxyEndpoint(session, realm, event); } @Path("p3/serviceValidate") src/main/java/org/keycloak/protocol/cas/endpoints/AbstractValidateEndpoint.java
@@ -5,29 +5,39 @@ import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.common.util.Time; import org.keycloak.models.*; import org.keycloak.protocol.ProtocolMapper; import org.keycloak.protocol.cas.CASLoginProtocol; import org.keycloak.protocol.cas.mappers.CASAttributeMapper; import org.keycloak.protocol.cas.representations.CASErrorCode; import org.keycloak.protocol.cas.utils.CASValidationException; import org.keycloak.protocol.oidc.utils.OAuth2CodeParser; import org.keycloak.protocol.oidc.utils.OAuth2Code; import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.UserSessionCrossDCManager; import org.keycloak.services.util.DefaultClientSessionContext; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.apache.http.client.utils.URIBuilder; import org.apache.http.client.methods.HttpGet; import org.apache.http.HttpResponse; import org.apache.http.impl.client.HttpClientBuilder; public abstract class AbstractValidateEndpoint { protected final Logger logger = Logger.getLogger(getClass()); private static final Pattern DOT = Pattern.compile("\\."); protected KeycloakSession session; protected RealmModel realm; protected EventBuilder event; protected ClientModel client; protected AuthenticatedClientSessionModel clientSession; protected String pgtIou; public AbstractValidateEndpoint(KeycloakSession session, RealmModel realm, EventBuilder event) { this.session = session; @@ -74,50 +84,78 @@ session.getContext().setClient(client); } protected void checkTicket(String ticket, boolean requireReauth) { protected void checkTicket(String ticket, String prefix, boolean requireReauth) { if (ticket == null) { event.error(Errors.INVALID_CODE); throw new CASValidationException(CASErrorCode.INVALID_REQUEST, "Missing parameter: " + CASLoginProtocol.TICKET_PARAM, Response.Status.BAD_REQUEST); } if (!ticket.startsWith(CASLoginProtocol.SERVICE_TICKET_PREFIX)) { if (!ticket.startsWith(prefix)) { event.error(Errors.INVALID_CODE); throw new CASValidationException(CASErrorCode.INVALID_TICKET_SPEC, "Malformed service ticket", Response.Status.BAD_REQUEST); } String code = ticket.substring(CASLoginProtocol.SERVICE_TICKET_PREFIX.length()); boolean isReusable = ticket.startsWith(CASLoginProtocol.PROXY_GRANTING_TICKET_PREFIX); OAuth2CodeParser.ParseResult parseResult = OAuth2CodeParser.parseCode(session, code, realm, event); if (parseResult.isIllegalCode()) { String[] parsed = DOT.split(ticket.substring(prefix.length()), 3); if (parsed.length != 3) { event.error(Errors.INVALID_CODE); // Attempt to use same code twice should invalidate existing clientSession AuthenticatedClientSessionModel clientSession = parseResult.getClientSession(); if (clientSession != null) { clientSession.detachFromUserSession(); throw new CASValidationException(CASErrorCode.INVALID_TICKET_SPEC, "Invalid format of the code", Response.Status.BAD_REQUEST); } String codeUUID = parsed[0]; String userSessionId = parsed[1]; String clientUUID = parsed[2]; event.detail(Details.CODE_ID, userSessionId); event.session(userSessionId); // Retrieve UserSession UserSessionModel userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, userSessionId, clientUUID); if (userSession == null) { // Needed to track if code is invalid userSession = session.sessions().getUserSession(realm, userSessionId); if (userSession == null) { event.error(Errors.USER_SESSION_NOT_FOUND); throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Code not valid", Response.Status.BAD_REQUEST); } } clientSession = userSession.getAuthenticatedClientSessionByClient(clientUUID); if (clientSession == null) { event.error(Errors.INVALID_CODE); throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Code not valid", Response.Status.BAD_REQUEST); } clientSession = parseResult.getClientSession(); SingleUseObjectProvider codeStore = session.singleUseObjects(); Map<String, String> codeDataSerialized = isReusable ? codeStore.get(prefix + codeUUID) : codeStore.remove(prefix + codeUUID); if (parseResult.isExpiredCode()) { // Either code not available if (codeDataSerialized == null) { event.error(Errors.INVALID_CODE); throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Code not valid", Response.Status.BAD_REQUEST); } OAuth2Code codeData = OAuth2Code.deserializeCode(codeDataSerialized); String persistedUserSessionId = codeData.getUserSessionId(); if (!userSessionId.equals(persistedUserSessionId)) { event.error(Errors.INVALID_CODE); throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Code not valid", Response.Status.BAD_REQUEST); } // Finally doublecheck if code is not expired int currentTime = Time.currentTime(); if (currentTime > codeData.getExpiration()) { event.error(Errors.EXPIRED_CODE); throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Code is expired", Response.Status.BAD_REQUEST); } clientSession.setNote(CASLoginProtocol.SESSION_SERVICE_TICKET, ticket); clientSession.setNote(prefix, ticket); if (requireReauth && AuthenticationManager.isSSOAuthentication(clientSession)) { event.error(Errors.SESSION_EXPIRED); throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Interactive authentication was requested but not performed", Response.Status.BAD_REQUEST); } UserSessionModel userSession = clientSession.getUserSession(); if (userSession == null) { event.error(Errors.USER_SESSION_NOT_FOUND); throw new CASValidationException(CASErrorCode.INVALID_TICKET, "User session not found", Response.Status.BAD_REQUEST); } UserModel user = userSession.getUser(); @@ -133,14 +171,44 @@ event.user(userSession.getUser()); event.session(userSession.getId()); if (client == null) { client = clientSession.getClient(); } else { if (!client.getClientId().equals(clientSession.getClient().getClientId())) { event.error(Errors.INVALID_CODE); throw new CASValidationException(CASErrorCode.INVALID_SERVICE, "Auth error", Response.Status.BAD_REQUEST); throw new CASValidationException(CASErrorCode.INVALID_SERVICE, "Invalid service", Response.Status.BAD_REQUEST); } } if (!AuthenticationManager.isSessionValid(realm, userSession)) { event.error(Errors.USER_SESSION_NOT_FOUND); throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Session not active", Response.Status.BAD_REQUEST); } } protected void createProxyGrant(String pgtUrl) { if ( RedirectUtils.verifyRedirectUri(session, pgtUrl, client) == null ) { event.error(Errors.INVALID_REQUEST); throw new CASValidationException(CASErrorCode.INVALID_PROXY_CALLBACK, "Proxy callback is invalid", Response.Status.BAD_REQUEST); } String pgtIou = getPGTIOU(); String pgtId = getPGT(session, clientSession, pgtUrl); try { HttpResponse response = HttpClientBuilder.create().build().execute( new HttpGet(new URIBuilder(pgtUrl).setParameter("pgtIou",pgtIou).setParameter("pgtId",pgtId).build()) ); if (response.getStatusLine().getStatusCode() != 200) { throw new Exception(); } this.pgtIou = pgtIou; } catch (Exception e) { event.error(Errors.INVALID_REQUEST); throw new CASValidationException(CASErrorCode.PROXY_CALLBACK_ERROR, "Proxy callback returned an error", Response.Status.BAD_REQUEST); } } @@ -160,4 +228,40 @@ } return attributes; } protected String getPGTIOU() { return CASLoginProtocol.PROXY_GRANTING_TICKET_IOU_PREFIX + UUID.randomUUID().toString(); } protected String getPGT(KeycloakSession session, AuthenticatedClientSessionModel clientSession, String pgtUrl) { return persistedTicket(pgtUrl, CASLoginProtocol.PROXY_GRANTING_TICKET_PREFIX); } protected String getPT(KeycloakSession session, AuthenticatedClientSessionModel clientSession, String targetService) { return persistedTicket(targetService, CASLoginProtocol.PROXY_TICKET_PREFIX); } protected String getST(String redirectUri) { return persistedTicket(redirectUri, CASLoginProtocol.SERVICE_TICKET_PREFIX); } public static String getST(KeycloakSession session, AuthenticatedClientSessionModel clientSession, String redirectUri) { ValidateEndpoint vp = new ValidateEndpoint(session,null,null); vp.clientSession = clientSession; return vp.getST(redirectUri); } protected String persistedTicket(String redirectUriParam, String prefix) { String key = UUID.randomUUID().toString(); UserSessionModel userSession = clientSession.getUserSession(); OAuth2Code codeData = new OAuth2Code(key, Time.currentTime() + userSession.getRealm().getAccessCodeLifespan(), null, null, redirectUriParam, null, null, userSession.getId()); session.singleUseObjects().put(prefix + key, clientSession.getUserSession().getRealm().getAccessCodeLifespan(), codeData.serializeCode()); return prefix + key + "." + clientSession.getUserSession().getId() + "." + clientSession.getClient().getId(); } } src/main/java/org/keycloak/protocol/cas/endpoints/ProxyEndpoint.java
New file @@ -0,0 +1,57 @@ package org.keycloak.protocol.cas.endpoints; import jakarta.ws.rs.GET; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; import org.jboss.resteasy.annotations.cache.NoCache; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; import org.keycloak.models.*; import org.keycloak.protocol.cas.CASLoginProtocol; import org.keycloak.protocol.cas.representations.CASServiceResponse; import org.keycloak.protocol.cas.utils.CASValidationException; import org.keycloak.protocol.cas.utils.ContentTypeHelper; import org.keycloak.protocol.cas.utils.ServiceResponseHelper; public class ProxyEndpoint extends AbstractValidateEndpoint { public ProxyEndpoint(KeycloakSession session, RealmModel realm, EventBuilder event) { super(session, realm, event); } @GET @NoCache public Response build() { MultivaluedMap<String, String> params = session.getContext().getUri().getQueryParameters(); String targetService = params.getFirst(CASLoginProtocol.TARGET_SERVICE_PARAM); String pgt = params.getFirst(CASLoginProtocol.PGT_PARAM); event.event(EventType.CODE_TO_TOKEN); try { checkSsl(); checkRealm(); checkTicket(pgt, CASLoginProtocol.PROXY_GRANTING_TICKET_PREFIX, false); event.success(); return successResponse(getPT(this.session, clientSession, targetService)); } catch (CASValidationException e) { return errorResponse(e); } } protected Response successResponse(String pt) { CASServiceResponse serviceResponse = ServiceResponseHelper.createProxySuccess(pt); return prepare(Response.Status.OK, serviceResponse); } protected Response errorResponse(CASValidationException e) { CASServiceResponse serviceResponse = ServiceResponseHelper.createProxyFailure(e.getError(), e.getErrorDescription()); return prepare(e.getStatus(), serviceResponse); } private Response prepare(Response.Status status, CASServiceResponse serviceResponse) { MediaType responseMediaType = new ContentTypeHelper(session.getContext().getUri()).selectResponseType(); return ServiceResponseHelper.createResponse(status, responseMediaType, serviceResponse); } } src/main/java/org/keycloak/protocol/cas/endpoints/ProxyValidateEndpoint.java
New file @@ -0,0 +1,73 @@ package org.keycloak.protocol.cas.endpoints; import jakarta.ws.rs.GET; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; import java.util.Map; import org.jboss.resteasy.annotations.cache.NoCache; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; import org.keycloak.models.*; import org.keycloak.protocol.cas.CASLoginProtocol; import org.keycloak.protocol.cas.representations.CASErrorCode; import org.keycloak.protocol.cas.representations.CASServiceResponse; import org.keycloak.protocol.cas.utils.CASValidationException; import org.keycloak.protocol.cas.utils.ContentTypeHelper; import org.keycloak.protocol.cas.utils.ServiceResponseHelper; public class ProxyValidateEndpoint extends AbstractValidateEndpoint { public ProxyValidateEndpoint(KeycloakSession session,RealmModel realm, EventBuilder event) { super(session, realm, event); } @GET @NoCache public Response build() { MultivaluedMap<String, String> params = session.getContext().getUri().getQueryParameters(); String ticket = params.getFirst(CASLoginProtocol.TICKET_PARAM); String pgtUrl = params.getFirst(CASLoginProtocol.PGTURL_PARAM); boolean renew = params.containsKey(CASLoginProtocol.RENEW_PARAM); event.event(EventType.CODE_TO_TOKEN); try { String prefix = ticket.startsWith(CASLoginProtocol.PROXY_TICKET_PREFIX)? CASLoginProtocol.PROXY_TICKET_PREFIX:( ticket.startsWith(CASLoginProtocol.SERVICE_TICKET_PREFIX)? CASLoginProtocol.SERVICE_TICKET_PREFIX : null ); if (prefix == null) { event.error(Errors.INVALID_CODE); throw new CASValidationException(CASErrorCode.INVALID_TICKET_SPEC, "Malformed service ticket", Response.Status.BAD_REQUEST); } checkSsl(); checkRealm(); checkTicket(ticket, prefix, renew); if (pgtUrl != null) createProxyGrant(pgtUrl); event.success(); return successResponse(); } catch (CASValidationException e) { return errorResponse(e); } } protected Response successResponse() { UserSessionModel userSession = clientSession.getUserSession(); Map<String, Object> attributes = getUserAttributes(); CASServiceResponse serviceResponse = ServiceResponseHelper.createSuccess(userSession.getUser().getUsername(),attributes); return prepare(Response.Status.OK, serviceResponse); } protected Response errorResponse(CASValidationException e) { CASServiceResponse serviceResponse = ServiceResponseHelper.createFailure(e.getError(), e.getErrorDescription()); return prepare(e.getStatus(), serviceResponse); } private Response prepare(Response.Status status, CASServiceResponse serviceResponse) { MediaType responseMediaType = new ContentTypeHelper(session.getContext().getUri()).selectResponseType(); return ServiceResponseHelper.createResponse(status, responseMediaType, serviceResponse); } } src/main/java/org/keycloak/protocol/cas/endpoints/SamlValidateEndpoint.java
@@ -56,7 +56,7 @@ String issuer = Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()); String ticket = getTicket(input); checkTicket(ticket, renew); checkTicket(ticket, CASLoginProtocol.SERVICE_TICKET_PREFIX, renew); UserModel user = clientSession.getUserSession().getUser(); Map<String, Object> attributes = getUserAttributes(); src/main/java/org/keycloak/protocol/cas/endpoints/ServiceValidateEndpoint.java
@@ -22,7 +22,7 @@ protected Response successResponse() { UserSessionModel userSession = clientSession.getUserSession(); Map<String, Object> attributes = getUserAttributes(); CASServiceResponse serviceResponse = ServiceResponseHelper.createSuccess(userSession.getUser().getUsername(), attributes); CASServiceResponse serviceResponse = ServiceResponseHelper.createSuccess(userSession.getUser().getUsername(), attributes, this.pgtIou, null); return prepare(Response.Status.OK, serviceResponse); } src/main/java/org/keycloak/protocol/cas/endpoints/ValidateEndpoint.java
@@ -26,6 +26,7 @@ public Response build() { MultivaluedMap<String, String> params = session.getContext().getUri().getQueryParameters(); String service = params.getFirst(CASLoginProtocol.SERVICE_PARAM); String pgtUrl = params.getFirst(CASLoginProtocol.PGTURL_PARAM); String ticket = params.getFirst(CASLoginProtocol.TICKET_PARAM); boolean renew = params.containsKey(CASLoginProtocol.RENEW_PARAM); @@ -36,7 +37,9 @@ checkRealm(); checkClient(service); checkTicket(ticket, renew); checkTicket(ticket, CASLoginProtocol.SERVICE_TICKET_PREFIX, renew); if (pgtUrl != null) createProxyGrant(pgtUrl); event.success(); return successResponse(); src/main/java/org/keycloak/protocol/cas/representations/CASErrorCode.java
@@ -9,6 +9,8 @@ UNAUTHORIZED_SERVICE_PROXY, /** The proxy callback specified is invalid. The credentials specified for proxy authentication do not meet the security requirements */ INVALID_PROXY_CALLBACK, /** The proxy callback specified return with error*/ PROXY_CALLBACK_ERROR, /** the ticket provided was not valid, or the ticket did not come from an initial login and renew was set on validation. */ INVALID_TICKET, /** the ticket provided was valid, but the service specified did not match the service associated with the ticket. */ src/main/java/org/keycloak/protocol/cas/representations/CASServiceResponse.java
@@ -6,6 +6,8 @@ public class CASServiceResponse { private CASServiceResponseAuthenticationFailure authenticationFailure; private CASServiceResponseAuthenticationSuccess authenticationSuccess; private CASServiceResponseProxySuccess proxySuccess; private CASServiceResponseProxyFailure proxyFailure; public CASServiceResponseAuthenticationFailure getAuthenticationFailure() { return this.authenticationFailure; @@ -22,4 +24,20 @@ public void setAuthenticationSuccess(final CASServiceResponseAuthenticationSuccess authenticationSuccess) { this.authenticationSuccess = authenticationSuccess; } public CASServiceResponseProxySuccess getProxySuccess() { return this.proxySuccess; } public void setProxySuccess(final CASServiceResponseProxySuccess proxySuccess) { this.proxySuccess = proxySuccess; } public CASServiceResponseProxyFailure getProxyFailure() { return this.proxyFailure; } public void setProxyFailure(final CASServiceResponseProxyFailure proxyFailure) { this.proxyFailure = proxyFailure; } } src/main/java/org/keycloak/protocol/cas/representations/CASServiceResponseProxyFailure.java
New file @@ -0,0 +1,30 @@ package org.keycloak.protocol.cas.representations; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlAttribute; import jakarta.xml.bind.annotation.XmlValue; @XmlAccessorType(XmlAccessType.FIELD) public class CASServiceResponseProxyFailure { @XmlAttribute private String code; @XmlValue private String description; public String getCode() { return this.code; } public void setCode(final String code) { this.code = code; } public String getDescription() { return this.description; } public void setDescription(final String description) { this.description = description; } } src/main/java/org/keycloak/protocol/cas/representations/CASServiceResponseProxySuccess.java
New file @@ -0,0 +1,18 @@ package org.keycloak.protocol.cas.representations; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; @XmlAccessorType(XmlAccessType.FIELD) public class CASServiceResponseProxySuccess { private String proxyTicket; public String getProxyTicket() { return this.proxyTicket; } public void setProxyTicket(final String proxyTicket) { this.proxyTicket = proxyTicket; } } src/main/java/org/keycloak/protocol/cas/utils/AttributesMapAdapter.java
@@ -39,7 +39,7 @@ this.elements = new ArrayList<>(); for (Map.Entry<String, Object> entry : attributes.entrySet()) { if (entry.getValue() instanceof Collection) { for (Object item : ((Collection) entry.getValue())) { for (Object item : ((Collection<?>) entry.getValue())) { addElement(entry.getKey(), item); } } else { src/main/java/org/keycloak/protocol/cas/utils/CASValidationException.java
@@ -5,6 +5,7 @@ import org.keycloak.protocol.cas.representations.CASErrorCode; public class CASValidationException extends WebApplicationException { private static final long serialVersionUID = 4929825917145240776L; private final CASErrorCode error; private final String errorDescription; private final Response.Status status; src/main/java/org/keycloak/protocol/cas/utils/ServiceResponseHelper.java
@@ -7,6 +7,8 @@ import org.keycloak.protocol.cas.representations.CASServiceResponse; import org.keycloak.protocol.cas.representations.CASServiceResponseAuthenticationFailure; import org.keycloak.protocol.cas.representations.CASServiceResponseAuthenticationSuccess; import org.keycloak.protocol.cas.representations.CASServiceResponseProxySuccess; import org.keycloak.protocol.cas.representations.CASServiceResponseProxyFailure; import java.util.List; import java.util.Map; @@ -43,6 +45,24 @@ return response; } public static CASServiceResponse createProxySuccess(String pt) { CASServiceResponse response = new CASServiceResponse(); CASServiceResponseProxySuccess success = new CASServiceResponseProxySuccess(); success.setProxyTicket(pt); response.setProxySuccess(success); return response; } public static CASServiceResponse createProxyFailure(CASErrorCode errorCode, String errorDescription) { CASServiceResponse response = new CASServiceResponse(); CASServiceResponseProxyFailure failure = new CASServiceResponseProxyFailure(); failure.setCode(errorCode == null ? CASErrorCode.INTERNAL_ERROR.name() : errorCode.name()); failure.setDescription(errorDescription); response.setProxyFailure(failure); return response; } public static Response createResponse(Response.Status status, MediaType mediaType, CASServiceResponse serviceResponse) { Response.ResponseBuilder builder = Response.status(status) .header(HttpHeaders.CONTENT_TYPE, mediaType.withCharset("utf-8")); src/test/java/org/keycloak/protocol/cas/ServiceResponseTest.java
@@ -52,10 +52,10 @@ assertEquals("username", xpath.evaluate("/cas:serviceResponse/cas:authenticationSuccess/cas:user", doc)); int idx = 0; for (Node node : xpath.selectNodes("/cas:serviceResponse/cas:authenticationSuccess/cas:attributes/cas:list", doc)) { assertEquals(((List)attributes.get("list")).get(idx), node.getTextContent()); assertEquals(((List<?>)attributes.get("list")).get(idx), node.getTextContent()); idx++; } assertEquals(((List)attributes.get("list")).size(), idx); assertEquals(((List<?>)attributes.get("list")).size(), idx); assertEquals(attributes.get("int").toString(), xpath.evaluate("/cas:serviceResponse/cas:authenticationSuccess/cas:attributes/cas:int", doc)); assertEquals(attributes.get("string").toString(), xpath.evaluate("/cas:serviceResponse/cas:authenticationSuccess/cas:attributes/cas:string", doc));