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.keycloak.dom.saml.v1.protocol.SAML11ResponseType;
4 import org.keycloak.events.EventBuilder;
5 import org.keycloak.events.EventType;
6 import org.keycloak.models.RealmModel;
7 import org.keycloak.models.UserModel;
8 import org.keycloak.protocol.cas.CASLoginProtocol;
9 import org.keycloak.protocol.cas.representations.CASErrorCode;
10 import org.keycloak.protocol.cas.representations.SamlResponseHelper;
11 import org.keycloak.protocol.cas.utils.CASValidationException;
12 import org.keycloak.services.Urls;
13 import org.xml.sax.InputSource;
14
15 import javax.ws.rs.Consumes;
16 import javax.ws.rs.POST;
17 import javax.ws.rs.Produces;
18 import javax.ws.rs.core.MultivaluedMap;
19 import javax.ws.rs.core.Response;
20 import javax.xml.namespace.NamespaceContext;
21 import javax.xml.xpath.XPath;
22 import javax.xml.xpath.XPathExpression;
23 import javax.xml.xpath.XPathExpressionException;
24 import javax.xml.xpath.XPathFactory;
25 import java.io.StringReader;
26 import java.util.*;
27
28 import static org.keycloak.protocol.cas.CASLoginProtocol.TARGET_PARAM;
29
30 public class SamlValidateEndpoint extends AbstractValidateEndpoint {
31     public SamlValidateEndpoint(RealmModel realm, EventBuilder event) {
32         super(realm, event.event(EventType.CODE_TO_TOKEN));
33     }
34
35     @POST
36     @Consumes("text/xml;charset=utf-8")
37     @Produces("text/xml;charset=utf-8")
38     public Response validate(String input) {
39         MultivaluedMap<String, String> queryParams = request.getUri().getQueryParameters();
40         try {
41             String soapAction = Optional.ofNullable(request.getHttpHeaders().getHeaderString("SOAPAction")).map(s -> s.trim().replace("\"", "")).orElse("");
42             if (!soapAction.equals("http://www.oasis-open.org/committees/security")) {
43                 throw new CASValidationException(CASErrorCode.INTERNAL_ERROR, "Not a validation request", Response.Status.BAD_REQUEST);
44             }
45
46             String service = queryParams.getFirst(TARGET_PARAM);
47             boolean renew = queryParams.containsKey(CASLoginProtocol.RENEW_PARAM);
48
49             checkRealm();
50             checkSsl();
51             checkClient(service);
52             String issuer = Urls.realmIssuer(request.getUri().getBaseUri(), realm.getName());
53             String ticket = getTicket(input);
54
55             checkTicket(ticket, renew);
56             UserModel user = clientSession.getUserSession().getUser();
57
58             Map<String, Object> attributes = getUserAttributes();
59
60             SAML11ResponseType response = SamlResponseHelper.successResponse(issuer, user.getUsername(), attributes);
61
62             return Response.ok(SamlResponseHelper.soap(response)).build();
63
64         } catch (CASValidationException ex) {
65             logger.warnf("Invalid SAML1.1 token %s", ex.getErrorDescription());
66
67             SAML11ResponseType response = SamlResponseHelper.errorResponse(ex);
68             return Response.ok().entity(SamlResponseHelper.soap(response)).build();
69         }
70     }
71
72     private String getTicket(String input) {
73         try {
74             XPath xPath = XPathFactory.newInstance().newXPath();
75             xPath.setNamespaceContext(new MapNamespaceContext(Collections.singletonMap("samlp", "urn:oasis:names:tc:SAML:1.0:protocol")));
76
77             XPathExpression expression = xPath.compile("//samlp:AssertionArtifact/text()");
78
79             return expression.evaluate(new InputSource(new StringReader(input)));
80         } catch (XPathExpressionException ex) {
81             throw new CASValidationException(CASErrorCode.INVALID_TICKET, ex.getMessage(), Response.Status.BAD_REQUEST);
82         }
83     }
84
85     private static class MapNamespaceContext implements NamespaceContext {
86         Map<String, String> map;
87
88         private MapNamespaceContext(Map<String, String> map) {
89             this.map = map;
90         }
91
92         @Override
93         public String getNamespaceURI(String s) {
94             return map.get(s);
95         }
96
97         @Override
98         public String getPrefix(String s) {
99             return map.entrySet().stream().filter(e -> e.getValue().equals(s)).findFirst().map(Map.Entry::getKey).orElse(null);
100         }
101
102         @Override
103         public Iterator<String> getPrefixes(String s) {
104             return map.keySet().iterator();
105         }
106     }
107 }