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

Matthias Piepkorn
2018-11-16 89e3d757b9f6893d16e5c3e49fc407aa1a003931
commit | author | age
7f7e0c 1 package org.keycloak.protocol.cas.endpoints;
MP 2
3 import org.jboss.logging.Logger;
4 import org.jboss.resteasy.annotations.cache.NoCache;
5 import org.jboss.resteasy.spi.HttpRequest;
6 import org.keycloak.common.ClientConnection;
7 import org.keycloak.events.Details;
8 import org.keycloak.events.Errors;
9 import org.keycloak.events.EventBuilder;
10 import org.keycloak.events.EventType;
11 import org.keycloak.models.*;
12 import org.keycloak.protocol.cas.CASLoginProtocol;
352436 13 import org.keycloak.protocol.cas.representations.CASErrorCode;
MP 14 import org.keycloak.protocol.cas.utils.CASValidationException;
7f7e0c 15 import org.keycloak.protocol.oidc.utils.RedirectUtils;
MP 16 import org.keycloak.services.managers.AuthenticationManager;
17 import org.keycloak.services.managers.ClientSessionCode;
18
19 import javax.ws.rs.GET;
20 import javax.ws.rs.core.*;
21
22 public class ValidateEndpoint {
57a6c1 23     protected static final Logger logger = Logger.getLogger(ValidateEndpoint.class);
7f7e0c 24
MP 25     private static final String RESPONSE_OK = "yes\n";
26     private static final String RESPONSE_FAILED = "no\n";
27
28     @Context
29     protected KeycloakSession session;
30
31     @Context
32     protected ClientConnection clientConnection;
33
34     @Context
35     protected HttpRequest request;
36
37     @Context
38     protected HttpHeaders headers;
39
40     protected RealmModel realm;
41     protected EventBuilder event;
42     protected ClientModel client;
f75caf 43     protected AuthenticatedClientSessionModel clientSession;
7f7e0c 44
MP 45     public ValidateEndpoint(RealmModel realm, EventBuilder event) {
46         this.realm = realm;
47         this.event = event;
48     }
49
50     @GET
51     @NoCache
52     public Response build() {
dee145 53         MultivaluedMap<String, String> params = session.getContext().getUri().getQueryParameters();
7f7e0c 54         String service = params.getFirst(CASLoginProtocol.SERVICE_PARAM);
MP 55         String ticket = params.getFirst(CASLoginProtocol.TICKET_PARAM);
7124d2 56         boolean renew = params.containsKey(CASLoginProtocol.RENEW_PARAM);
7f7e0c 57
MP 58         event.event(EventType.CODE_TO_TOKEN);
59
60         try {
61             checkSsl();
62             checkRealm();
63             checkClient(service);
64
65             checkTicket(ticket, renew);
66
67             event.success();
68             return successResponse();
352436 69         } catch (CASValidationException e) {
7f7e0c 70             return errorResponse(e);
MP 71         }
72     }
73
74     protected Response successResponse() {
75         return Response.ok(RESPONSE_OK).type(MediaType.TEXT_PLAIN).build();
76     }
77
352436 78     protected Response errorResponse(CASValidationException e) {
MP 79         return Response.status(e.getStatus()).entity(RESPONSE_FAILED).type(MediaType.TEXT_PLAIN).build();
7f7e0c 80     }
MP 81
82     private void checkSsl() {
dee145 83         if (!session.getContext().getUri().getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
352436 84             throw new CASValidationException(CASErrorCode.INVALID_REQUEST, "HTTPS required", Response.Status.FORBIDDEN);
7f7e0c 85         }
MP 86     }
87
88     private void checkRealm() {
89         if (!realm.isEnabled()) {
352436 90             throw new CASValidationException(CASErrorCode.INTERNAL_ERROR, "Realm not enabled", Response.Status.FORBIDDEN);
7f7e0c 91         }
MP 92     }
93
94     private void checkClient(String service) {
95         if (service == null) {
96             event.error(Errors.INVALID_REQUEST);
352436 97             throw new CASValidationException(CASErrorCode.INVALID_REQUEST, "Missing parameter: " + CASLoginProtocol.SERVICE_PARAM, Response.Status.BAD_REQUEST);
7f7e0c 98         }
MP 99
100         client = realm.getClients().stream()
101                 .filter(c -> CASLoginProtocol.LOGIN_PROTOCOL.equals(c.getProtocol()))
dee145 102                 .filter(c -> RedirectUtils.verifyRedirectUri(session.getContext().getUri(), service, realm, c) != null)
7f7e0c 103                 .findFirst().orElse(null);
MP 104         if (client == null) {
105             event.error(Errors.CLIENT_NOT_FOUND);
352436 106             throw new CASValidationException(CASErrorCode.INVALID_SERVICE, "Client not found", Response.Status.BAD_REQUEST);
7f7e0c 107         }
MP 108
109         if (!client.isEnabled()) {
110             event.error(Errors.CLIENT_DISABLED);
352436 111             throw new CASValidationException(CASErrorCode.INVALID_SERVICE, "Client disabled", Response.Status.BAD_REQUEST);
7f7e0c 112         }
MP 113
114         event.client(client.getClientId());
115
116         session.getContext().setClient(client);
117     }
118
119     private void checkTicket(String ticket, boolean requireReauth) {
352436 120         if (ticket == null) {
7f7e0c 121             event.error(Errors.INVALID_CODE);
352436 122             throw new CASValidationException(CASErrorCode.INVALID_REQUEST, "Missing parameter: " + CASLoginProtocol.TICKET_PARAM, Response.Status.BAD_REQUEST);
MP 123         }
124         if (!ticket.startsWith(CASLoginProtocol.SERVICE_TICKET_PREFIX)) {
125             event.error(Errors.INVALID_CODE);
126             throw new CASValidationException(CASErrorCode.INVALID_TICKET_SPEC, "Malformed service ticket", Response.Status.BAD_REQUEST);
7f7e0c 127         }
MP 128
129         String code = ticket.substring(CASLoginProtocol.SERVICE_TICKET_PREFIX.length());
130
f75caf 131         String[] parts = code.split("\\.");
MP 132         if (parts.length == 4) {
133             event.detail(Details.CODE_ID, parts[2]);
134         }
135
6638b8 136         ClientSessionCode.ParseResult<AuthenticatedClientSessionModel> parseResult = ClientSessionCode.parseResult(code, null, session, realm, client, event, AuthenticatedClientSessionModel.class);
f75caf 137         if (parseResult.isAuthSessionNotFound() || parseResult.isIllegalHash()) {
7f7e0c 138             event.error(Errors.INVALID_CODE);
f75caf 139
MP 140             // Attempt to use same code twice should invalidate existing clientSession
141             AuthenticatedClientSessionModel clientSession = parseResult.getClientSession();
142             if (clientSession != null) {
6638b8 143                 clientSession.detachFromUserSession();
7f7e0c 144             }
f75caf 145
352436 146             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Code not valid", Response.Status.BAD_REQUEST);
7f7e0c 147         }
MP 148
149         clientSession = parseResult.getClientSession();
150
6638b8 151         if (parseResult.isExpiredToken()) {
MP 152             event.error(Errors.EXPIRED_CODE);
153             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Code is expired", Response.Status.BAD_REQUEST);
7f7e0c 154         }
MP 155
57a6c1 156         clientSession.setNote(CASLoginProtocol.SESSION_SERVICE_TICKET, ticket);
7f7e0c 157
7124d2 158         if (requireReauth && AuthenticationManager.isSSOAuthentication(clientSession)) {
MP 159             event.error(Errors.SESSION_EXPIRED);
160             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Interactive authentication was requested but not performed", Response.Status.BAD_REQUEST);
161         }
162
7f7e0c 163         UserSessionModel userSession = clientSession.getUserSession();
MP 164
165         if (userSession == null) {
166             event.error(Errors.USER_SESSION_NOT_FOUND);
352436 167             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "User session not found", Response.Status.BAD_REQUEST);
7f7e0c 168         }
MP 169
170         UserModel user = userSession.getUser();
171         if (user == null) {
172             event.error(Errors.USER_NOT_FOUND);
352436 173             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "User not found", Response.Status.BAD_REQUEST);
7f7e0c 174         }
MP 175         if (!user.isEnabled()) {
176             event.error(Errors.USER_DISABLED);
352436 177             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "User disabled", Response.Status.BAD_REQUEST);
7f7e0c 178         }
MP 179
180         event.user(userSession.getUser());
181         event.session(userSession.getId());
182
183         if (!client.getClientId().equals(clientSession.getClient().getClientId())) {
184             event.error(Errors.INVALID_CODE);
352436 185             throw new CASValidationException(CASErrorCode.INVALID_SERVICE, "Auth error", Response.Status.BAD_REQUEST);
7f7e0c 186         }
MP 187
188         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
189             event.error(Errors.USER_SESSION_NOT_FOUND);
352436 190             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Session not active", Response.Status.BAD_REQUEST);
7f7e0c 191         }
MP 192
193     }
194 }