Merge lp:~nwilliams/akiban-server/security-service-login-service into lp:~akiban-technologies/akiban-server/trunk
- security-service-login-service
- Merge into trunk
Proposed by
Nathan Williams
Status: | Merged |
---|---|
Approved by: | Thomas Jones-Low |
Approved revision: | 2638 |
Merged at revision: | 2634 |
Proposed branch: | lp:~nwilliams/akiban-server/security-service-login-service |
Merge into: | lp:~akiban-technologies/akiban-server/trunk |
Diff against target: |
540 lines (+256/-118) 9 files modified
src/main/java/com/akiban/http/HttpConductorImpl.java (+8/-47) src/main/java/com/akiban/http/SecurityServiceLoginService.java (+72/-0) src/main/java/com/akiban/server/service/security/SecurityServiceImpl.java (+6/-4) src/main/java/com/akiban/server/service/security/User.java (+17/-1) src/main/resources/com/akiban/http/basic.properties (+0/-33) src/main/resources/com/akiban/http/digest.properties (+0/-33) src/main/resources/com/akiban/server/service/config/configuration-defaults.properties (+3/-0) src/test/java/com/akiban/http/HttpThreadedLoginIT.java (+147/-0) src/test/java/com/akiban/server/service/security/SecurityServiceIT.java (+3/-0) |
To merge this branch: | bzr merge lp:~nwilliams/akiban-server/security-service-login-service |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Thomas Jones-Low | Approve | ||
Review via email: mp+160229@code.launchpad.net |
Commit message
Description of the change
Replace SingleThreadedJ
The JDBCService is racey and doesn't work with the requirements of our EmbeddedJDBCService so just ditch it.
Expose more of the information off of the User (password hashes, all roles). It wasn't a direct concern, but the new LoginService also has a cache time so the performance should be identical.
The test failed quite consistently for the 5, 10, and 20 thread cases before the fix. Now passes as desired.
To post a comment you must log in.
- 2638. By Nathan Williams
-
Remove unneeded import
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/main/java/com/akiban/http/HttpConductorImpl.java' |
2 | --- src/main/java/com/akiban/http/HttpConductorImpl.java 2013-04-14 16:11:04 +0000 |
3 | +++ src/main/java/com/akiban/http/HttpConductorImpl.java 2013-04-22 23:01:29 +0000 |
4 | @@ -17,19 +17,18 @@ |
5 | |
6 | package com.akiban.http; |
7 | |
8 | -import com.akiban.server.error.AkibanInternalException; |
9 | import com.akiban.server.service.Service; |
10 | import com.akiban.server.service.config.ConfigurationService; |
11 | import com.akiban.server.service.monitor.MonitorService; |
12 | import com.akiban.server.service.monitor.ServerMonitor; |
13 | import com.akiban.server.service.security.SecurityService; |
14 | +import com.akiban.sql.embedded.EmbeddedJDBCService; |
15 | import com.google.inject.Inject; |
16 | import org.eclipse.jetty.servlets.CrossOriginFilter; |
17 | import org.eclipse.jetty.util.security.Constraint; |
18 | import org.eclipse.jetty.security.Authenticator; |
19 | import org.eclipse.jetty.security.ConstraintMapping; |
20 | import org.eclipse.jetty.security.ConstraintSecurityHandler; |
21 | -import org.eclipse.jetty.security.JDBCLoginService; |
22 | import org.eclipse.jetty.security.LoginService; |
23 | import org.eclipse.jetty.security.authentication.BasicAuthenticator; |
24 | import org.eclipse.jetty.security.authentication.DigestAuthenticator; |
25 | @@ -45,8 +44,6 @@ |
26 | |
27 | import javax.servlet.FilterRegistration; |
28 | import javax.servlet.ServletException; |
29 | -import java.io.IOException; |
30 | -import java.lang.reflect.Method; |
31 | import java.util.Collections; |
32 | import java.util.HashSet; |
33 | import java.util.Set; |
34 | @@ -59,6 +56,7 @@ |
35 | private static final String CONFIG_PORT_PROPERTY = CONFIG_HTTP_PREFIX + "port"; |
36 | private static final String CONFIG_SSL_PROPERTY = CONFIG_HTTP_PREFIX + "ssl"; |
37 | private static final String CONFIG_LOGIN_PROPERTY = CONFIG_HTTP_PREFIX + "login"; |
38 | + private static final String CONFIG_LOGIN_CACHE_SECONDS = CONFIG_HTTP_PREFIX + "login_cache_seconds"; |
39 | private static final String CONFIG_XORIGIN_PREFIX = CONFIG_HTTP_PREFIX + "cross_origin."; |
40 | private static final String CONFIG_XORIGIN_ENABLED = CONFIG_XORIGIN_PREFIX + "enabled"; |
41 | private static final String CONFIG_XORIGIN_ORIGINS = CONFIG_XORIGIN_PREFIX + "allowed_origins"; |
42 | @@ -152,6 +150,7 @@ |
43 | String portProperty = configurationService.getProperty(CONFIG_PORT_PROPERTY); |
44 | String sslProperty = configurationService.getProperty(CONFIG_SSL_PROPERTY); |
45 | String loginProperty = configurationService.getProperty(CONFIG_LOGIN_PROPERTY); |
46 | + int loginCacheSeconds = Integer.parseInt(configurationService.getProperty(CONFIG_LOGIN_CACHE_SECONDS)); |
47 | int portLocal; |
48 | boolean ssl; |
49 | AuthenticationType login; |
50 | @@ -210,20 +209,20 @@ |
51 | localServer.setHandler(localHandlerList); |
52 | } |
53 | else { |
54 | - String resource; |
55 | + SecurityServiceLoginService.CredentialType credentialType; |
56 | Authenticator authenticator; |
57 | switch (login) { |
58 | case BASIC: |
59 | - resource = "basic.properties"; |
60 | + credentialType = SecurityServiceLoginService.CredentialType.BASIC; |
61 | authenticator = new BasicAuthenticator(); |
62 | break; |
63 | case DIGEST: |
64 | - resource = "digest.properties"; |
65 | + credentialType = SecurityServiceLoginService.CredentialType.DIGEST; |
66 | authenticator = new DigestAuthenticator(); |
67 | break; |
68 | default: |
69 | assert false : "Unexpected authentication type " + login; |
70 | - resource = null; |
71 | + credentialType = null; |
72 | authenticator = null; |
73 | } |
74 | Constraint constraint = new Constraint(authenticator.getAuthMethod(), |
75 | @@ -238,9 +237,7 @@ |
76 | sh.setAuthenticator(authenticator); |
77 | sh.setConstraintMappings(Collections.singletonList(cm)); |
78 | |
79 | - LoginService loginService = |
80 | - new SingleThreadJDBCLoginService(SecurityService.REALM, |
81 | - HttpConductorImpl.class.getResource(resource).toString()); |
82 | + LoginService loginService = new SecurityServiceLoginService(securityService, credentialType, loginCacheSeconds); |
83 | sh.setLoginService(loginService); |
84 | |
85 | sh.setHandler(localHandlerList); |
86 | @@ -315,42 +312,6 @@ |
87 | return result; |
88 | } |
89 | |
90 | - // Embedded JDBC is single-threaded, but login service assumes it |
91 | - // is thread-safe. Also, it does not close its ResultSet, which |
92 | - // leaves a transaction active. So just close connection around |
93 | - // each use. Login service is already |
94 | - // synchronized. Unfortunately, this needs a private method. |
95 | - static class SingleThreadJDBCLoginService extends JDBCLoginService { |
96 | - private Method closeMethod; |
97 | - |
98 | - public SingleThreadJDBCLoginService(String name, String config) |
99 | - throws IOException { |
100 | - super(name, config); |
101 | - try { |
102 | - closeMethod = JDBCLoginService.class.getDeclaredMethod("closeConnection", null); |
103 | - closeMethod.setAccessible(true); |
104 | - } |
105 | - catch (Exception ex) { |
106 | - throw new AkibanInternalException("Cannot get JDBC close method", ex); |
107 | - } |
108 | - } |
109 | - |
110 | - @Override |
111 | - protected org.eclipse.jetty.server.UserIdentity loadUser(String username) { |
112 | - try { |
113 | - return super.loadUser(username); |
114 | - } |
115 | - finally { |
116 | - try { |
117 | - closeMethod.invoke(this); |
118 | - } |
119 | - catch (Exception ex) { |
120 | - logger.warn("Cannot call JDBC close method", ex); |
121 | - } |
122 | - } |
123 | - } |
124 | - } |
125 | - |
126 | private class ConnectionMonitor implements ServerMonitor { |
127 | public static final String SERVER_TYPE = "REST"; |
128 | private final SelectChannelConnector connector; |
129 | |
130 | === added file 'src/main/java/com/akiban/http/SecurityServiceLoginService.java' |
131 | --- src/main/java/com/akiban/http/SecurityServiceLoginService.java 1970-01-01 00:00:00 +0000 |
132 | +++ src/main/java/com/akiban/http/SecurityServiceLoginService.java 2013-04-22 23:01:29 +0000 |
133 | @@ -0,0 +1,72 @@ |
134 | +/** |
135 | + * Copyright (C) 2009-2013 Akiban Technologies, Inc. |
136 | + * |
137 | + * This program is free software: you can redistribute it and/or modify |
138 | + * it under the terms of the GNU Affero General Public License as published by |
139 | + * the Free Software Foundation, either version 3 of the License, or |
140 | + * (at your option) any later version. |
141 | + * |
142 | + * This program is distributed in the hope that it will be useful, |
143 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
144 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
145 | + * GNU Affero General Public License for more details. |
146 | + * |
147 | + * You should have received a copy of the GNU Affero General Public License |
148 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
149 | + */ |
150 | + |
151 | +package com.akiban.http; |
152 | + |
153 | +import java.util.List; |
154 | + |
155 | +import com.akiban.server.service.security.SecurityService; |
156 | +import com.akiban.server.service.security.User; |
157 | +import com.akiban.util.ArgumentValidation; |
158 | +import org.eclipse.jetty.security.MappedLoginService; |
159 | +import org.eclipse.jetty.server.UserIdentity; |
160 | +import org.eclipse.jetty.util.security.Credential; |
161 | + |
162 | +public class SecurityServiceLoginService extends MappedLoginService |
163 | +{ |
164 | + public enum CredentialType { BASIC, DIGEST } |
165 | + |
166 | + private final SecurityService securityService; |
167 | + private final CredentialType credentialType; |
168 | + private final long cacheMillis; |
169 | + private volatile long lastCachePurge; |
170 | + |
171 | + public SecurityServiceLoginService(SecurityService securityService, CredentialType credentialType, int cacheSeconds) { |
172 | + ArgumentValidation.isGTE("cacheSeconds", cacheSeconds, 0); |
173 | + if(credentialType != CredentialType.BASIC && credentialType != CredentialType.DIGEST) { |
174 | + throw new IllegalArgumentException("Unknown credential: " + credentialType); |
175 | + } |
176 | + this.securityService = securityService; |
177 | + this.credentialType = credentialType; |
178 | + this.cacheMillis = cacheSeconds * 1000; |
179 | + } |
180 | + |
181 | + @Override |
182 | + public UserIdentity login(String username, Object credentials) { |
183 | + long now = System.currentTimeMillis(); |
184 | + if((now - lastCachePurge) > cacheMillis) { |
185 | + super._users.clear(); |
186 | + lastCachePurge = now; |
187 | + } |
188 | + return super.login(username, credentials); |
189 | + } |
190 | + |
191 | + @Override |
192 | + protected void loadUsers() { |
193 | + } |
194 | + |
195 | + @Override |
196 | + protected UserIdentity loadUser(String username) { |
197 | + User user = securityService.getUser(username); |
198 | + if(user != null) { |
199 | + String password = (credentialType == CredentialType.BASIC) ? user.getBasicPassword() : user.getDigestPassword(); |
200 | + List<String> roles = user.getRoles(); |
201 | + return putUser(username, Credential.getCredential(password), roles.toArray(new String[roles.size()])); |
202 | + } |
203 | + return null; |
204 | + } |
205 | +} |
206 | |
207 | === modified file 'src/main/java/com/akiban/server/service/security/SecurityServiceImpl.java' |
208 | --- src/main/java/com/akiban/server/service/security/SecurityServiceImpl.java 2013-03-27 10:45:17 +0000 |
209 | +++ src/main/java/com/akiban/server/service/security/SecurityServiceImpl.java 2013-04-22 23:01:29 +0000 |
210 | @@ -204,7 +204,7 @@ |
211 | roles.add(rs1.getString(2)); |
212 | } |
213 | rs1.close(); |
214 | - return new User(rs.getInt(1), rs.getString(2), roles); |
215 | + return new User(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4), roles); |
216 | } |
217 | |
218 | @Override |
219 | @@ -212,12 +212,14 @@ |
220 | int id; |
221 | Connection conn = null; |
222 | PreparedStatement stmt = null; |
223 | + String basicPassword = basicPassword(password); |
224 | + String digestPassword = digestPassword(name, password); |
225 | try { |
226 | conn = openConnection(); |
227 | stmt = conn.prepareStatement(ADD_USER_SQL); |
228 | stmt.setString(1, name); |
229 | - stmt.setString(2, basicPassword(password)); |
230 | - stmt.setString(3, digestPassword(name, password)); |
231 | + stmt.setString(2, basicPassword); |
232 | + stmt.setString(3, digestPassword); |
233 | stmt.setString(4, md5Password(name, password)); |
234 | int nrows = stmt.executeUpdate(); |
235 | if (nrows != 1) { |
236 | @@ -250,7 +252,7 @@ |
237 | finally { |
238 | cleanup(conn, stmt); |
239 | } |
240 | - return new User(id, name, new ArrayList<>(roles)); |
241 | + return new User(id, name, basicPassword, digestPassword, new ArrayList<>(roles)); |
242 | } |
243 | |
244 | @Override |
245 | |
246 | === modified file 'src/main/java/com/akiban/server/service/security/User.java' |
247 | --- src/main/java/com/akiban/server/service/security/User.java 2013-03-22 20:05:57 +0000 |
248 | +++ src/main/java/com/akiban/server/service/security/User.java 2013-04-22 23:01:29 +0000 |
249 | @@ -25,11 +25,15 @@ |
250 | { |
251 | private final int id; |
252 | private final String name; |
253 | + private final String basicPassword; |
254 | + private final String digestPassword; |
255 | private final List<String> roles; |
256 | |
257 | - protected User(int id, String name, List<String> roles) { |
258 | + protected User(int id, String name, String basicPassword, String digestPassword, List<String> roles) { |
259 | this.id = id; |
260 | this.name = name; |
261 | + this.basicPassword = basicPassword; |
262 | + this.digestPassword = digestPassword; |
263 | this.roles = roles; |
264 | } |
265 | |
266 | @@ -42,6 +46,18 @@ |
267 | return name; |
268 | } |
269 | |
270 | + public String getBasicPassword() { |
271 | + return basicPassword; |
272 | + } |
273 | + |
274 | + public String getDigestPassword() { |
275 | + return digestPassword; |
276 | + } |
277 | + |
278 | + public List<String> getRoles() { |
279 | + return roles; |
280 | + } |
281 | + |
282 | public boolean hasRole(String role) { |
283 | return roles.contains(role); |
284 | } |
285 | |
286 | === removed file 'src/main/resources/com/akiban/http/basic.properties' |
287 | --- src/main/resources/com/akiban/http/basic.properties 2013-03-22 20:05:57 +0000 |
288 | +++ src/main/resources/com/akiban/http/basic.properties 1970-01-01 00:00:00 +0000 |
289 | @@ -1,33 +0,0 @@ |
290 | -# |
291 | -# Copyright (C) 2009-2013 Akiban Technologies, Inc. |
292 | -# |
293 | -# This program is free software: you can redistribute it and/or modify |
294 | -# it under the terms of the GNU Affero General Public License as published by |
295 | -# the Free Software Foundation, either version 3 of the License, or |
296 | -# (at your option) any later version. |
297 | -# |
298 | -# This program is distributed in the hope that it will be useful, |
299 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
300 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
301 | -# GNU Affero General Public License for more details. |
302 | -# |
303 | -# You should have received a copy of the GNU Affero General Public License |
304 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
305 | -# |
306 | - |
307 | -# Driver doesn't matter (already loaded), but must have no-arg ctor. |
308 | -jdbcdriver=java.lang.Object |
309 | -url=jdbc:default:connection |
310 | -username=akiban |
311 | -password=akiban |
312 | -usertable=security_schema.users |
313 | -usertablekey=id |
314 | -usertableuserfield=name |
315 | -usertablepasswordfield=password_basic |
316 | -roletable=security_schema.roles |
317 | -roletablekey=id |
318 | -roletablerolefield=name |
319 | -userroletable=security_schema.user_roles |
320 | -userroletableuserkey=user_id |
321 | -userroletablerolekey=role_id |
322 | -cachetime=300 |
323 | |
324 | === removed file 'src/main/resources/com/akiban/http/digest.properties' |
325 | --- src/main/resources/com/akiban/http/digest.properties 2013-03-22 20:05:57 +0000 |
326 | +++ src/main/resources/com/akiban/http/digest.properties 1970-01-01 00:00:00 +0000 |
327 | @@ -1,33 +0,0 @@ |
328 | -# |
329 | -# Copyright (C) 2009-2013 Akiban Technologies, Inc. |
330 | -# |
331 | -# This program is free software: you can redistribute it and/or modify |
332 | -# it under the terms of the GNU Affero General Public License as published by |
333 | -# the Free Software Foundation, either version 3 of the License, or |
334 | -# (at your option) any later version. |
335 | -# |
336 | -# This program is distributed in the hope that it will be useful, |
337 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
338 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
339 | -# GNU Affero General Public License for more details. |
340 | -# |
341 | -# You should have received a copy of the GNU Affero General Public License |
342 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
343 | -# |
344 | - |
345 | -# Driver doesn't matter (already loaded), but must have no-arg ctor. |
346 | -jdbcdriver=java.lang.Object |
347 | -url=jdbc:default:connection |
348 | -username=akiban |
349 | -password=akiban |
350 | -usertable=security_schema.users |
351 | -usertablekey=id |
352 | -usertableuserfield=name |
353 | -usertablepasswordfield=password_digest |
354 | -roletable=security_schema.roles |
355 | -roletablekey=id |
356 | -roletablerolefield=name |
357 | -userroletable=security_schema.user_roles |
358 | -userroletableuserkey=user_id |
359 | -userroletablerolekey=role_id |
360 | -cachetime=300 |
361 | |
362 | === modified file 'src/main/resources/com/akiban/server/service/config/configuration-defaults.properties' |
363 | --- src/main/resources/com/akiban/server/service/config/configuration-defaults.properties 2013-04-15 16:20:25 +0000 |
364 | +++ src/main/resources/com/akiban/server/service/config/configuration-defaults.properties 2013-04-22 23:01:29 +0000 |
365 | @@ -82,6 +82,9 @@ |
366 | akserver.http.ssl=false |
367 | akserver.http.login=none |
368 | |
369 | +# How long to save credential look-ups |
370 | +akserver.http.login_cache_seconds = 300 |
371 | + |
372 | # If enabled, use a CrossOriginFilter and construct it with these defaults. |
373 | akserver.http.cross_origin.enabled = true |
374 | akserver.http.cross_origin.allowed_origins = * |
375 | |
376 | === added file 'src/test/java/com/akiban/http/HttpThreadedLoginIT.java' |
377 | --- src/test/java/com/akiban/http/HttpThreadedLoginIT.java 1970-01-01 00:00:00 +0000 |
378 | +++ src/test/java/com/akiban/http/HttpThreadedLoginIT.java 2013-04-22 23:01:29 +0000 |
379 | @@ -0,0 +1,147 @@ |
380 | +/** |
381 | + * Copyright (C) 2009-2013 Akiban Technologies, Inc. |
382 | + * |
383 | + * This program is free software: you can redistribute it and/or modify |
384 | + * it under the terms of the GNU Affero General Public License as published by |
385 | + * the Free Software Foundation, either version 3 of the License, or |
386 | + * (at your option) any later version. |
387 | + * |
388 | + * This program is distributed in the hope that it will be useful, |
389 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
390 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
391 | + * GNU Affero General Public License for more details. |
392 | + * |
393 | + * You should have received a copy of the GNU Affero General Public License |
394 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
395 | + */ |
396 | + |
397 | +package com.akiban.http; |
398 | + |
399 | +import com.akiban.rest.RestService; |
400 | +import com.akiban.rest.RestServiceImpl; |
401 | +import com.akiban.server.service.servicemanager.GuicedServiceManager; |
402 | +import com.akiban.server.test.it.ITBase; |
403 | +import com.akiban.sql.embedded.EmbeddedJDBCService; |
404 | +import com.akiban.sql.embedded.EmbeddedJDBCServiceImpl; |
405 | + |
406 | +import org.apache.http.HttpResponse; |
407 | +import org.apache.http.HttpStatus; |
408 | +import org.apache.http.client.HttpClient; |
409 | +import org.apache.http.client.methods.HttpGet; |
410 | +import org.apache.http.impl.client.DefaultHttpClient; |
411 | +import org.apache.http.util.EntityUtils; |
412 | +import org.junit.Test; |
413 | +import org.slf4j.Logger; |
414 | +import org.slf4j.LoggerFactory; |
415 | + |
416 | +import static org.junit.Assert.*; |
417 | + |
418 | +import java.net.URI; |
419 | +import java.util.Collections; |
420 | +import java.util.HashMap; |
421 | +import java.util.Map; |
422 | + |
423 | +public class HttpThreadedLoginIT extends ITBase |
424 | +{ |
425 | + private static final Logger LOG = LoggerFactory.getLogger(HttpThreadedLoginIT.class); |
426 | + |
427 | + @Override |
428 | + protected GuicedServiceManager.BindingsConfigurationProvider serviceBindingsProvider() { |
429 | + return super.serviceBindingsProvider() |
430 | + .bindAndRequire(RestService.class, RestServiceImpl.class); |
431 | + } |
432 | + |
433 | + @Override |
434 | + protected Map<String, String> startupConfigProperties() { |
435 | + Map<String, String> properties = new HashMap<>(); |
436 | + properties.put("akserver.http.login", "basic"); |
437 | + return properties; |
438 | + } |
439 | + |
440 | + private static int openRestURL(String userInfo, int port, String path) throws Exception { |
441 | + HttpClient client = new DefaultHttpClient(); |
442 | + URI uri = new URI("http", userInfo, "localhost", port, path, null, null); |
443 | + HttpGet get = new HttpGet(uri); |
444 | + HttpResponse response = client.execute(get); |
445 | + int code = response.getStatusLine().getStatusCode(); |
446 | + EntityUtils.consume(response.getEntity()); |
447 | + client.getConnectionManager().shutdown(); |
448 | + return code; |
449 | + } |
450 | + |
451 | + @Test |
452 | + public void oneThread() throws InterruptedException { |
453 | + run(1); |
454 | + } |
455 | + |
456 | + @Test |
457 | + public void fiveThreads() throws InterruptedException { |
458 | + run(5); |
459 | + } |
460 | + |
461 | + @Test |
462 | + public void tenThreads() throws InterruptedException { |
463 | + run(10); |
464 | + } |
465 | + |
466 | + @Test |
467 | + public void twentyThreads() throws InterruptedException { |
468 | + run(20); |
469 | + } |
470 | + |
471 | + |
472 | + private void run(int count) throws InterruptedException { |
473 | + final int port = serviceManager().getServiceByClass(HttpConductor.class).getPort(); |
474 | + final String context = serviceManager().getServiceByClass(RestService.class).getContextPath(); |
475 | + final String path = context + "/version"; |
476 | + final UncaughtHandler uncaughtHandler = new UncaughtHandler(); |
477 | + |
478 | + Thread threads[] = new Thread[count]; |
479 | + for(int i = 0; i < count; ++i) { |
480 | + threads[i] = new Thread(new TestRunnable(port, path, i), "Thread"+i); |
481 | + threads[i].setUncaughtExceptionHandler(uncaughtHandler); |
482 | + } |
483 | + for(int i = 0; i < count; ++i) { |
484 | + threads[i].start(); |
485 | + } |
486 | + for(int i = 0; i < count; ++i) { |
487 | + threads[i].join(); |
488 | + } |
489 | + |
490 | + for(Throwable entry : uncaughtHandler.uncaught.values()) { |
491 | + LOG.error("Uncaught exception", entry); |
492 | + } |
493 | + assertEquals("uncaught exception count", 0, uncaughtHandler.uncaught.size()); |
494 | + } |
495 | + |
496 | + private static class UncaughtHandler implements Thread.UncaughtExceptionHandler { |
497 | + public final Map<Thread,Throwable> uncaught = Collections.synchronizedMap(new HashMap<Thread,Throwable>()); |
498 | + |
499 | + @Override |
500 | + public void uncaughtException(Thread t, Throwable e) { |
501 | + uncaught.put(t, e); |
502 | + } |
503 | + } |
504 | + |
505 | + private static class TestRunnable implements Runnable { |
506 | + private final int port; |
507 | + private final String url; |
508 | + private final int userNum; |
509 | + |
510 | + public TestRunnable(int port, String url, int userNum) { |
511 | + this.port = port; |
512 | + this.url = url; |
513 | + this.userNum = userNum; |
514 | + } |
515 | + |
516 | + @Override |
517 | + public void run() { |
518 | + String userInfo = String.format("user_%d:password", userNum); |
519 | + try { |
520 | + assertEquals(userInfo, HttpStatus.SC_UNAUTHORIZED, openRestURL(userInfo, port, url)); |
521 | + } catch(Exception e) { |
522 | + throw new RuntimeException(e); |
523 | + } |
524 | + } |
525 | + } |
526 | +} |
527 | |
528 | === modified file 'src/test/java/com/akiban/server/service/security/SecurityServiceIT.java' |
529 | --- src/test/java/com/akiban/server/service/security/SecurityServiceIT.java 2013-04-07 07:28:15 +0000 |
530 | +++ src/test/java/com/akiban/server/service/security/SecurityServiceIT.java 2013-04-22 23:01:29 +0000 |
531 | @@ -101,6 +101,9 @@ |
532 | assertNotNull("user found", user); |
533 | assertTrue("user has role", user.hasRole("rest-user")); |
534 | assertFalse("user does not have role", user.hasRole("admin")); |
535 | + assertEquals("users roles", "[rest-user]", user.getRoles().toString()); |
536 | + assertEquals("user password basic", "MD5:5F4DCC3B5AA765D61D8327DEB882CF99", user.getBasicPassword()); |
537 | + assertEquals("user password digest", "MD5:740C53C6C7EE87EEEE489E8CE4116048", user.getDigestPassword()); |
538 | } |
539 | |
540 | @Test |
Well, this will certainly make user process tracking simpler.
Otherwise looks as described.