1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package com.allanbank.mongodb.client.connection.auth;
22
23 import java.nio.ByteBuffer;
24 import java.nio.CharBuffer;
25 import java.nio.charset.Charset;
26 import java.security.MessageDigest;
27 import java.security.NoSuchAlgorithmException;
28 import java.util.Arrays;
29 import java.util.List;
30 import java.util.concurrent.ExecutionException;
31
32 import com.allanbank.mongodb.Credential;
33 import com.allanbank.mongodb.MongoDbException;
34 import com.allanbank.mongodb.bson.Document;
35 import com.allanbank.mongodb.bson.Element;
36 import com.allanbank.mongodb.bson.builder.BuilderFactory;
37 import com.allanbank.mongodb.bson.builder.DocumentBuilder;
38 import com.allanbank.mongodb.bson.element.StringElement;
39 import com.allanbank.mongodb.client.FutureCallback;
40 import com.allanbank.mongodb.client.callback.AbstractReplyCallback;
41 import com.allanbank.mongodb.client.callback.AbstractValidatingReplyCallback;
42 import com.allanbank.mongodb.client.connection.Connection;
43 import com.allanbank.mongodb.client.message.Command;
44 import com.allanbank.mongodb.client.message.Reply;
45 import com.allanbank.mongodb.error.MongoDbAuthenticationException;
46 import com.allanbank.mongodb.util.IOUtils;
47
48
49
50
51
52
53
54 public class MongoDbAuthenticator implements Authenticator {
55
56
57 public static final Charset ASCII = Charset.forName("US-ASCII");
58
59
60 protected FutureCallback<Boolean> myResults = new FutureCallback<Boolean>();
61
62
63
64
65 public MongoDbAuthenticator() {
66 super();
67 }
68
69
70
71
72
73
74
75 @Override
76 public MongoDbAuthenticator clone() {
77 try {
78 final MongoDbAuthenticator newAuth = (MongoDbAuthenticator) super
79 .clone();
80 newAuth.myResults = new FutureCallback<Boolean>();
81
82 return newAuth;
83 }
84 catch (final CloneNotSupportedException cannotHappen) {
85 return new MongoDbAuthenticator();
86 }
87 }
88
89
90
91
92
93
94
95
96
97
98 public String passwordHash(final Credential credentials)
99 throws NoSuchAlgorithmException {
100 final MessageDigest md5 = MessageDigest.getInstance("MD5");
101
102 return passwordHash(md5, credentials);
103 }
104
105
106
107
108
109
110
111 @Override
112 public boolean result() throws MongoDbAuthenticationException {
113
114
115 final boolean interrupted = Thread.interrupted();
116 try {
117 return myResults.get().booleanValue();
118 }
119 catch (final InterruptedException e) {
120 throw new MongoDbAuthenticationException(e);
121 }
122 catch (final ExecutionException e) {
123 throw new MongoDbAuthenticationException(e);
124 }
125 finally {
126 if (interrupted) {
127 Thread.currentThread().interrupt();
128 }
129 }
130 }
131
132
133
134
135
136
137
138
139 @Override
140 public void startAuthentication(final Credential credential,
141 final Connection connection) throws MongoDbAuthenticationException {
142
143 try {
144 final DocumentBuilder builder = BuilderFactory.start();
145 builder.addInteger("getnonce", 1);
146
147 connection.send(new Command(credential.getDatabase(),
148 Command.COMMAND_COLLECTION, builder.build()),
149 new NonceReplyCallback(credential, connection));
150 }
151 catch (final MongoDbException errorOnSend) {
152 myResults.exception(errorOnSend);
153
154 throw errorOnSend;
155 }
156 }
157
158
159
160
161
162
163
164
165
166
167 protected String passwordHash(final MessageDigest md5,
168 final Credential credentials) {
169
170 final char[] password = credentials.getPassword();
171 final ByteBuffer bb = ASCII.encode(CharBuffer.wrap(password));
172
173 md5.update((credentials.getUserName() + ":mongo:").getBytes(ASCII));
174 md5.update(bb.array(), 0, bb.limit());
175
176 Arrays.fill(password, ' ');
177 Arrays.fill(bb.array(), (byte) 0);
178
179 return IOUtils.toHex(md5.digest());
180 }
181
182
183
184
185
186
187
188 private static class AuthenticateReplyCallback extends
189 AbstractReplyCallback<Boolean> {
190
191
192
193
194
195
196 public AuthenticateReplyCallback(final FutureCallback<Boolean> results) {
197 super(results);
198 }
199
200
201
202
203
204
205
206
207 @Override
208 protected Boolean convert(final Reply reply) throws MongoDbException {
209 boolean current = false;
210 final List<Document> results = reply.getResults();
211 if (results.size() == 1) {
212 final Document doc = results.get(0);
213 final Element okElem = doc.get("ok");
214 if (okElem != null) {
215 final int okValue = toInt(okElem);
216 if (okValue == 1) {
217 current = true;
218 }
219
220 }
221 }
222
223 return Boolean.valueOf(current);
224 }
225 }
226
227
228
229
230
231
232
233 private class NonceReplyCallback extends AbstractValidatingReplyCallback {
234
235
236 private final Connection myConnection;
237
238
239 private final Credential myCredential;
240
241
242
243
244
245
246
247
248
249 public NonceReplyCallback(final Credential credential,
250 final Connection connection) {
251 myCredential = credential;
252 myConnection = connection;
253 }
254
255
256
257
258
259
260
261
262 @Override
263 public void exception(final Throwable thrown) {
264 myResults.exception(thrown);
265 }
266
267
268
269
270
271
272
273
274 @Override
275 public boolean isLightWeight() {
276 return true;
277 }
278
279
280
281
282
283
284
285
286 @Override
287 protected void handle(final Reply reply) {
288
289 StringElement nonce = null;
290 if (reply.getResults().size() > 0) {
291 final Document doc = reply.getResults().get(0);
292 nonce = doc.get(StringElement.class, "nonce");
293 if (nonce == null) {
294
295 exception(new MongoDbAuthenticationException(
296 "Bad response from nonce request."));
297 return;
298 }
299 }
300 else {
301
302 exception(new MongoDbAuthenticationException(
303 "Bad response from nonce request."));
304 return;
305 }
306
307
308 try {
309 final MessageDigest md5 = MessageDigest.getInstance("MD5");
310 final String passwordHash = passwordHash(md5, myCredential);
311
312 final String text = nonce.getValue()
313 + myCredential.getUserName() + passwordHash;
314
315 md5.reset();
316 md5.update(text.getBytes(ASCII));
317 final byte[] bytes = md5.digest();
318
319 final DocumentBuilder builder = BuilderFactory.start();
320 builder.addInteger("authenticate", 1);
321 builder.add(nonce);
322 builder.addString("user", myCredential.getUserName());
323 builder.addString("key", IOUtils.toHex(bytes));
324
325 myConnection.send(new Command(myCredential.getDatabase(),
326 Command.COMMAND_COLLECTION, builder.build()),
327 new AuthenticateReplyCallback(myResults));
328 }
329 catch (final NoSuchAlgorithmException e) {
330 exception(new MongoDbAuthenticationException(e));
331 }
332 catch (final RuntimeException e) {
333 exception(new MongoDbAuthenticationException(e));
334 }
335 }
336 }
337 }