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

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