1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.logic.saml2;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.util.ArrayList;
26 import java.util.Base64;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.Optional;
30 import org.apache.commons.lang3.StringUtils;
31 import org.apache.cxf.helpers.IOUtils;
32 import org.apache.syncope.common.lib.to.Item;
33 import org.apache.syncope.common.lib.to.SAML2SP4UIIdPTO;
34 import org.apache.syncope.common.lib.types.SAML2BindingType;
35 import org.apache.syncope.core.persistence.api.entity.SAML2SP4UIIdP;
36 import org.opensaml.saml.common.xml.SAMLConstants;
37 import org.opensaml.saml.saml2.core.NameID;
38 import org.opensaml.saml.saml2.metadata.EntityDescriptor;
39 import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver;
40 import org.pac4j.saml.client.SAML2Client;
41 import org.pac4j.saml.config.SAML2Configuration;
42 import org.pac4j.saml.metadata.SAML2IdentityProviderMetadataResolver;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45 import org.springframework.core.io.ByteArrayResource;
46
47
48
49
50 public class SAML2ClientCache {
51
52 protected static final Logger LOG = LoggerFactory.getLogger(SAML2ClientCache.class);
53
54 protected static Path METADATA_PATH;
55
56 static {
57 try {
58 METADATA_PATH = Files.createTempDirectory("saml2sp4ui-").toAbsolutePath();
59 } catch (IOException e) {
60 LOG.error("Could not create a temp directory to store metadata files", e);
61 }
62 }
63
64 public static Optional<String> getSPMetadataPath(final String spEntityID) {
65 String entityIDPath = StringUtils.replaceChars(
66 StringUtils.removeStart(StringUtils.removeStart(spEntityID, "https://"), "http://"), ":/", "__");
67 return Optional.ofNullable(METADATA_PATH).map(path -> path.resolve(entityIDPath).toAbsolutePath().toString());
68 }
69
70 public static SAML2SP4UIIdPTO importMetadata(
71 final InputStream metadata, final SAML2Configuration cfg) throws IOException {
72
73 cfg.setIdentityProviderMetadataResource(new ByteArrayResource(IOUtils.readBytesFromStream(metadata)));
74 SAML2IdentityProviderMetadataResolver metadataResolver = new SAML2IdentityProviderMetadataResolver(cfg);
75 metadataResolver.init();
76
77 String entityId = metadataResolver.getEntityId();
78
79 SAML2SP4UIIdPTO idpTO = new SAML2SP4UIIdPTO();
80 idpTO.setEntityID(entityId);
81 idpTO.setName(entityId);
82
83 EntityDescriptor entityDescriptor = (EntityDescriptor) metadataResolver.getEntityDescriptorElement();
84 entityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleSignOnServices().forEach(sso -> {
85 if (idpTO.getBindingType() == null) {
86 if (SAML2BindingType.POST.getUri().equals(sso.getBinding())) {
87 idpTO.setBindingType(SAML2BindingType.POST);
88 } else if (SAML2BindingType.REDIRECT.getUri().equals(sso.getBinding())) {
89 idpTO.setBindingType(SAML2BindingType.REDIRECT);
90 }
91 }
92 });
93 if (idpTO.getBindingType() == null) {
94 throw new IllegalArgumentException("Neither POST nor REDIRECT artifacts supported by " + entityId);
95 }
96
97 cfg.setAuthnRequestBindingType(idpTO.getBindingType().getUri());
98 cfg.setResponseBindingType(SAML2BindingType.POST.getUri());
99 cfg.setSpLogoutRequestBindingType(idpTO.getBindingType().getUri());
100 cfg.setSpLogoutResponseBindingType(idpTO.getBindingType().getUri());
101
102 entityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleLogoutServices().stream().
103 filter(slo -> SAML2BindingType.POST.getUri().equals(slo.getBinding())
104 || SAML2BindingType.REDIRECT.getUri().equals(slo.getBinding())).
105 findFirst().
106 ifPresent(slo -> idpTO.setLogoutSupported(true));
107
108 idpTO.setMetadata(Base64.getEncoder().encodeToString(metadataResolver.getMetadata().getBytes()));
109
110 Item connObjectKeyItem = new Item();
111 connObjectKeyItem.setIntAttrName("username");
112 connObjectKeyItem.setExtAttrName(NameID.DEFAULT_ELEMENT_LOCAL_NAME);
113 idpTO.setConnObjectKeyItem(connObjectKeyItem);
114
115 return idpTO;
116 }
117
118 protected final List<SAML2Client> cache = Collections.synchronizedList(new ArrayList<>());
119
120 public Optional<SAML2Client> get(final String idpEntityID, final String spEntityID) {
121 return cache.stream().filter(c -> idpEntityID.equals(c.getIdentityProviderResolvedEntityId())
122 && spEntityID.equals(c.getConfiguration().getServiceProviderEntityId())).findFirst();
123 }
124
125 public SAML2Client add(
126 final SAML2SP4UIIdP idp, final SAML2Configuration cfg, final String spEntityID, final String callbackUrl) {
127
128 cfg.setIdentityProviderMetadataResource((new ByteArrayResource(idp.getMetadata())));
129 cfg.setServiceProviderEntityId(spEntityID);
130 getSPMetadataPath(spEntityID).ifPresent(cfg::setServiceProviderMetadataResourceFilepath);
131
132 SAML2Client saml2Client = new SAML2Client(cfg);
133 saml2Client.setCallbackUrlResolver(new NoParameterCallbackUrlResolver());
134 saml2Client.setCallbackUrl(callbackUrl);
135 saml2Client.init();
136
137 cache.add(saml2Client);
138 return saml2Client;
139 }
140
141 public boolean removeAll(final String idpEntityID) {
142 return cache.removeIf(c -> idpEntityID.equals(c.getIdentityProviderResolvedEntityId()));
143 }
144 }