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

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