Make eclipse metadata reflect current JAXP config.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / common / NameMapper.java
1 /*
2  * Copyright [2005] [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;
18
19 import java.io.StringReader;
20 import java.lang.reflect.Constructor;
21 import java.net.URI;
22 import java.net.URISyntaxException;
23 import java.security.Principal;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.Map;
28
29 import org.apache.log4j.Logger;
30 import org.opensaml.SAMLException;
31 import org.opensaml.SAMLNameIdentifier;
32 import org.w3c.dom.Element;
33 import org.xml.sax.InputSource;
34
35 import edu.internet2.middleware.shibboleth.common.provider.SharedMemoryShibHandle;
36 import edu.internet2.middleware.shibboleth.xml.Parser;
37
38 /**
39  * Facility for managing mappings from SAML Name Identifiers to local {@link LocalPrincipal}objects. Mappings are
40  * registered by Name Identifier format and can be associated with a <code>String</code> id and recovered based on the
41  * same.
42  * 
43  * @author Walter Hoehn
44  * @see NameIdentifierMapping
45  */
46 public class NameMapper {
47
48         private static Logger log = Logger.getLogger(NameMapper.class.getName());
49         private Map byFormat = new HashMap();
50         private Map byId = new HashMap();
51         private static Map registeredMappingTypes = Collections.synchronizedMap(new HashMap());
52         /** true if mappings have been added */
53         protected boolean initialized = false;
54         /** Mapping to use if no other mappings have been added */
55         protected SharedMemoryShibHandle defaultMapping;
56
57         // Preload aliases for bundled mappings
58         static {
59                 try {
60                         registeredMappingTypes.put("CryptoHandleGenerator", Class
61                                         .forName("edu.internet2.middleware.shibboleth.common.provider.CryptoShibHandle"));
62
63                         registeredMappingTypes.put("SharedMemoryShibHandle", Class
64                                         .forName("edu.internet2.middleware.shibboleth.common.provider.SharedMemoryShibHandle"));
65
66                         registeredMappingTypes.put("Principal", Class
67                                         .forName("edu.internet2.middleware.shibboleth.common.provider.PrincipalNameIdentifier"));
68
69                 } catch (ClassNotFoundException e) {
70                         log.error("Unable to pre-register Name mapping implementation types.");
71                 }
72         }
73
74         /**
75          * Constructs the name mapper and loads a default name mapping.
76          */
77         public NameMapper() {
78
79                 try {
80                         // Load the default mapping
81                         String rawConfig = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
82                                         + "<NameMapping xmlns=\"urn:mace:shibboleth:namemapper:1.0\" format=\"urn:mace:shibboleth:1.0:nameIdentifier\""
83                                         + "             handleTTL=\"1800\"/>";
84                         Parser.DOMParser parser = new Parser.DOMParser(false);
85                         parser.parse(new InputSource(new StringReader(rawConfig)));
86                         defaultMapping = new SharedMemoryShibHandle(parser.getDocument().getDocumentElement());
87
88                 } catch (Exception e) {
89                         log.error("Unable to register default Name Identifier Mapping: " + e);
90                         initialize();
91                 }
92         }
93
94         protected void initialize() {
95
96                 initialized = true;
97                 defaultMapping = null;
98         }
99
100         /**
101          * Constructs a {@link NameIdentifierMapping}based on XML configuration data and adds it to this {@link NameMapper},
102          * registering it according to its format.
103          * 
104          * @param e
105          *            An XML representation of a {@link NameIdentifierMapping}
106          * @throws NameIdentifierMappingException
107          *             If the mapping could not be constructed according to the supplied configuration
108          */
109         public void addNameMapping(Element e) throws NameIdentifierMappingException {
110
111                 if (!e.getLocalName().equals("NameMapping")) { throw new IllegalArgumentException(); }
112
113                 log.info("Found Name Mapping. Loading...");
114
115                 String type = ((Element) e).getAttribute("type");
116                 String implementation = ((Element) e).getAttribute("class");
117                 if (type != null && (!type.equals("")) && implementation != null && (!implementation.equals(""))) {
118                         log.error("Name Mapping has both a \"type\" and a \"class\" attribute. Only \"type\" will take effect.");
119                 }
120
121                 if (type != null && (!type.equals(""))) {
122
123                         Class registeredImplementation = (Class) registeredMappingTypes.get(type);
124                         if (registeredImplementation == null) {
125                                 log.error("Name Mapping refers to an unregistered implementation type.");
126                                 throw new NameIdentifierMappingException("Invalid mapping implementation specified.");
127                         }
128
129                         log.debug("Found type (" + type + ") registered with an implementation class of ("
130                                         + registeredImplementation.getName() + ").");
131                         addNameMapping(loadNameIdentifierMapping(registeredImplementation, e));
132
133                 } else if (implementation != null && (!implementation.equals(""))) {
134
135                         try {
136                                 Class implementorClass = Class.forName(implementation);
137                                 addNameMapping(loadNameIdentifierMapping(implementorClass, e));
138
139                         } catch (ClassNotFoundException cnfe) {
140                                 log.error("Name Mapping refers to an implementation class that cannot be loaded: " + cnfe);
141                                 throw new NameIdentifierMappingException("Invalid mapping implementation specified.");
142                         }
143
144                 } else {
145                         log.error("Name Mapping requires either a \"type\" or a \"class\" attribute.");
146                         throw new NameIdentifierMappingException("No mapping implementation specified.");
147                 }
148
149         }
150
151         /**
152          * Adds a {@link NameIdentifierMapping}to this name mapper, registering it according to its format.
153          * 
154          * @param mapping
155          *            the mapping to add
156          */
157         public void addNameMapping(NameIdentifierMapping mapping) {
158
159                 initialize();
160
161                 if (byFormat.containsKey(mapping.getNameIdentifierFormat())) {
162                         log.error("Attempted to register multiple Name Mappings with the same format.  Skipping duplicates...");
163                         return;
164                 }
165                 byFormat.put(mapping.getNameIdentifierFormat(), mapping);
166
167                 if (mapping.getId() != null && !mapping.getId().equals("")) {
168                         byId.put(mapping.getId(), mapping);
169                 }
170
171         }
172
173         /**
174          * Returns the {@link NameIdentifierMapping}registered for a given format.
175          * 
176          * @param format
177          *            the registered format
178          * @return the mapping or <code>null</code> if no mapping is registered for the given format
179          */
180         public NameIdentifierMapping getNameIdentifierMapping(URI format) {
181
182                 if (format.toString().equals("urn:mace:shibboleth:test:nameIdentifier")) { return new TestNameIdentifierMapping(); }
183
184                 if (!initialized) { return defaultMapping; }
185
186                 return (NameIdentifierMapping) byFormat.get(format);
187         }
188
189         /**
190          * Returns the <code>NameIdentifierMapping</code> registered for a given id
191          * 
192          * @param id
193          *            the registered id
194          * @return the mapping or <tt>null</tt> if no mapping is registered for the given id
195          */
196         public NameIdentifierMapping getNameIdentifierMappingById(String id) {
197
198                 if (id == null || id.equals("")) {
199                         if (!initialized) { return defaultMapping; }
200
201                         if (byFormat.size() == 1) {
202                                 Iterator values = byFormat.values().iterator();
203                                 Object mapping = values.next();
204                                 return (NameIdentifierMapping) mapping;
205                         }
206                 }
207
208                 return (NameIdentifierMapping) byId.get(id);
209         }
210
211         protected NameIdentifierMapping loadNameIdentifierMapping(Class implementation, Element config)
212                         throws NameIdentifierMappingException {
213
214                 try {
215                         Class[] params = new Class[]{Element.class};
216                         Constructor implementorConstructor = implementation.getConstructor(params);
217                         Object[] args = new Object[]{config};
218                         log.debug("Initializing Name Identifier Mapping of type (" + implementation.getName() + ").");
219                         return (NameIdentifierMapping) implementorConstructor.newInstance(args);
220
221                 } catch (NoSuchMethodException nsme) {
222                         log.error("Failed to instantiate a Name Identifier Mapping: NameIdentifierMapping "
223                                         + "implementation must contain a constructor that accepts an Element object for "
224                                         + "configuration data.");
225                         throw new NameIdentifierMappingException("Failed to instantiate a Name Identifier Mapping.");
226
227                 } catch (Exception e) {
228                         log.error("Failed to instantiate a Name Identifier Mapping: " + e + ":" + e.getCause());
229                         throw new NameIdentifierMappingException("Failed to instantiate a Name Identifier Mapping: " + e);
230
231                 }
232
233         }
234
235         /**
236          * Maps a SAML Name Identifier to a local principal using the appropriate registered mapping.
237          * 
238          * @param nameId
239          *            the SAML Name Identifier that should be converted
240          * @param sProv
241          *            the provider initiating the request
242          * @param idProv
243          *            the provider handling the request
244          * @return the local principal
245          * @throws NameIdentifierMappingException
246          *             If the {@link NameMapper}encounters an internal error
247          * @throws InvalidNameIdentifierException
248          *             If the {@link SAMLNameIdentifier}contains invalid data
249          */
250         public Principal getPrincipal(SAMLNameIdentifier nameId, ServiceProvider sProv, IdentityProvider idProv)
251                         throws NameIdentifierMappingException, InvalidNameIdentifierException {
252
253                 NameIdentifierMapping mapping = null;
254                 log.debug("Name Identifier format: (" + nameId.getFormat() + ").");
255                 try {
256                         mapping = getNameIdentifierMapping(new URI(nameId.getFormat()));
257                 } catch (URISyntaxException e) {
258                         log.error("Invalid Name Identifier format.");
259                 }
260                 if (mapping == null) { throw new NameIdentifierMappingException("Name Identifier format not registered."); }
261                 return mapping.getPrincipal(nameId, sProv, idProv);
262         }
263
264         /**
265          * Maps a local principal to a SAML Name Identifier using the mapping registered under a given id.
266          * 
267          * @param id
268          *            the id under which the effective <code>NameIdentifierMapping</code> is registered
269          * @param principal
270          *            the principal to map
271          * @param sProv
272          *            the provider initiating the request
273          * @param idProv
274          *            the provider handling the request
275          * @return
276          * @throws NameIdentifierMappingException
277          *             If the <code>NameMapper</code> encounters an internal error
278          */
279         public SAMLNameIdentifier getNameIdentifier(String id, LocalPrincipal principal, ServiceProvider sProv,
280                         IdentityProvider idProv) throws NameIdentifierMappingException {
281
282                 NameIdentifierMapping mapping = getNameIdentifierMappingById(id);
283
284                 if (mapping == null) { throw new NameIdentifierMappingException("Name Identifier id not registered."); }
285                 return mapping.getNameIdentifier(principal, sProv, idProv);
286         }
287
288         /**
289          * <code>NameIdentifierMapping</code> implement that always maps to the same principal name. Used for testing.
290          */
291         public class TestNameIdentifierMapping implements NameIdentifierMapping {
292
293                 private TestNameIdentifierMapping() {
294
295                 // Constructor to prevent others from creating this class
296                 }
297
298                 /*
299                  * (non-Javadoc)
300                  * 
301                  * @see edu.internet2.middleware.shibboleth.common.NameIdentifierMapping#getNameIdentifierFormat()
302                  */
303                 public URI getNameIdentifierFormat() {
304
305                         try {
306                                 return new URI("urn:mace:shibboleth:test:nameIdentifier");
307                         } catch (URISyntaxException e) {
308                                 log.error("Name Mapping \"format\" is not a valid URI: " + e);
309                                 throw new RuntimeException("Internal error: Encountered an error generating a standard URI.");
310                         }
311                 }
312
313                 /*
314                  * (non-Javadoc)
315                  * 
316                  * @see edu.internet2.middleware.shibboleth.common.NameIdentifierMapping#getPrincipal(org.opensaml.SAMLNameIdentifier,
317                  *      edu.internet2.middleware.shibboleth.common.ServiceProvider,
318                  *      edu.internet2.middleware.shibboleth.common.IdentityProvider)
319                  */
320                 public Principal getPrincipal(SAMLNameIdentifier nameId, ServiceProvider sProv, IdentityProvider idProv)
321                                 throws NameIdentifierMappingException, InvalidNameIdentifierException {
322
323                         log.info("Request references built-in test principal.");
324
325                         if (idProv.getProviderId() == null || !idProv.getProviderId().equals(nameId.getNameQualifier())) {
326                                 log.error("The name qualifier (" + nameId.getNameQualifier()
327                                                 + ") for the referenced subject is not valid for this identity provider.");
328                                 throw new NameIdentifierMappingException("The name qualifier (" + nameId.getNameQualifier()
329                                                 + ") for the referenced subject is not valid for this identity provider.");
330                         }
331
332                         return new LocalPrincipal("test-handle");
333                 }
334
335                 /*
336                  * (non-Javadoc)
337                  * 
338                  * @see edu.internet2.middleware.shibboleth.common.NameIdentifierMapping#destroy()
339                  */
340                 public void destroy() {
341
342                 // Nothing to do
343                 }
344
345                 /*
346                  * (non-Javadoc)
347                  * 
348                  * @see edu.internet2.middleware.shibboleth.common.NameIdentifierMapping#getId()
349                  */
350                 public String getId() {
351
352                         return null;
353                 }
354
355                 /*
356                  * (non-Javadoc)
357                  * 
358                  * @see edu.internet2.middleware.shibboleth.common.NameIdentifierMapping#getNameIdentifierName(edu.internet2.middleware.shibboleth.common.LocalPrincipal,
359                  *      edu.internet2.middleware.shibboleth.common.ServiceProvider,
360                  *      edu.internet2.middleware.shibboleth.common.IdentityProvider)
361                  */
362                 public SAMLNameIdentifier getNameIdentifier(LocalPrincipal principal, ServiceProvider sProv,
363                                 IdentityProvider idProv) throws NameIdentifierMappingException {
364
365                         try {
366                                 return new SAMLNameIdentifier("test-handle", idProv.getProviderId(), getNameIdentifierFormat()
367                                                 .toString());
368                         } catch (SAMLException e) {
369                                 throw new NameIdentifierMappingException("Unable to generate Name Identifier: " + e);
370                         }
371                 }
372         }
373
374         /**
375          * Cleanup resources that won't be released when this object is garbage-collected
376          */
377         public void destroy() {
378
379                 Iterator mappingIterator = byFormat.values().iterator();
380                 while (mappingIterator.hasNext()) {
381                         ((NameIdentifierMapping) mappingIterator.next()).destroy();
382                 }
383         }
384 }