Ripped out the shib-specific "test" name identifier format.
[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.SAMLNameIdentifier;
31 import org.w3c.dom.Element;
32 import org.xml.sax.InputSource;
33
34 import edu.internet2.middleware.shibboleth.common.provider.SharedMemoryShibHandle;
35 import edu.internet2.middleware.shibboleth.xml.Parser;
36
37 /**
38  * Facility for managing mappings from SAML Name Identifiers to local {@link LocalPrincipal}objects. Mappings are
39  * registered by Name Identifier format and can be associated with a <code>String</code> id and recovered based on the
40  * same.
41  * 
42  * @author Walter Hoehn
43  * @see NameIdentifierMapping
44  */
45 public class NameMapper {
46
47         private static Logger log = Logger.getLogger(NameMapper.class.getName());
48         private Map byFormat = new HashMap();
49         private Map byId = new HashMap();
50         private static Map registeredMappingTypes = Collections.synchronizedMap(new HashMap());
51         /** true if mappings have been added */
52         protected boolean initialized = false;
53         /** Mapping to use if no other mappings have been added */
54         protected SharedMemoryShibHandle defaultMapping;
55
56         // Preload aliases for bundled mappings
57         static {
58                 try {
59                         registeredMappingTypes.put("CryptoHandleGenerator", Class
60                                         .forName("edu.internet2.middleware.shibboleth.common.provider.CryptoShibHandle"));
61
62                         registeredMappingTypes.put("SharedMemoryShibHandle", Class
63                                         .forName("edu.internet2.middleware.shibboleth.common.provider.SharedMemoryShibHandle"));
64
65                         registeredMappingTypes.put("Principal", Class
66                                         .forName("edu.internet2.middleware.shibboleth.common.provider.PrincipalNameIdentifier"));
67
68                 } catch (ClassNotFoundException e) {
69                         log.error("Unable to pre-register Name mapping implementation types.");
70                 }
71         }
72
73         /**
74          * Constructs the name mapper and loads a default name mapping.
75          */
76         public NameMapper() {
77
78                 try {
79                         // Load the default mapping
80                         String rawConfig = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
81                                         + "<NameMapping xmlns=\"urn:mace:shibboleth:namemapper:1.0\" format=\"urn:mace:shibboleth:1.0:nameIdentifier\""
82                                         + "             handleTTL=\"1800\"/>";
83                         Parser.DOMParser parser = new Parser.DOMParser(false);
84                         parser.parse(new InputSource(new StringReader(rawConfig)));
85                         defaultMapping = new SharedMemoryShibHandle(parser.getDocument().getDocumentElement());
86
87                 } catch (Exception e) {
88                         log.error("Unable to register default Name Identifier Mapping: " + e);
89                         initialize();
90                 }
91         }
92
93         protected void initialize() {
94
95                 initialized = true;
96                 defaultMapping = null;
97         }
98
99         /**
100          * Constructs a {@link NameIdentifierMapping}based on XML configuration data and adds it to this {@link NameMapper},
101          * registering it according to its format.
102          * 
103          * @param e
104          *            An XML representation of a {@link NameIdentifierMapping}
105          * @throws NameIdentifierMappingException
106          *             If the mapping could not be constructed according to the supplied configuration
107          */
108         public void addNameMapping(Element e) throws NameIdentifierMappingException {
109
110                 if (!e.getLocalName().equals("NameMapping")) { throw new IllegalArgumentException(); }
111
112                 log.info("Found Name Mapping. Loading...");
113
114                 String type = ((Element) e).getAttribute("type");
115                 String implementation = ((Element) e).getAttribute("class");
116                 if (type != null && (!type.equals("")) && implementation != null && (!implementation.equals(""))) {
117                         log.error("Name Mapping has both a \"type\" and a \"class\" attribute. Only \"type\" will take effect.");
118                 }
119
120                 if (type != null && (!type.equals(""))) {
121
122                         Class registeredImplementation = (Class) registeredMappingTypes.get(type);
123                         if (registeredImplementation == null) {
124                                 log.error("Name Mapping refers to an unregistered implementation type.");
125                                 throw new NameIdentifierMappingException("Invalid mapping implementation specified.");
126                         }
127
128                         log.debug("Found type (" + type + ") registered with an implementation class of ("
129                                         + registeredImplementation.getName() + ").");
130                         addNameMapping(loadNameIdentifierMapping(registeredImplementation, e));
131
132                 } else if (implementation != null && (!implementation.equals(""))) {
133
134                         try {
135                                 Class implementorClass = Class.forName(implementation);
136                                 addNameMapping(loadNameIdentifierMapping(implementorClass, e));
137
138                         } catch (ClassNotFoundException cnfe) {
139                                 log.error("Name Mapping refers to an implementation class that cannot be loaded: " + cnfe);
140                                 throw new NameIdentifierMappingException("Invalid mapping implementation specified.");
141                         }
142
143                 } else {
144                         log.error("Name Mapping requires either a \"type\" or a \"class\" attribute.");
145                         throw new NameIdentifierMappingException("No mapping implementation specified.");
146                 }
147
148         }
149
150         /**
151          * Adds a {@link NameIdentifierMapping}to this name mapper, registering it according to its format.
152          * 
153          * @param mapping
154          *            the mapping to add
155          */
156         public void addNameMapping(NameIdentifierMapping mapping) {
157
158                 initialize();
159
160                 if (byFormat.containsKey(mapping.getNameIdentifierFormat())) {
161                         log.error("Attempted to register multiple Name Mappings with the same format.  Skipping duplicates...");
162                         return;
163                 }
164                 byFormat.put(mapping.getNameIdentifierFormat(), mapping);
165
166                 if (mapping.getId() != null && !mapping.getId().equals("")) {
167                         byId.put(mapping.getId(), mapping);
168                 }
169
170         }
171
172         /**
173          * Returns the {@link NameIdentifierMapping}registered for a given format.
174          * 
175          * @param format
176          *            the registered format
177          * @return the mapping or <code>null</code> if no mapping is registered for the given format
178          */
179         public NameIdentifierMapping getNameIdentifierMapping(URI format) {
180
181                 if (!initialized) { return defaultMapping; }
182
183                 return (NameIdentifierMapping) byFormat.get(format);
184         }
185
186         /**
187          * Returns the <code>NameIdentifierMapping</code> registered for a given id
188          * 
189          * @param id
190          *            the registered id
191          * @return the mapping or <tt>null</tt> if no mapping is registered for the given id
192          */
193         public NameIdentifierMapping getNameIdentifierMappingById(String id) {
194
195                 if (id == null || id.equals("")) {
196                         if (!initialized) { return defaultMapping; }
197
198                         if (byFormat.size() == 1) {
199                                 Iterator values = byFormat.values().iterator();
200                                 Object mapping = values.next();
201                                 return (NameIdentifierMapping) mapping;
202                         }
203                 }
204
205                 return (NameIdentifierMapping) byId.get(id);
206         }
207
208         protected NameIdentifierMapping loadNameIdentifierMapping(Class implementation, Element config)
209                         throws NameIdentifierMappingException {
210
211                 try {
212                         Class[] params = new Class[]{Element.class};
213                         Constructor implementorConstructor = implementation.getConstructor(params);
214                         Object[] args = new Object[]{config};
215                         log.debug("Initializing Name Identifier Mapping of type (" + implementation.getName() + ").");
216                         return (NameIdentifierMapping) implementorConstructor.newInstance(args);
217
218                 } catch (NoSuchMethodException nsme) {
219                         log.error("Failed to instantiate a Name Identifier Mapping: NameIdentifierMapping "
220                                         + "implementation must contain a constructor that accepts an Element object for "
221                                         + "configuration data.");
222                         throw new NameIdentifierMappingException("Failed to instantiate a Name Identifier Mapping.");
223
224                 } catch (Exception e) {
225                         log.error("Failed to instantiate a Name Identifier Mapping: " + e + ":" + e.getCause());
226                         throw new NameIdentifierMappingException("Failed to instantiate a Name Identifier Mapping: " + e);
227
228                 }
229
230         }
231
232         /**
233          * Maps a SAML Name Identifier to a local principal using the appropriate registered mapping.
234          * 
235          * @param nameId
236          *            the SAML Name Identifier that should be converted
237          * @param sProv
238          *            the provider initiating the request
239          * @param idProv
240          *            the provider handling the request
241          * @return the local principal
242          * @throws NameIdentifierMappingException
243          *             If the {@link NameMapper}encounters an internal error
244          * @throws InvalidNameIdentifierException
245          *             If the {@link SAMLNameIdentifier}contains invalid data
246          */
247         public Principal getPrincipal(SAMLNameIdentifier nameId, ServiceProvider sProv, IdentityProvider idProv)
248                         throws NameIdentifierMappingException, InvalidNameIdentifierException {
249
250                 NameIdentifierMapping mapping = null;
251                 log.debug("Name Identifier format: (" + nameId.getFormat() + ").");
252                 try {
253                         mapping = getNameIdentifierMapping(new URI(nameId.getFormat()));
254                 } catch (URISyntaxException e) {
255                         log.error("Invalid Name Identifier format.");
256                 }
257                 if (mapping == null) { throw new NameIdentifierMappingException("Name Identifier format not registered."); }
258                 return mapping.getPrincipal(nameId, sProv, idProv);
259         }
260
261         /**
262          * Maps a local principal to a SAML Name Identifier using the mapping registered under a given id.
263          * 
264          * @param id
265          *            the id under which the effective <code>NameIdentifierMapping</code> is registered
266          * @param principal
267          *            the principal to map
268          * @param sProv
269          *            the provider initiating the request
270          * @param idProv
271          *            the provider handling the request
272          * @return
273          * @throws NameIdentifierMappingException
274          *             If the <code>NameMapper</code> encounters an internal error
275          */
276         public SAMLNameIdentifier getNameIdentifier(String id, LocalPrincipal principal, ServiceProvider sProv,
277                         IdentityProvider idProv) throws NameIdentifierMappingException {
278
279                 NameIdentifierMapping mapping = getNameIdentifierMappingById(id);
280
281                 if (mapping == null) { throw new NameIdentifierMappingException("Name Identifier id not registered."); }
282                 return mapping.getNameIdentifier(principal, sProv, idProv);
283         }
284
285         /**
286          * Cleanup resources that won't be released when this object is garbage-collected
287          */
288         public void destroy() {
289
290                 Iterator mappingIterator = byFormat.values().iterator();
291                 while (mappingIterator.hasNext()) {
292                         ((NameIdentifierMapping) mappingIterator.next()).destroy();
293                 }
294         }
295 }