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

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