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

Erlend Hamnaberg
2018-11-26 74023ad339616936c5a2415f3b0347858c38df18
commit | author | age
74023a 1 package org.keycloak.protocol.cas.endpoints;
EH 2
3 import org.jboss.logging.Logger;
4 import org.jboss.resteasy.spi.HttpRequest;
5 import org.keycloak.common.ClientConnection;
6 import org.keycloak.events.Details;
7 import org.keycloak.events.Errors;
8 import org.keycloak.events.EventBuilder;
9 import org.keycloak.models.*;
10 import org.keycloak.protocol.ProtocolMapper;
11 import org.keycloak.protocol.cas.CASLoginProtocol;
12 import org.keycloak.protocol.cas.mappers.CASAttributeMapper;
13 import org.keycloak.protocol.cas.representations.CASErrorCode;
14 import org.keycloak.protocol.cas.utils.CASValidationException;
15 import org.keycloak.protocol.oidc.utils.RedirectUtils;
16 import org.keycloak.services.managers.AuthenticationManager;
17 import org.keycloak.services.managers.ClientSessionCode;
18 import org.keycloak.services.util.DefaultClientSessionContext;
19
20 import javax.ws.rs.core.Context;
21 import javax.ws.rs.core.HttpHeaders;
22 import javax.ws.rs.core.Response;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Set;
26
27 public abstract class AbstractValidateEndpoint {
28     protected final Logger logger = Logger.getLogger(getClass());
29     @Context
30     protected KeycloakSession session;
31     @Context
32     protected ClientConnection clientConnection;
33     @Context
34     protected HttpRequest request;
35     @Context
36     protected HttpHeaders headers;
37     protected RealmModel realm;
38     protected EventBuilder event;
39     protected ClientModel client;
40     protected AuthenticatedClientSessionModel clientSession;
41
42     public AbstractValidateEndpoint(RealmModel realm, EventBuilder event) {
43         this.realm = realm;
44         this.event = event;
45     }
46
47     protected void checkSsl() {
48         if (!session.getContext().getUri().getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
49             throw new CASValidationException(CASErrorCode.INVALID_REQUEST, "HTTPS required", Response.Status.FORBIDDEN);
50         }
51     }
52
53     protected void checkRealm() {
54         if (!realm.isEnabled()) {
55             throw new CASValidationException(CASErrorCode.INTERNAL_ERROR, "Realm not enabled", Response.Status.FORBIDDEN);
56         }
57     }
58
59     protected void checkClient(String service) {
60         if (service == null) {
61             event.error(Errors.INVALID_REQUEST);
62             throw new CASValidationException(CASErrorCode.INVALID_REQUEST, "Missing parameter: " + CASLoginProtocol.SERVICE_PARAM, Response.Status.BAD_REQUEST);
63         }
64
65         client = realm.getClients().stream()
66                 .filter(c -> CASLoginProtocol.LOGIN_PROTOCOL.equals(c.getProtocol()))
67                 .filter(c -> RedirectUtils.verifyRedirectUri(session.getContext().getUri(), service, realm, c) != null)
68                 .findFirst().orElse(null);
69         if (client == null) {
70             event.error(Errors.CLIENT_NOT_FOUND);
71             throw new CASValidationException(CASErrorCode.INVALID_SERVICE, "Client not found", Response.Status.BAD_REQUEST);
72         }
73
74         if (!client.isEnabled()) {
75             event.error(Errors.CLIENT_DISABLED);
76             throw new CASValidationException(CASErrorCode.INVALID_SERVICE, "Client disabled", Response.Status.BAD_REQUEST);
77         }
78
79         event.client(client.getClientId());
80
81         session.getContext().setClient(client);
82     }
83
84     protected void checkTicket(String ticket, boolean requireReauth) {
85         if (ticket == null) {
86             event.error(Errors.INVALID_CODE);
87             throw new CASValidationException(CASErrorCode.INVALID_REQUEST, "Missing parameter: " + CASLoginProtocol.TICKET_PARAM, Response.Status.BAD_REQUEST);
88         }
89         if (!ticket.startsWith(CASLoginProtocol.SERVICE_TICKET_PREFIX)) {
90             event.error(Errors.INVALID_CODE);
91             throw new CASValidationException(CASErrorCode.INVALID_TICKET_SPEC, "Malformed service ticket", Response.Status.BAD_REQUEST);
92         }
93
94         String code = ticket.substring(CASLoginProtocol.SERVICE_TICKET_PREFIX.length());
95
96         String[] parts = code.split("\\.");
97         if (parts.length == 4) {
98             event.detail(Details.CODE_ID, parts[2]);
99         }
100
101         ClientSessionCode.ParseResult<AuthenticatedClientSessionModel> parseResult = ClientSessionCode.parseResult(code, null, session, realm, client, event, AuthenticatedClientSessionModel.class);
102         if (parseResult.isAuthSessionNotFound() || parseResult.isIllegalHash()) {
103             event.error(Errors.INVALID_CODE);
104
105             // Attempt to use same code twice should invalidate existing clientSession
106             AuthenticatedClientSessionModel clientSession = parseResult.getClientSession();
107             if (clientSession != null) {
108                 clientSession.detachFromUserSession();
109             }
110
111             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Code not valid", Response.Status.BAD_REQUEST);
112         }
113
114         clientSession = parseResult.getClientSession();
115
116         if (parseResult.isExpiredToken()) {
117             event.error(Errors.EXPIRED_CODE);
118             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Code is expired", Response.Status.BAD_REQUEST);
119         }
120
121         clientSession.setNote(CASLoginProtocol.SESSION_SERVICE_TICKET, ticket);
122
123         if (requireReauth && AuthenticationManager.isSSOAuthentication(clientSession)) {
124             event.error(Errors.SESSION_EXPIRED);
125             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Interactive authentication was requested but not performed", Response.Status.BAD_REQUEST);
126         }
127
128         UserSessionModel userSession = clientSession.getUserSession();
129
130         if (userSession == null) {
131             event.error(Errors.USER_SESSION_NOT_FOUND);
132             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "User session not found", Response.Status.BAD_REQUEST);
133         }
134
135         UserModel user = userSession.getUser();
136         if (user == null) {
137             event.error(Errors.USER_NOT_FOUND);
138             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "User not found", Response.Status.BAD_REQUEST);
139         }
140         if (!user.isEnabled()) {
141             event.error(Errors.USER_DISABLED);
142             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "User disabled", Response.Status.BAD_REQUEST);
143         }
144
145         event.user(userSession.getUser());
146         event.session(userSession.getId());
147
148         if (!client.getClientId().equals(clientSession.getClient().getClientId())) {
149             event.error(Errors.INVALID_CODE);
150             throw new CASValidationException(CASErrorCode.INVALID_SERVICE, "Auth error", Response.Status.BAD_REQUEST);
151         }
152
153         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
154             event.error(Errors.USER_SESSION_NOT_FOUND);
155             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Session not active", Response.Status.BAD_REQUEST);
156         }
157     }
158
159     protected Map<String, Object> getUserAttributes() {
160         UserSessionModel userSession = clientSession.getUserSession();
161         // CAS protocol does not support scopes, so pass null scopeParam
162         ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, null);
163
164         Set<ProtocolMapperModel> mappings = clientSessionCtx.getProtocolMappers();
165         KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
166         Map<String, Object> attributes = new HashMap<>();
167         for (ProtocolMapperModel mapping : mappings) {
168             ProtocolMapper mapper = (ProtocolMapper) sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
169             if (mapper instanceof CASAttributeMapper) {
170                 ((CASAttributeMapper) mapper).setAttribute(attributes, mapping, userSession, session, clientSessionCtx);
171             }
172         }
173         return attributes;
174     }
175 }