View Javadoc
1   /*
2    * #%L
3    * AuthenticatingConnection.java - mongodb-async-driver - Allanbank Consulting, Inc.
4    * %%
5    * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc.
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * 
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  
21  package com.allanbank.mongodb.client.connection.auth;
22  
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.Map;
26  import java.util.concurrent.ConcurrentHashMap;
27  
28  import com.allanbank.mongodb.Credential;
29  import com.allanbank.mongodb.MongoClientConfiguration;
30  import com.allanbank.mongodb.MongoDbException;
31  import com.allanbank.mongodb.client.Message;
32  import com.allanbank.mongodb.client.callback.ReplyCallback;
33  import com.allanbank.mongodb.client.connection.Connection;
34  import com.allanbank.mongodb.client.connection.proxy.AbstractProxyConnection;
35  import com.allanbank.mongodb.error.MongoDbAuthenticationException;
36  import com.allanbank.mongodb.util.log.Log;
37  import com.allanbank.mongodb.util.log.LogFactory;
38  
39  /**
40   * AuthenticatingConnection provides a connection that authenticated with the
41   * server for each database before it is used.
42   * 
43   * @api.no This class is <b>NOT</b> part of the drivers API. This class may be
44   *         mutated in incompatible ways between any two releases of the driver.
45   * @copyright 2012-2014, Allanbank Consulting, Inc., All Rights Reserved
46   */
47  public class AuthenticatingConnection extends AbstractProxyConnection {
48  
49      /** The name of the administration database. */
50      public static final String ADMIN_DB_NAME = MongoClientConfiguration.ADMIN_DB_NAME;
51  
52      /** The logger for the authenticator. */
53      public static final Log LOG = LogFactory
54              .getLog(AuthenticatingConnection.class);
55  
56      /** Map of the authenticators. */
57      private final Map<String, Authenticator> myAuthenticators;
58  
59      /** Set of the databases with authentication failures. */
60      private final Map<String, MongoDbException> myFailures;
61  
62      /**
63       * Creates a new AuthenticatingConnection.
64       * 
65       * @param connection
66       *            The connection to ensure gets authenticated as needed.
67       * @param config
68       *            The MongoDB client configuration.
69       */
70      public AuthenticatingConnection(final Connection connection,
71              final MongoClientConfiguration config) {
72          super(connection);
73  
74          myAuthenticators = new ConcurrentHashMap<String, Authenticator>();
75          myFailures = new ConcurrentHashMap<String, MongoDbException>();
76  
77          // With the advent of delegated credentials we must now authenticate
78          // with all available credentials immediately.
79          final Collection<Credential> credentials = config.getCredentials();
80          for (final Credential credential : credentials) {
81              final Authenticator authenticator = credential.authenticator();
82  
83              authenticator.startAuthentication(credential, connection);
84  
85              // Boo! MongoDB does not support concurrent authentication attempts.
86              // Block here for the results if more than 1 credential. Boo!
87              if (credentials.size() > 1) {
88                  try {
89                      if (!authenticator.result()) {
90                          myFailures.put(credential.getDatabase(),
91                                  new MongoDbAuthenticationException(
92                                          "Authentication failed for the "
93                                                  + credential.getDatabase()
94                                                  + " database."));
95                      }
96                  }
97                  catch (final MongoDbException error) {
98                      myFailures.put(credential.getDatabase(), error);
99                  }
100             }
101             else {
102                 myAuthenticators.put(credential.getDatabase(), authenticator);
103             }
104         }
105     }
106 
107     /**
108      * {@inheritDoc}
109      * <p>
110      * Makes sure the connection is authenticated for the current database
111      * before forwarding to the proxied connection.
112      * </p>
113      */
114     @Override
115     public void send(final Message message1, final Message message2,
116             final ReplyCallback replyCallback) throws MongoDbException {
117         ensureAuthenticated(message1);
118         ensureAuthenticated(message2);
119 
120         super.send(message1, message2, replyCallback);
121     }
122 
123     /**
124      * {@inheritDoc}
125      * <p>
126      * Makes sure the connection is authenticated for the current database
127      * before forwarding to the proxied connection.
128      * </p>
129      */
130     @Override
131     public void send(final Message message, final ReplyCallback replyCallback)
132             throws MongoDbException {
133         ensureAuthenticated(message);
134 
135         super.send(message, replyCallback);
136     }
137 
138     /**
139      * {@inheritDoc}
140      * <p>
141      * Overridden to return the socket information.
142      * </p>
143      */
144     @Override
145     public String toString() {
146         return "Auth(" + getProxiedConnection() + ")";
147     }
148 
149     /**
150      * {@inheritDoc}
151      * <p>
152      * Overridden to give access to the proxied connections to tests.
153      * </p>
154      */
155     @Override
156     protected Connection getProxiedConnection() {
157         final Connection proxied = super.getProxiedConnection();
158 
159         return proxied;
160     }
161 
162     /**
163      * Ensures the connection has either already authenticated with the server
164      * or completes the authentication.
165      * 
166      * @param message
167      *            The message to authenticate for.
168      * @throws MongoDbAuthenticationException
169      *             On a failure to authenticate with the MongDB server.
170      */
171     private void ensureAuthenticated(final Message message)
172             throws MongoDbAuthenticationException {
173         // Check the authentication results are done.
174         if (!myAuthenticators.isEmpty()) {
175             final Iterator<Map.Entry<String, Authenticator>> iter = myAuthenticators
176                     .entrySet().iterator();
177             while (iter.hasNext()) {
178                 final Map.Entry<String, Authenticator> authenticator = iter
179                         .next();
180                 try {
181                     if (!authenticator.getValue().result()) {
182                         myFailures.put(authenticator.getKey(),
183                                 new MongoDbAuthenticationException(
184                                         "Authentication failed for the "
185                                                 + authenticator.getKey()
186                                                 + " database."));
187                     }
188                 }
189                 catch (final MongoDbException error) {
190                     // Just log the error here.
191                     LOG.warn(error, "Authentication failed: []",
192                             error.getMessage());
193                     // Re-throw if our DB.
194                     myFailures.put(authenticator.getKey(), error);
195                 }
196                 finally {
197                     iter.remove();
198                 }
199             }
200         }
201 
202         if (myFailures.containsKey(message.getDatabaseName())) {
203             throw new MongoDbAuthenticationException(myFailures.get(message
204                     .getDatabaseName()));
205         }
206     }
207 }