mirror of https://github.com/jacekkow/keycloak-protocol-cas

Jacek Kowalski
2020-05-10 4da0d94b96e662b8dffe281d0a2de812f11cda71
commit | author | age
7f7e0c 1 package org.keycloak.protocol.cas;
MP 2
57a6c1 3 import org.apache.http.HttpEntity;
MP 4 import org.jboss.logging.Logger;
7f7e0c 5 import org.keycloak.common.util.KeycloakUriBuilder;
c140ce 6 import org.keycloak.common.util.Time;
7f7e0c 7 import org.keycloak.events.EventBuilder;
MP 8 import org.keycloak.events.EventType;
4a6620 9 import org.keycloak.forms.login.LoginFormsProvider;
7f7e0c 10 import org.keycloak.models.*;
MP 11 import org.keycloak.protocol.LoginProtocol;
57a6c1 12 import org.keycloak.protocol.cas.utils.LogoutHelper;
c140ce 13 import org.keycloak.protocol.oidc.utils.OAuth2Code;
MP 14 import org.keycloak.protocol.oidc.utils.OAuth2CodeParser;
7f7e0c 15 import org.keycloak.services.managers.ResourceAdminManager;
f75caf 16 import org.keycloak.sessions.AuthenticationSessionModel;
7f7e0c 17
MP 18 import javax.ws.rs.core.HttpHeaders;
19 import javax.ws.rs.core.Response;
20 import javax.ws.rs.core.UriInfo;
57a6c1 21 import java.io.IOException;
7f7e0c 22 import java.net.URI;
c140ce 23 import java.util.UUID;
7f7e0c 24
MP 25 public class CASLoginProtocol implements LoginProtocol {
57a6c1 26     private static final Logger logger = Logger.getLogger(CASLoginProtocol.class);
MP 27
7f7e0c 28     public static final String LOGIN_PROTOCOL = "cas";
MP 29
30     public static final String SERVICE_PARAM = "service";
74023a 31     public static final String TARGET_PARAM = "TARGET";
7f7e0c 32     public static final String RENEW_PARAM = "renew";
MP 33     public static final String GATEWAY_PARAM = "gateway";
34     public static final String TICKET_PARAM = "ticket";
35     public static final String FORMAT_PARAM = "format";
36
37     public static final String TICKET_RESPONSE_PARAM = "ticket";
38
39     public static final String SERVICE_TICKET_PREFIX = "ST-";
57a6c1 40     public static final String SESSION_SERVICE_TICKET = "service_ticket";
4a6620 41
MP 42     public static final String LOGOUT_REDIRECT_URI = "CAS_LOGOUT_REDIRECT_URI";
7f7e0c 43
MP 44     protected KeycloakSession session;
45     protected RealmModel realm;
46     protected UriInfo uriInfo;
47     protected HttpHeaders headers;
48     protected EventBuilder event;
49
7124d2 50     public CASLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, EventBuilder event) {
7f7e0c 51         this.session = session;
MP 52         this.realm = realm;
53         this.uriInfo = uriInfo;
54         this.headers = headers;
55         this.event = event;
56     }
57
58     public CASLoginProtocol() {
59     }
60
61     @Override
62     public CASLoginProtocol setSession(KeycloakSession session) {
63         this.session = session;
64         return this;
65     }
66
67     @Override
68     public CASLoginProtocol setRealm(RealmModel realm) {
69         this.realm = realm;
70         return this;
71     }
72
73     @Override
74     public CASLoginProtocol setUriInfo(UriInfo uriInfo) {
75         this.uriInfo = uriInfo;
76         return this;
77     }
78
79     @Override
80     public CASLoginProtocol setHttpHeaders(HttpHeaders headers) {
81         this.headers = headers;
82         return this;
83     }
84
85     @Override
86     public CASLoginProtocol setEventBuilder(EventBuilder event) {
87         this.event = event;
88         return this;
89     }
90
91     @Override
c140ce 92     public Response authenticated(AuthenticationSessionModel authSession, UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
b8d686 93         AuthenticatedClientSessionModel clientSession = clientSessionCtx.getClientSession();
7f7e0c 94
c140ce 95         String service = authSession.getRedirectUri();
7f7e0c 96         //TODO validate service
5570d4 97
c140ce 98         OAuth2Code codeData = new OAuth2Code(UUID.randomUUID(),
MP 99                 Time.currentTime() + userSession.getRealm().getAccessCodeLifespan(),
100                 null, null, authSession.getRedirectUri(), null, null);
101         String code = OAuth2CodeParser.persistCode(session, clientSession, codeData);
102
7f7e0c 103         KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(service);
5570d4 104         uriBuilder.queryParam(TICKET_RESPONSE_PARAM, SERVICE_TICKET_PREFIX + code);
7f7e0c 105
MP 106         URI redirectUri = uriBuilder.build();
107
108         Response.ResponseBuilder location = Response.status(302).location(redirectUri);
109         return location.build();
110     }
111
112     @Override
f75caf 113     public Response sendError(AuthenticationSessionModel authSession, Error error) {
7f7e0c 114         return Response.serverError().entity(error).build();
MP 115     }
116
117     @Override
f75caf 118     public void backchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
57a6c1 119         String logoutUrl = clientSession.getRedirectUri();
MP 120         String serviceTicket = clientSession.getNote(CASLoginProtocol.SESSION_SERVICE_TICKET);
121         //check if session is fully authenticated (i.e. serviceValidate has been called)
122         if (serviceTicket != null && !serviceTicket.isEmpty()) {
123             sendSingleLogoutRequest(logoutUrl, serviceTicket);
124         }
7f7e0c 125         ClientModel client = clientSession.getClient();
019db5 126         new ResourceAdminManager(session).logoutClientSession(realm, client, clientSession);
7f7e0c 127     }
MP 128
57a6c1 129     private void sendSingleLogoutRequest(String logoutUrl, String serviceTicket) {
MP 130         HttpEntity requestEntity = LogoutHelper.buildSingleLogoutRequest(serviceTicket);
131         try {
132             LogoutHelper.postWithRedirect(session, logoutUrl, requestEntity);
133             logger.debug("Sent CAS single logout for service " + logoutUrl);
134         } catch (IOException e) {
135             logger.warn("Failed to call CAS service for logout: " + logoutUrl, e);
136         }
137     }
138
7f7e0c 139     @Override
f75caf 140     public Response frontchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
7f7e0c 141         // todo oidc redirect support
MP 142         throw new RuntimeException("NOT IMPLEMENTED");
143     }
144
145     @Override
146     public Response finishLogout(UserSessionModel userSession) {
4a6620 147         String redirectUri = userSession.getNote(CASLoginProtocol.LOGOUT_REDIRECT_URI);
MP 148
7f7e0c 149         event.event(EventType.LOGOUT);
MP 150         event.user(userSession.getUser()).session(userSession).success();
cbb2f2 151
4a6620 152         if (redirectUri != null) {
cbb2f2 153             return Response.status(302).location(URI.create(redirectUri)).build();
4a6620 154         } else {
cbb2f2 155             LoginFormsProvider infoPage = session.getProvider(LoginFormsProvider.class).setSuccess("Logout successful");
4a6620 156             infoPage.setAttribute("skipLink", true);
cbb2f2 157             return infoPage.createInfoPage();
4a6620 158         }
7f7e0c 159     }
MP 160
161     @Override
f75caf 162     public boolean requireReauthentication(UserSessionModel userSession, AuthenticationSessionModel authSession) {
MP 163         return "true".equals(authSession.getClientNote(CASLoginProtocol.RENEW_PARAM));
7f7e0c 164     }
MP 165
166     @Override
167     public void close() {
168
169     }
170 }