PrincipalDatabaseAuthenticationManager.java
001 /*
002  *  Licensed to the Apache Software Foundation (ASF) under one
003  *  or more contributor license agreements.  See the NOTICE file
004  *  distributed with this work for additional information
005  *  regarding copyright ownership.  The ASF licenses this file
006  *  to you under the Apache License, Version 2.0 (the
007  *  "License"); you may not use this file except in compliance
008  *  with the License.  You may obtain a copy of the License at
009  *
010  *    http://www.apache.org/licenses/LICENSE-2.0
011  *
012  *  Unless required by applicable law or agreed to in writing,
013  *  software distributed under the License is distributed on an
014  *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015  *  KIND, either express or implied.  See the License for the
016  *  specific language governing permissions and limitations
017  *  under the License.    
018  *
019  
020  */
021 package org.apache.qpid.server.security.auth.manager;
022 
023 import org.apache.log4j.Logger;
024 import org.apache.commons.configuration.Configuration;
025 import org.apache.commons.configuration.ConfigurationException;
026 import org.apache.qpid.server.configuration.VirtualHostConfiguration;
027 import org.apache.qpid.server.registry.ApplicationRegistry;
028 import org.apache.qpid.server.security.auth.manager.AuthenticationManager;
029 import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
030 import org.apache.qpid.server.security.auth.sasl.JCAProvider;
031 import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser;
032 import org.apache.qpid.server.security.auth.AuthenticationResult;
033 
034 import javax.security.auth.callback.CallbackHandler;
035 import javax.security.sasl.SaslServerFactory;
036 import javax.security.sasl.SaslServer;
037 import javax.security.sasl.SaslException;
038 import javax.security.sasl.Sasl;
039 import java.util.Map;
040 import java.util.HashMap;
041 import java.util.TreeMap;
042 import java.security.Security;
043 
044 public class PrincipalDatabaseAuthenticationManager implements AuthenticationManager
045 {
046     private static final Logger _logger = Logger.getLogger(PrincipalDatabaseAuthenticationManager.class);
047 
048     /** The list of mechanisms, in the order in which they are configured (i.e. preferred order) */
049     private String _mechanisms;
050 
051     /** Maps from the mechanism to the callback handler to use for handling those requests */
052     private Map<String, CallbackHandler> _callbackHandlerMap = new HashMap<String, CallbackHandler>();
053 
054     /**
055      * Maps from the mechanism to the properties used to initialise the server. See the method Sasl.createSaslServer for
056      * details of the use of these properties. This map is populated during initialisation of each provider.
057      */
058     private Map<String, Map<String, ?>> _serverCreationProperties = new HashMap<String, Map<String, ?>>();
059 
060     private AuthenticationManager _default = null;
061     /** The name for the required SASL Server mechanisms */
062     public static final String PROVIDER_NAME= "AMQSASLProvider-Server";
063 
064     public PrincipalDatabaseAuthenticationManager(String name, VirtualHostConfiguration hostConfigthrows Exception
065     {
066         _logger.info("Initialising " (name == null "Default" "'" + name + "'")
067                      " PrincipleDatabase authentication manager.");
068 
069         // Fixme This should be done per Vhost but allowing global hack isn't right but ...
070         // required as authentication is done before Vhost selection
071 
072         Map<String, Class<? extends SaslServerFactory>> providerMap = new TreeMap<String, Class<? extends SaslServerFactory>>();
073 
074 
075         if (name == null || hostConfig == null)
076         {
077             initialiseAuthenticationMechanisms(providerMap, ApplicationRegistry.getInstance().getDatabaseManager().getDatabases());
078         }
079         else
080         {
081             String databaseName = hostConfig.getAuthenticationDatabase();
082 
083             if (databaseName == null)
084             {
085 
086                 _default = ApplicationRegistry.getInstance().getAuthenticationManager();
087                 return;
088             }
089             else
090             {
091                 PrincipalDatabase database = ApplicationRegistry.getInstance().getDatabaseManager().getDatabases().get(databaseName);
092 
093                 if (database == null)
094                 {
095                     throw new ConfigurationException("Requested database:" + databaseName + " was not found");
096                 }
097 
098                 initialiseAuthenticationMechanisms(providerMap, database);
099             }
100         }
101 
102         if (providerMap.size() 0)
103         {
104             // Ensure we are used before the defaults
105             if (Security.insertProviderAt(new JCAProvider(PROVIDER_NAME, providerMap)1== -1)
106             {
107                 _logger.error("Unable to load custom SASL providers. Qpid custom SASL authenticators unavailable.");
108             }
109             else
110             {
111                 _logger.info("Additional SASL providers successfully registered.");
112             }
113 
114         }
115         else
116         {
117             _logger.warn("No additional SASL providers registered.");
118         }
119 
120     }
121 
122 
123     private void initialiseAuthenticationMechanisms(Map<String, Class<? extends SaslServerFactory>> providerMap, Map<String, PrincipalDatabase> databasesthrows Exception
124     {
125         if (databases.size() 1)
126         {
127             _logger.warn("More than one principle database provided currently authentication mechanism will override each other.");
128         }
129 
130         for (Map.Entry<String, PrincipalDatabase> entry : databases.entrySet())
131         {
132             // fixme As the database now provide the mechanisms they support, they will ...
133             // overwrite each other in the map. There should only be one database per vhost.
134             // But currently we must have authentication before vhost definition.
135             initialiseAuthenticationMechanisms(providerMap, entry.getValue());
136         }
137     }
138 
139     private void initialiseAuthenticationMechanisms(Map<String, Class<? extends SaslServerFactory>> providerMap, PrincipalDatabase databasethrows Exception
140     {
141         if (database == null || database.getMechanisms().size() == 0)
142         {
143             _logger.warn("No Database or no mechanisms to initialise authentication");
144             return;
145         }
146 
147         for (Map.Entry<String, AuthenticationProviderInitialiser> mechanism : database.getMechanisms().entrySet())
148         {
149             initialiseAuthenticationMechanism(mechanism.getKey(), mechanism.getValue(), providerMap);
150         }
151     }
152 
153     private void initialiseAuthenticationMechanism(String mechanism, AuthenticationProviderInitialiser initialiser,
154                                                    Map<String, Class<? extends SaslServerFactory>> providerMap)
155             throws Exception
156     {
157         if (_mechanisms == null)
158         {
159             _mechanisms = mechanism;
160         }
161         else
162         {
163             // simple append should be fine since the number of mechanisms is small and this is a one time initialisation
164             _mechanisms = _mechanisms + " " + mechanism;
165         }
166         _callbackHandlerMap.put(mechanism, initialiser.getCallbackHandler());
167         _serverCreationProperties.put(mechanism, initialiser.getProperties());
168         Class<? extends SaslServerFactory> factory = initialiser.getServerFactoryClassForJCARegistration();
169         if (factory != null)
170         {
171             providerMap.put(mechanism, factory);
172         }
173         _logger.info("Initialised " + mechanism + " SASL provider successfully");
174     }
175 
176     public String getMechanisms()
177     {
178         if (_default != null)
179         {
180             // Use the default AuthenticationManager if present
181             return _default.getMechanisms();
182         }
183         else
184         {
185             return _mechanisms;
186         }
187     }
188 
189     public SaslServer createSaslServer(String mechanism, String localFQDNthrows SaslException
190     {
191         if (_default != null)
192         {
193             // Use the default AuthenticationManager if present
194             return _default.createSaslServer(mechanism, localFQDN);
195         }
196         else
197         {
198             return Sasl.createSaslServer(mechanism, "AMQP", localFQDN, _serverCreationProperties.get(mechanism),
199                                          _callbackHandlerMap.get(mechanism));
200         }
201 
202     }
203 
204     public AuthenticationResult authenticate(SaslServer server, byte[] response)
205     {
206         // Use the default AuthenticationManager if present
207         if (_default != null)
208         {
209             return _default.authenticate(server, response);
210         }
211 
212 
213         try
214         {
215             // Process response from the client
216             byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]);
217 
218             if (server.isComplete())
219             {
220                 return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.SUCCESS);
221             }
222             else
223             {
224                 return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE);
225             }
226         }
227         catch (SaslException e)
228         {
229             return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e);
230         }
231     }
232 
233     public void close()
234     {
235         Security.removeProvider(PROVIDER_NAME);
236     }
237 }