View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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   * Basic in-memory cache for available {@link SAML2Client} instances.
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 }