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

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