CallbackHandlerRegistry.java
001 /*
002  *
003  * Licensed to the Apache Software Foundation (ASF) under one
004  * or more contributor license agreements.  See the NOTICE file
005  * distributed with this work for additional information
006  * regarding copyright ownership.  The ASF licenses this file
007  * to you under the Apache License, Version 2.0 (the
008  * "License"); you may not use this file except in compliance
009  * with the License.  You may obtain a copy of the License at
010  *
011  *   http://www.apache.org/licenses/LICENSE-2.0
012  *
013  * Unless required by applicable law or agreed to in writing,
014  * software distributed under the License is distributed on an
015  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016  * KIND, either express or implied.  See the License for the
017  * specific language governing permissions and limitations
018  * under the License.
019  *
020  */
021 package org.apache.qpid.client.security;
022 
023 import org.apache.qpid.util.FileUtils;
024 
025 import org.slf4j.Logger;
026 import org.slf4j.LoggerFactory;
027 
028 import java.io.IOException;
029 import java.io.InputStream;
030 import java.util.Enumeration;
031 import java.util.HashMap;
032 import java.util.Map;
033 import java.util.Properties;
034 
035 /**
036  * CallbackHandlerRegistry is a registry for call back handlers for user authentication and interaction during user
037  * authentication. It is capable of reading its configuration from a properties file containing call back handler
038  * implementing class names for different SASL mechanism names. Instantiating this registry also has the effect of
039  * configuring and registering the SASL client factory implementations using {@link DynamicSaslRegistrar}.
040  *
041  <p/>The callback configuration should be specified in a properties file, refered to by the System property
042  * "amp.callbackhandler.properties". The format of the properties file is:
043  *
044  <p/><pre>
045  * CallbackHanlder.mechanism=fully.qualified.class.name
046  </pre>
047  *
048  <p/>Where mechanism is an IANA-registered mechanism name and the fully qualified class name refers to a
049  * class that implements org.apache.qpid.client.security.AMQCallbackHanlder and provides a call back handler for the
050  * specified mechanism.
051  *
052  <p><table id="crc"><caption>CRC Card</caption>
053  <tr><th> Responsibilities <th> Collaborations
054  <tr><td> Parse callback properties.
055  <tr><td> Provide mapping from SASL mechanisms to callback implementations.
056  </table>
057  */
058 public class CallbackHandlerRegistry
059 {
060     private static final Logger _logger = LoggerFactory.getLogger(CallbackHandlerRegistry.class);
061 
062     /** The name of the system property that holds the name of the callback handler properties file. */
063     private static final String FILE_PROPERTY = "amq.callbackhandler.properties";
064 
065     /** The default name of the callback handler properties resource. */
066     public static final String DEFAULT_RESOURCE_NAME = "org/apache/qpid/client/security/CallbackHandlerRegistry.properties";
067 
068     /** A static reference to the singleton instance of this registry. */
069     private static CallbackHandlerRegistry _instance = new CallbackHandlerRegistry();
070 
071     /** Holds a map from SASL mechanism names to call back handlers. */
072     private Map<String, Class> _mechanismToHandlerClassMap = new HashMap<String, Class>();
073 
074     /** Holds a space delimited list of mechanisms that callback handlers exist for. */
075     private String _mechanisms;
076 
077     /**
078      * Gets the singleton instance of this registry.
079      *
080      @return The singleton instance of this registry.
081      */
082     public static CallbackHandlerRegistry getInstance()
083     {
084         return _instance;
085     }
086 
087     /**
088      * Gets the callback handler class for a given SASL mechanism name.
089      *
090      @param mechanism The SASL mechanism name.
091      *
092      @return The callback handler class for the mechanism, or null if none is configured for that mechanism.
093      */
094     public Class getCallbackHandlerClass(String mechanism)
095     {
096         return (Class_mechanismToHandlerClassMap.get(mechanism);
097     }
098 
099     /**
100      * Gets a space delimited list of supported SASL mechanisms.
101      *
102      @return A space delimited list of supported SASL mechanisms.
103      */
104     public String getMechanisms()
105     {
106         return _mechanisms;
107     }
108 
109     /**
110      * Creates the call back handler registry from its configuration resource or file. This also has the side effect
111      * of configuring and registering the SASL client factory implementations using {@link DynamicSaslRegistrar}.
112      */
113     private CallbackHandlerRegistry()
114     {
115         // Register any configured SASL client factories.
116         DynamicSaslRegistrar.registerSaslProviders();
117 
118         String filename = System.getProperty(FILE_PROPERTY);
119         InputStream is =
120             FileUtils.openFileOrDefaultResource(filename, DEFAULT_RESOURCE_NAME,
121                 CallbackHandlerRegistry.class.getClassLoader());
122 
123         try
124         {
125             Properties props = new Properties();
126             props.load(is);
127             parseProperties(props);
128             _logger.info("Callback handlers available for SASL mechanisms: " + _mechanisms);
129         }
130         catch (IOException e)
131         {
132             _logger.error("Error reading properties: " + e, e);
133         }
134         finally
135         {
136             if (is != null)
137             {
138                 try
139                 {
140                     is.close();
141 
142                 }
143                 catch (IOException e)
144                 {
145                     _logger.error("Unable to close properties stream: " + e, e);
146                 }
147             }
148         }
149     }
150 
151     /*private InputStream openPropertiesInputStream(String filename)
152     {
153         boolean useDefault = true;
154         InputStream is = null;
155         if (filename != null)
156         {
157             try
158             {
159                 is = new BufferedInputStream(new FileInputStream(new File(filename)));
160                 useDefault = false;
161             }
162             catch (FileNotFoundException e)
163             {
164                 _logger.error("Unable to read from file " + filename + ": " + e, e);
165             }
166         }
167 
168         if (useDefault)
169         {
170             is = CallbackHandlerRegistry.class.getResourceAsStream(DEFAULT_RESOURCE_NAME);
171         }
172 
173         return is;
174     }*/
175 
176     /**
177      * Scans the specified properties as a mapping from IANA registered SASL mechanism to call back handler
178      * implementations, that provide the necessary call back handling for obtaining user log in credentials
179      * during authentication for the specified mechanism, and builds a map from mechanism names to handler
180      * classes.
181      *
182      @param props
183      */
184     private void parseProperties(Properties props)
185     {
186         Enumeration e = props.propertyNames();
187         while (e.hasMoreElements())
188         {
189             String propertyName = (Stringe.nextElement();
190             int period = propertyName.indexOf(".");
191             if (period < 0)
192             {
193                 _logger.warn("Unable to parse property " + propertyName + " when configuring SASL providers");
194 
195                 continue;
196             }
197 
198             String mechanism = propertyName.substring(period + 1);
199             String className = props.getProperty(propertyName);
200             Class clazz = null;
201             try
202             {
203                 clazz = Class.forName(className);
204                 if (!AMQCallbackHandler.class.isAssignableFrom(clazz))
205                 {
206                     _logger.warn("SASL provider " + clazz + " does not implement " + AMQCallbackHandler.class
207                         ". Skipping");
208 
209                     continue;
210                 }
211 
212                 _mechanismToHandlerClassMap.put(mechanism, clazz);
213                 if (_mechanisms == null)
214                 {
215                     _mechanisms = mechanism;
216                 }
217                 else
218                 {
219                     // one time cost
220                     _mechanisms = _mechanisms + " " + mechanism;
221                 }
222             }
223             catch (ClassNotFoundException ex)
224             {
225                 _logger.warn("Unable to load class " + className + ". Skipping that SASL provider");
226 
227                 continue;
228             }
229         }
230     }
231 }