View Javadoc

1   /*
2    * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package edu.internet2.middleware.shibboleth.common.relyingparty.provider;
18  
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.concurrent.locks.Lock;
23  
24  import org.opensaml.saml2.metadata.EntitiesDescriptor;
25  import org.opensaml.saml2.metadata.EntityDescriptor;
26  import org.opensaml.saml2.metadata.provider.MetadataProvider;
27  import org.opensaml.saml2.metadata.provider.MetadataProviderException;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  import org.springframework.context.ApplicationContext;
31  
32  import edu.internet2.middleware.shibboleth.common.config.BaseReloadableService;
33  import edu.internet2.middleware.shibboleth.common.config.relyingparty.RelyingPartyGroup;
34  import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
35  import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfigurationManager;
36  import edu.internet2.middleware.shibboleth.common.service.ServiceException;
37  
38  /**
39   * A relying party manager that uses SAML metadata to lookup information about requested entities. Relying party
40   * configuration information is looked up as follows:
41   * 
42   * If the given entity ID is null, empty, or contains only whitespace the anonymous relying party configuration is
43   * returned. Otherwise, the given relying party entity ID is looked for in the list of registered
44   * {@link RelyingPartyConfiguration}s and if found is returned. If no configuration is registered for the specific
45   * entity ID the entity descriptor for the relying party is located using the {@link MetadataProvider}. The name of
46   * ancestral entities descriptors are then looked up, in ascending order (i.e. the parent entities descriptor, then the
47   * grandparent, great-grandparent, etc.), with the first configuration found being returned. If no configuration is
48   * found once the top of the tree is reached the default configuration is returned.
49   */
50  public class SAMLMDRelyingPartyConfigurationManager extends BaseReloadableService implements
51          RelyingPartyConfigurationManager {
52  
53      /** ID used for anonymous relying party. */
54      public static final String ANONYMOUS_RP_NAME = "anonymous";
55  
56      /** ID used for default relying party. */
57      public static final String DEFAULT_RP_NAME = "default";
58  
59      /** Class logger. */
60      private final Logger log = LoggerFactory.getLogger(SAMLMDRelyingPartyConfigurationManager.class);
61  
62      /** Metadata provider used to lookup information about entities. */
63      private MetadataProvider metadataProvider;
64  
65      /** Registered relying party configurations. */
66      private HashMap<String, RelyingPartyConfiguration> rpConfigs;
67  
68      /** Constructor. */
69      public SAMLMDRelyingPartyConfigurationManager() {
70          super();
71          rpConfigs = new HashMap<String, RelyingPartyConfiguration>();
72      }
73  
74      /** {@inheritDoc} */
75      public RelyingPartyConfiguration getAnonymousRelyingConfiguration() {
76          Lock readLock = getReadWriteLock().readLock();
77          readLock.lock();
78          try {
79              return rpConfigs.get(ANONYMOUS_RP_NAME);
80          } finally {
81              readLock.unlock();
82          }
83      }
84  
85      /** {@inheritDoc} */
86      public RelyingPartyConfiguration getDefaultRelyingPartyConfiguration() {
87          Lock readLock = getReadWriteLock().readLock();
88          readLock.lock();
89          try {
90              return rpConfigs.get(DEFAULT_RP_NAME);
91          } finally {
92              readLock.unlock();
93          }
94      }
95  
96      /**
97       * Gets the metadata provider used to lookup information about entities.
98       * 
99       * @return metadata provider used to lookup information about entities
100      */
101     public MetadataProvider getMetadataProvider() {
102         Lock readLock = getReadWriteLock().readLock();
103         readLock.lock();
104         try {
105             return metadataProvider;
106         } finally {
107             readLock.unlock();
108         }
109     }
110 
111     /**
112      * Sets the metadata provider used to lookup information about entities.
113      * 
114      * @param provider metadata provider used to lookup information about entities
115      */
116     public void setMetadataProvider(MetadataProvider provider) {
117         metadataProvider = provider;
118     }
119 
120     /** {@inheritDoc} */
121     public RelyingPartyConfiguration getRelyingPartyConfiguration(String relyingPartyEntityID) {
122         Lock readLock = getReadWriteLock().readLock();
123         readLock.lock();
124 
125         try {
126             log.debug("Looking up relying party configuration for {}", relyingPartyEntityID);
127             if (rpConfigs.containsKey(relyingPartyEntityID)) {
128                 log.debug("Custom relying party configuration found for {}", relyingPartyEntityID);
129                 return rpConfigs.get(relyingPartyEntityID);
130             }
131 
132             log.debug("No custom relying party configuration found for {}, looking up configuration based on metadata groups.",
133                             relyingPartyEntityID);
134             try {
135                 if (metadataProvider == null) {
136                     log.debug("No metadata provider available, unable to lookup configuration based on entity group");
137                 } else {
138                     EntityDescriptor entityDescriptor = metadataProvider.getEntityDescriptor(relyingPartyEntityID);
139                     if (entityDescriptor != null) {
140                         EntitiesDescriptor entityGroup = (EntitiesDescriptor) entityDescriptor.getParent();
141                         while (entityGroup != null) {
142                             if (rpConfigs.containsKey(entityGroup.getName())) {
143                                 log.debug("Relying party configuration found for {} as member of metadata group {}",
144                                         relyingPartyEntityID, entityGroup.getName());
145                                 return rpConfigs.get(entityGroup.getName());
146                             }
147                             entityGroup = (EntitiesDescriptor) entityGroup.getParent();
148                         }
149                     }
150                 }
151             } catch (MetadataProviderException e) {
152                 log.error("Error fetching metadata for relying party " + relyingPartyEntityID, e);
153             }
154 
155             log.debug("No custom or group-based relying party configuration found for {}. Using default relying party configuration.",
156                     relyingPartyEntityID);
157             return getDefaultRelyingPartyConfiguration();
158         } finally {
159             readLock.unlock();
160         }
161     }
162 
163     /** {@inheritDoc} */
164     public Map<String, RelyingPartyConfiguration> getRelyingPartyConfigurations() {
165         return rpConfigs;
166     }
167 
168     /** {@inheritDoc} */
169     protected void onNewContextCreated(ApplicationContext newServiceContext) throws ServiceException {
170         MetadataProvider oldProvider = metadataProvider;
171         HashMap<String, RelyingPartyConfiguration> oldRpConfigs = rpConfigs;
172         try {
173             String[] relyingPartyGroupNames = newServiceContext.getBeanNamesForType(RelyingPartyGroup.class);
174             RelyingPartyGroup newRpGroup = (RelyingPartyGroup) newServiceContext.getBean(relyingPartyGroupNames[0]);
175 
176             metadataProvider = newRpGroup.getMetadataProvider();
177 
178             HashMap<String, RelyingPartyConfiguration> newRpConfigs = new HashMap<String, RelyingPartyConfiguration>();
179             List<RelyingPartyConfiguration> loadRpConfigs = newRpGroup.getRelyingParties();
180             if (loadRpConfigs != null) {
181                 for (RelyingPartyConfiguration newRpConfig : loadRpConfigs) {
182                     newRpConfigs.put(newRpConfig.getRelyingPartyId(), newRpConfig);
183                     log.debug("Registering configuration for relying party: {}", newRpConfig.getRelyingPartyId());
184                 }
185             }
186             newRpConfigs.put(ANONYMOUS_RP_NAME, newRpGroup.getAnonymousRP());
187             newRpConfigs.put(DEFAULT_RP_NAME, newRpGroup.getDefaultRP());
188             rpConfigs = newRpConfigs;
189 
190         } catch (Exception e) {
191             metadataProvider = oldProvider;
192             rpConfigs = oldRpConfigs;
193             throw new ServiceException(getId() + " configuration is not valid, retaining old configuration", e);
194         }
195     }
196 }