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

Jacek Kowalski
2023-01-24 b73d96162e93146ebc8d7e76cc3bbc57ea3b399e
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;
ea9555 25 import java.util.stream.Collectors;
74023a 26
EH 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
ea9555 65         client = realm.getClientsStream()
74023a 66                 .filter(c -> CASLoginProtocol.LOGIN_PROTOCOL.equals(c.getProtocol()))
019db5 67                 .filter(c -> RedirectUtils.verifyRedirectUri(session, service, c) != null)
74023a 68                 .findFirst().orElse(null);
EH 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
c140ce 96         OAuth2CodeParser.ParseResult parseResult = OAuth2CodeParser.parseCode(session, code, realm, event);
MP 97         if (parseResult.isIllegalCode()) {
74023a 98             event.error(Errors.INVALID_CODE);
EH 99
100             // Attempt to use same code twice should invalidate existing clientSession
101             AuthenticatedClientSessionModel clientSession = parseResult.getClientSession();
102             if (clientSession != null) {
103                 clientSession.detachFromUserSession();
104             }
105
106             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Code not valid", Response.Status.BAD_REQUEST);
107         }
108
109         clientSession = parseResult.getClientSession();
110
c140ce 111         if (parseResult.isExpiredCode()) {
74023a 112             event.error(Errors.EXPIRED_CODE);
EH 113             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Code is expired", Response.Status.BAD_REQUEST);
114         }
115
116         clientSession.setNote(CASLoginProtocol.SESSION_SERVICE_TICKET, ticket);
117
118         if (requireReauth && AuthenticationManager.isSSOAuthentication(clientSession)) {
119             event.error(Errors.SESSION_EXPIRED);
120             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Interactive authentication was requested but not performed", Response.Status.BAD_REQUEST);
121         }
122
123         UserSessionModel userSession = clientSession.getUserSession();
124
125         if (userSession == null) {
126             event.error(Errors.USER_SESSION_NOT_FOUND);
127             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "User session not found", Response.Status.BAD_REQUEST);
128         }
129
130         UserModel user = userSession.getUser();
131         if (user == null) {
132             event.error(Errors.USER_NOT_FOUND);
133             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "User not found", Response.Status.BAD_REQUEST);
134         }
135         if (!user.isEnabled()) {
136             event.error(Errors.USER_DISABLED);
137             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "User disabled", Response.Status.BAD_REQUEST);
138         }
139
140         event.user(userSession.getUser());
141         event.session(userSession.getId());
142
143         if (!client.getClientId().equals(clientSession.getClient().getClientId())) {
144             event.error(Errors.INVALID_CODE);
145             throw new CASValidationException(CASErrorCode.INVALID_SERVICE, "Auth error", Response.Status.BAD_REQUEST);
146         }
147
148         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
149             event.error(Errors.USER_SESSION_NOT_FOUND);
150             throw new CASValidationException(CASErrorCode.INVALID_TICKET, "Session not active", Response.Status.BAD_REQUEST);
151         }
152     }
153
154     protected Map<String, Object> getUserAttributes() {
155         UserSessionModel userSession = clientSession.getUserSession();
156         // CAS protocol does not support scopes, so pass null scopeParam
8379a3 157         ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, null, session);
74023a 158
ea9555 159         Set<ProtocolMapperModel> mappings = clientSessionCtx.getProtocolMappersStream().collect(Collectors.toSet());
74023a 160         KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
EH 161         Map<String, Object> attributes = new HashMap<>();
162         for (ProtocolMapperModel mapping : mappings) {
163             ProtocolMapper mapper = (ProtocolMapper) sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
164             if (mapper instanceof CASAttributeMapper) {
165                 ((CASAttributeMapper) mapper).setAttribute(attributes, mapping, userSession, session, clientSessionCtx);
166             }
167         }
168         return attributes;
169     }
170 }