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

Matthias Piepkorn
2017-01-27 2e3b4dea824af204c49ee3310896faf6d107a8c8
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.OAuthErrorException;
7 import org.keycloak.common.ClientConnection;
8 import org.keycloak.events.Details;
9 import org.keycloak.events.Errors;
10 import org.keycloak.events.EventBuilder;
11 import org.keycloak.events.EventType;
12 import org.keycloak.models.*;
13 import org.keycloak.protocol.cas.CASLoginProtocol;
14 import org.keycloak.protocol.oidc.utils.RedirectUtils;
15 import org.keycloak.services.ErrorPageException;
16 import org.keycloak.services.ErrorResponseException;
17 import org.keycloak.services.managers.AuthenticationManager;
18 import org.keycloak.services.managers.ClientSessionCode;
19 import org.keycloak.services.messages.Messages;
20
21 import javax.ws.rs.GET;
22 import javax.ws.rs.core.*;
23
24 public class ValidateEndpoint {
25     protected static final Logger logger = Logger.getLogger(org.keycloak.protocol.oidc.endpoints.LogoutEndpoint.class);
26
27     private static final String RESPONSE_OK = "yes\n";
28     private static final String RESPONSE_FAILED = "no\n";
29
30     @Context
31     protected KeycloakSession session;
32
33     @Context
34     protected ClientConnection clientConnection;
35
36     @Context
37     protected HttpRequest request;
38
39     @Context
40     protected HttpHeaders headers;
41
42     @Context
43     protected UriInfo uriInfo;
44
45     protected RealmModel realm;
46     protected EventBuilder event;
47     protected ClientModel client;
48     protected ClientSessionModel clientSession;
49
50     public ValidateEndpoint(RealmModel realm, EventBuilder event) {
51         this.realm = realm;
52         this.event = event;
53     }
54
55     @GET
56     @NoCache
57     public Response build() {
58         MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
59         String service = params.getFirst(CASLoginProtocol.SERVICE_PARAM);
60         String ticket = params.getFirst(CASLoginProtocol.TICKET_PARAM);
61         boolean renew = "true".equalsIgnoreCase(params.getFirst(CASLoginProtocol.RENEW_PARAM));
62
63         event.event(EventType.CODE_TO_TOKEN);
64
65         try {
66             checkSsl();
67             checkRealm();
68             checkClient(service);
69
70             checkTicket(ticket, renew);
71
72             event.success();
73             return successResponse();
74         } catch (ErrorResponseException e) {
75             return errorResponse(e);
76         }
77     }
78
79     protected Response successResponse() {
80         return Response.ok(RESPONSE_OK).type(MediaType.TEXT_PLAIN).build();
81     }
82
83     protected Response errorResponse(ErrorResponseException e) {
84         return Response.status(Response.Status.UNAUTHORIZED).entity(RESPONSE_FAILED).type(MediaType.TEXT_PLAIN).build();
85     }
86
87     private void checkSsl() {
88         if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
89             throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "HTTPS required", Response.Status.FORBIDDEN);
90         }
91     }
92
93     private void checkRealm() {
94         if (!realm.isEnabled()) {
95             throw new ErrorResponseException("access_denied", "Realm not enabled", Response.Status.FORBIDDEN);
96         }
97     }
98
99     private void checkClient(String service) {
100         if (service == null) {
101             event.error(Errors.INVALID_REQUEST);
102             throw new ErrorPageException(session, Messages.MISSING_PARAMETER, CASLoginProtocol.SERVICE_PARAM);
103         }
104
105         client = realm.getClients().stream()
106                 .filter(c -> CASLoginProtocol.LOGIN_PROTOCOL.equals(c.getProtocol()))
107                 .filter(c -> RedirectUtils.verifyRedirectUri(uriInfo, service, realm, c) != null)
108                 .findFirst().orElse(null);
109         if (client == null) {
110             event.error(Errors.CLIENT_NOT_FOUND);
111             throw new ErrorPageException(session, Messages.CLIENT_NOT_FOUND);
112         }
113
114         if (!client.isEnabled()) {
115             event.error(Errors.CLIENT_DISABLED);
116             throw new ErrorPageException(session, Messages.CLIENT_DISABLED);
117         }
118
119         if (client.isBearerOnly()) {
120             event.error(Errors.NOT_ALLOWED);
121             throw new ErrorPageException(session, Messages.BEARER_ONLY);
122         }
123
124         event.client(client.getClientId());
125
126         session.getContext().setClient(client);
127     }
128
129     private void checkTicket(String ticket, boolean requireReauth) {
130         if (ticket == null || !ticket.startsWith(CASLoginProtocol.SERVICE_TICKET_PREFIX)) {
131             event.error(Errors.INVALID_CODE);
132             throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Missing or invalid parameter: " + CASLoginProtocol.TICKET_PARAM, Response.Status.BAD_REQUEST);
133         }
134
135         String code = ticket.substring(CASLoginProtocol.SERVICE_TICKET_PREFIX.length());
136
137         ClientSessionCode.ParseResult parseResult = ClientSessionCode.parseResult(code, session, realm);
138         if (parseResult.isClientSessionNotFound() || parseResult.isIllegalHash()) {
139             String[] parts = code.split("\\.");
140             if (parts.length == 2) {
141                 event.detail(Details.CODE_ID, parts[1]);
142             }
143             event.error(Errors.INVALID_CODE);
144             if (parseResult.getClientSession() != null) {
145                 session.sessions().removeClientSession(realm, parseResult.getClientSession());
146             }
147             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code not valid", Response.Status.BAD_REQUEST);
148         }
149
150         clientSession = parseResult.getClientSession();
151         event.detail(Details.CODE_ID, clientSession.getId());
152
153         if (!parseResult.getCode().isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) {
154             event.error(Errors.INVALID_CODE);
155             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST);
156         }
157
158         parseResult.getCode().setAction(null);
159
160         UserSessionModel userSession = clientSession.getUserSession();
161
162         if (userSession == null) {
163             event.error(Errors.USER_SESSION_NOT_FOUND);
164             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "User session not found", Response.Status.BAD_REQUEST);
165         }
166
167         UserModel user = userSession.getUser();
168         if (user == null) {
169             event.error(Errors.USER_NOT_FOUND);
170             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "User not found", Response.Status.BAD_REQUEST);
171         }
172         if (!user.isEnabled()) {
173             event.error(Errors.USER_DISABLED);
174             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "User disabled", Response.Status.BAD_REQUEST);
175         }
176
177         event.user(userSession.getUser());
178         event.session(userSession.getId());
179
180         if (!client.getClientId().equals(clientSession.getClient().getClientId())) {
181             event.error(Errors.INVALID_CODE);
182             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Auth error", Response.Status.BAD_REQUEST);
183         }
184
185         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
186             event.error(Errors.USER_SESSION_NOT_FOUND);
187             throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Session not active", Response.Status.BAD_REQUEST);
188         }
189
190     }
191 }