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 }