source: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/service/Authentication.java@ 32369

Last change on this file since 32369 was 32369, checked in by kjdon, 6 years ago

lots of changes. moved to version 2 of recaptcha. moved all the error strings into the servicerack.properties file. fixed up the bug whereby a user changes his own account details, then was being shown the full user list.

File size: 40.6 KB
Line 
1package org.greenstone.gsdl3.service;
2
3import java.io.File;
4import java.io.Serializable;
5import java.math.BigInteger;
6import java.sql.SQLException;
7import java.util.ArrayList;
8import java.util.HashMap;
9import java.util.UUID;
10import java.util.Vector;
11import java.util.regex.Pattern;
12
13// for verifying recaptcha
14import java.io.BufferedReader;
15import java.io.DataOutputStream;
16import java.io.IOException;
17import java.io.InputStreamReader;
18import java.io.StringReader;
19import java.net.URL;
20import javax.net.ssl.HttpsURLConnection;
21// https://developer.android.com/reference/org/json/JSONObject.html
22// https://developer.android.com/reference/org/json/JSONArray.html
23import org.json.JSONArray;
24import org.json.JSONException;
25import org.json.JSONObject;
26
27import org.apache.commons.codec.digest.DigestUtils;
28import org.greenstone.gsdl3.util.DerbyWrapper;
29import org.greenstone.gsdl3.util.GSXML;
30import org.greenstone.gsdl3.util.UserQueryResult;
31import org.greenstone.gsdl3.util.UserTermInfo;
32import org.greenstone.gsdl3.util.XMLConverter;
33import org.greenstone.util.GlobalProperties;
34
35import org.w3c.dom.Document;
36import org.w3c.dom.Element;
37import org.w3c.dom.NodeList;
38
39public class Authentication extends ServiceRack
40{
41 //Some useful constants
42 protected static final int USERNAME_MIN_LENGTH = 2;
43 protected static final int USERNAME_MAX_LENGTH = 30;
44 protected static final int PASSWORD_MIN_LENGTH = 3;
45 protected static final int PASSWORD_MAX_LENGTH = 64;
46
47 //Error codes
48 protected static final int NO_ERROR = 0;
49 protected static final int ERROR_NOT_LOGGED_IN = -2;
50 protected static final int ERROR_ADMIN_NOT_LOGGED_IN = -3;
51 protected static final int ERROR_COULD_NOT_GET_USER_INFO = -4;
52 protected static final int ERROR_USERNAME_NOT_SPECIFIED = -5;
53 protected static final int ERROR_USER_NOT_FOUND = -6;
54 protected static final int ERROR_SQL_EXCEPTION = -7;
55 protected static final int ERROR_INVALID_USERNAME = -8;
56 protected static final int ERROR_PASSWORD_NOT_ENTERED = -9;
57 protected static final int ERROR_PASSWORD_TOO_SHORT = -10;
58 protected static final int ERROR_PASSWORD_TOO_LONG = -11;
59 protected static final int ERROR_PASSWORD_USES_ILLEGAL_CHARACTERS = -12;
60 protected static final int ERROR_INCORRECT_PASSWORD = -13;
61 protected static final int ERROR_USER_ALREADY_EXISTS = -14;
62 protected static final int ERROR_ADDING_USER = -15;
63 protected static final int ERROR_REMOVING_USER = -16;
64 protected static final int ERROR_CAPTCHA_FAILED = -17;
65 protected static final int ERROR_CAPTCHA_MISSING = -18;
66 protected static final int ERROR_NOT_AUTHORISED = -19;
67 protected static final int ERROR_MISSING_PARAMS = -20;
68
69 protected static final HashMap<Integer, String> _errorKeyMap;
70 static
71 {
72 //Corresponding error message keys for looking up in ServiceRack dictionary
73 HashMap<Integer, String> errorKeyMap = new HashMap<Integer, String>();
74 errorKeyMap.put(ERROR_NOT_LOGGED_IN, "auth.error.not_logged_in");
75 errorKeyMap.put(ERROR_ADMIN_NOT_LOGGED_IN, "auth.error.admin_not_logged_in");
76 errorKeyMap.put(ERROR_COULD_NOT_GET_USER_INFO, "auth.error.could_not_get_user_info");
77 errorKeyMap.put(ERROR_USERNAME_NOT_SPECIFIED, "auth.error.username_not_specified");
78 errorKeyMap.put(ERROR_USER_NOT_FOUND, "auth.error.user_not_found");
79 errorKeyMap.put(ERROR_SQL_EXCEPTION, "auth.error.sql_exception");
80 errorKeyMap.put(ERROR_INVALID_USERNAME, "auth.error.invalid_username");
81 errorKeyMap.put(ERROR_PASSWORD_NOT_ENTERED, "auth.error.no_password");
82 errorKeyMap.put(ERROR_PASSWORD_TOO_SHORT, "auth.error.password_too_short");
83 errorKeyMap.put(ERROR_PASSWORD_TOO_LONG, "auth.error.password_too_long");
84 errorKeyMap.put(ERROR_PASSWORD_USES_ILLEGAL_CHARACTERS, "auth.error.password_illegal_chars");
85 errorKeyMap.put(ERROR_INCORRECT_PASSWORD, "auth.error.incorrect_password");
86 errorKeyMap.put(ERROR_USER_ALREADY_EXISTS, "auth.error.user_already_exists");
87 errorKeyMap.put(ERROR_ADDING_USER, "auth.error.add_user_error");
88 errorKeyMap.put(ERROR_REMOVING_USER, "auth.error.remove_user_error");
89 errorKeyMap.put(ERROR_CAPTCHA_FAILED, "auth.error.captcha_failed");
90 errorKeyMap.put(ERROR_CAPTCHA_MISSING, "auth.error.captcha_missing");
91 errorKeyMap.put(ERROR_NOT_AUTHORISED, "auth.error.not_authorised");
92 errorKeyMap.put(ERROR_MISSING_PARAMS, "auth.error.missing_params"); // ???
93 _errorKeyMap = errorKeyMap;
94 }
95
96 //Admin-required operations
97 protected static final String LIST_USERS = "ListUsers";
98 protected static final String PERFORM_ADD = "PerformAdd";
99 protected static final String PERFORM_EDIT = "PerformEdit";
100 protected static final String ADD_USER = "AddUser";
101 protected static final String EDIT_USER = "EditUser";
102 protected static final String PERFORM_DELETE_USER = "PerformDeleteUser";
103
104 protected static final ArrayList<String> _adminOpList;
105 static
106 {
107 ArrayList<String> opList = new ArrayList<String>();
108 opList.add(LIST_USERS);
109 opList.add(PERFORM_ADD);
110 opList.add(PERFORM_EDIT);
111 opList.add(EDIT_USER);
112 opList.add(PERFORM_DELETE_USER);
113
114 _adminOpList = opList;
115 }
116
117 //User-required operations
118 protected static final String ACCOUNT_SETTINGS = "AccountSettings";
119 protected static final String PERFORM_ACCOUNT_EDIT = "PerformAccEdit";
120 protected static final String PERFORM_RESET_PASSWORD = "PerformResetPassword";
121 protected static final String PERFORM_CHANGE_PASSWORD = "PerformChangePassword";
122 protected static final String PERFORM_RETRIEVE_PASSWORD = "PerformRetrievePassword";
123 protected static final ArrayList<String> _userOpList;
124 static
125 {
126 ArrayList<String> opList = new ArrayList<String>();
127 opList.add(ACCOUNT_SETTINGS);
128 opList.add(PERFORM_ACCOUNT_EDIT);
129 opList.add(PERFORM_RESET_PASSWORD);
130 opList.addAll(_adminOpList);
131 _userOpList = opList;
132 }
133
134 //Other operations
135 protected static final String REGISTER = "Register"; // displays the register page
136 protected static final String PERFORM_REGISTER = "PerformRegister"; // performs the registration action
137 protected static final String LOGIN = "Login";
138 protected static final String BLANK = "Info"; // a dummy page just for showing an error message
139 //the services on offer
140 protected static final String AUTHENTICATION_SERVICE = "Authentication";
141 protected static final String GET_USER_INFORMATION_SERVICE = "GetUserInformation";
142 protected static final String CHANGE_USER_EDIT_MODE_SERVICE = "ChangeUserEditMode";
143 protected static final String REMOTE_AUTHENTICATION_SERVICE = "RemoteAuthentication";
144
145 protected static boolean _derbyWrapperDoneForcedShutdown = false;
146
147 protected String _recaptchaSiteKey = null;
148 protected String _recaptchaSecretKey = null;
149
150 /** constructor */
151 public Authentication()
152 {
153 }
154
155 public void cleanUp()
156 {
157 super.cleanUp();
158
159 if (!_derbyWrapperDoneForcedShutdown)
160 {
161
162 // This boolean is used to ensure we always shutdown the derby server, even if it is never
163 // used by the Authentication server. This is because the Tomcat greenstone3.xml
164 // config file also specifies a connection to the database, which can result in the
165 // server being initialized when the servlet is first accessed. Note also,
166 // Authentication is a ServiceRack, meaning cleanUp() is called for each service
167 // supported, however we only need to shutdown the Derby server once. Again
168 // this boolean variable helps achieve this.
169
170 logger.info("Authentication Service performing forced shutdown of Derby Server ...");
171
172 DerbyWrapper.shutdownDatabaseServer();
173 _derbyWrapperDoneForcedShutdown = true;
174 }
175 }
176
177 public boolean configure(Element info, Element extra_info)
178 {
179 logger.info("Configuring Authentication...");
180 this.config_info = info;
181
182 // set up Authentication service info - for now just has name and type
183 Element authentication_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
184 authentication_service.setAttribute(GSXML.TYPE_ATT, "authen");
185 authentication_service.setAttribute(GSXML.NAME_ATT, AUTHENTICATION_SERVICE);
186 this.short_service_info.appendChild(authentication_service);
187
188 Element getUserInformation_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
189 getUserInformation_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
190 getUserInformation_service.setAttribute(GSXML.NAME_ATT, GET_USER_INFORMATION_SERVICE);
191 this.short_service_info.appendChild(getUserInformation_service);
192
193 Element changeEditMode_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
194 changeEditMode_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
195 changeEditMode_service.setAttribute(GSXML.NAME_ATT, CHANGE_USER_EDIT_MODE_SERVICE);
196 this.short_service_info.appendChild(changeEditMode_service);
197
198 Element remoteAuthentication_service = this.desc_doc.createElement(GSXML.SERVICE_ELEM);
199 remoteAuthentication_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
200 remoteAuthentication_service.setAttribute(GSXML.NAME_ATT, REMOTE_AUTHENTICATION_SERVICE);
201 this.short_service_info.appendChild(remoteAuthentication_service);
202
203
204 DerbyWrapper.createDatabaseIfNeeded();
205
206 NodeList recaptchaElems = info.getElementsByTagName("recaptcha");
207 for (int i = 0; i < recaptchaElems.getLength(); i++)
208 {
209 Element currentElem = (Element) recaptchaElems.item(i);
210 if (currentElem.getAttribute(GSXML.NAME_ATT) != null && currentElem.getAttribute(GSXML.NAME_ATT).equals("site_key"))
211 {
212 if (currentElem.getAttribute(GSXML.VALUE_ATT) != null)
213 {
214 _recaptchaSiteKey = currentElem.getAttribute(GSXML.VALUE_ATT);
215 }
216 }
217 else if (currentElem.getAttribute(GSXML.NAME_ATT) != null && currentElem.getAttribute(GSXML.NAME_ATT).equals("secret_key"))
218 {
219 if (currentElem.getAttribute(GSXML.VALUE_ATT) != null)
220 {
221 _recaptchaSecretKey = currentElem.getAttribute(GSXML.VALUE_ATT);
222 }
223 }
224 }
225
226 return true;
227 }
228
229 protected Element getServiceDescription(Document doc, String service_id, String lang, String subset)
230 {
231
232 Element authen_service = doc.createElement(GSXML.SERVICE_ELEM);
233
234 if (service_id.equals(AUTHENTICATION_SERVICE))
235 {
236 authen_service.setAttribute(GSXML.TYPE_ATT, "authen");
237 authen_service.setAttribute(GSXML.NAME_ATT, AUTHENTICATION_SERVICE);
238 }
239 else if (service_id.equals(GET_USER_INFORMATION_SERVICE))
240 {
241 authen_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
242 authen_service.setAttribute(GSXML.NAME_ATT, GET_USER_INFORMATION_SERVICE);
243 }
244 else if (service_id.equals(CHANGE_USER_EDIT_MODE_SERVICE))
245 {
246 authen_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
247 authen_service.setAttribute(GSXML.NAME_ATT, CHANGE_USER_EDIT_MODE_SERVICE);
248 }
249 else if (service_id.equals(REMOTE_AUTHENTICATION_SERVICE))
250 {
251 authen_service.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_PROCESS);
252 authen_service.setAttribute(GSXML.NAME_ATT, REMOTE_AUTHENTICATION_SERVICE);
253 }
254 else
255 {
256 return null;
257 }
258
259 if (service_id.equals(AUTHENTICATION_SERVICE) && (subset == null || subset.equals(GSXML.DISPLAY_TEXT_ELEM + GSXML.LIST_MODIFIER)))
260 {
261 authen_service.appendChild(GSXML.createDisplayTextElement(doc, GSXML.DISPLAY_TEXT_NAME, getServiceName(service_id, lang)));
262 authen_service.appendChild(GSXML.createDisplayTextElement(doc, GSXML.DISPLAY_TEXT_DESCRIPTION, getServiceDescription(service_id, lang)));
263 }
264 return authen_service;
265 }
266
267 protected String getServiceName(String service_id, String lang)
268 {
269 return getTextString(service_id + ".name", lang);
270 }
271
272 protected String getServiceSubmit(String service_id, String lang)
273 {
274 return getTextString(service_id + ".submit", lang);
275 }
276
277 protected String getServiceDescription(String service_id, String lang)
278 {
279 return getTextString(service_id + ".description", lang);
280 }
281 protected String getErrorTextString(int error_code, String lang) {
282 return getTextString(_errorKeyMap.get(error_code), lang);
283
284 }
285 protected Element processChangeUserEditMode(Element request)
286 {
287 // Create a new (empty) result message
288 Document result_doc = XMLConverter.newDOM();
289 Element result = result_doc.createElement(GSXML.RESPONSE_ELEM);
290
291 result.setAttribute(GSXML.FROM_ATT, CHANGE_USER_EDIT_MODE_SERVICE);
292 result.setAttribute(GSXML.TYPE_ATT, GSXML.REQUEST_TYPE_PROCESS);
293
294 Element paramList = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
295 if (paramList == null)
296 {
297 logger.error("ChangeUserEditMode request has no param list!!");
298 return result;
299 }
300
301 HashMap<String, Serializable> params = GSXML.extractParams(paramList, true);
302
303 String username = (String) params.get("username");
304 String editMode = (String) params.get("enabled");
305
306 if (!editMode.toLowerCase().equals("true") && !editMode.toLowerCase().equals("false"))
307 {
308 editMode = "false";
309 }
310
311 DerbyWrapper dw = openDatabase();
312 dw.addUserData(username, "USER_EDIT_ENABLED", editMode);
313 dw.closeDatabase();
314
315 return result;
316 }
317
318 /**
319 * This method replaces the gliserver.pl code for authenticating a user against the derby database
320 * gliserver.pl needed to instantiate its own JVM to access the derby DB, but the GS3 already has
321 * the Derby DB open and 2 JVMs are not allowed concurrent access to an open embedded Derby DB.
322 * Gliserver.pl now goes through this method (via ServletRealmCheck.java), thereby using the same
323 * connection to the DerbyDB. This method reproduces the same behaviour as gliserver.pl used to,
324 * by returning the user_groups on successful authentication, else returns the specific
325 * "Authentication failed" messages that glisever.pl would produce.
326 * http://remote-host-name:8383/greenstone3/library?a=s&sa=authenticated-ping&excerptid=gs_content&un=admin&pw=<PW>&col=demo
327 */
328 protected Element processRemoteAuthentication(Element request) {
329 //logger.info("*** Authentication::processRemoteAuthentication");
330
331 String message = "";
332
333 Element system = (Element) GSXML.getChildByTagName(request, GSXML.REQUEST_TYPE_SYSTEM);
334 String username = system.hasAttribute("username") ? system.getAttribute("username") : "";
335 String password = system.hasAttribute("password") ? system.getAttribute("password") : "";
336
337
338 // If we're not editing a collection then the user doesn't need to be in a particular group
339 String collection = system.hasAttribute("collection") ? system.getAttribute("collection") : "";
340
341
342 if(username.equals("") || password.equals("")) {
343 message = "Authentication failed: no (username or) password specified.";
344 //logger.error("*** Remote login failed. No username or pwd provided");
345 }
346 else {
347 String storedPassword = retrieveDataForUser(username, "password");
348 if(storedPassword != null && (password.equals(storedPassword) || hashPassword(password).equals(storedPassword))) {
349
350 // gliserver.pl used to return the groups when authentication succeeded
351 String groups = retrieveDataForUser(username, "groups"); //comma-separated list
352
353 if(collection.equals("")) {
354 message = groups;
355 } else {
356
357 if(groups.indexOf("all-collections-editor") != -1) { // Does this user have access to all collections?
358 message = groups;
359 } else if(groups.indexOf("personal-collections-editor") != -1 && collection.startsWith(username+"-")) { // Does this user have access to personal collections, and is this one?
360 message = groups;
361 } else if(groups.indexOf(collection+"-collection-editor") != -1) { // Does this user have access to this collection?
362 message = groups;
363 }
364 else {
365 message = "Authentication failed: user is not in the required group.";
366 //logger.error("*** Remote login failed. Groups did not match for the collection specified");
367 }
368 }
369
370 } else {
371
372 if(storedPassword == null) {
373 message = "Authentication failed: no account for user '" + username + "'";
374 //logger.error("*** Remote login failed. User not found or password not set for user.");
375 } else {
376 message = "Authentication failed: incorrect password.";
377 //logger.error("*** Remote login failed. Password did not match for user");
378 }
379 }
380 }
381 Document result_doc = XMLConverter.newDOM();
382 Element result = result_doc.createElement(GSXML.RESPONSE_ELEM);
383 result.setAttribute(GSXML.FROM_ATT, REMOTE_AUTHENTICATION_SERVICE);
384 result.setAttribute(GSXML.TYPE_ATT, GSXML.REQUEST_TYPE_PROCESS);
385 Element s = GSXML.createTextElement(result_doc, GSXML.STATUS_ELEM, message);
386 result.appendChild(s);
387 return result;
388 }
389
390 protected Element processGetUserInformation(Element request)
391 {
392 // Create a new (empty) result message
393 Document result_doc = XMLConverter.newDOM();
394 Element result = result_doc.createElement(GSXML.RESPONSE_ELEM);
395
396 result.setAttribute(GSXML.FROM_ATT, GET_USER_INFORMATION_SERVICE);
397 result.setAttribute(GSXML.TYPE_ATT, GSXML.REQUEST_TYPE_PROCESS);
398
399 String lang = request.getAttribute(GSXML.LANG_ATT);
400 Element paramList = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
401 if (paramList == null)
402 {
403 logger.error("GetUserInformation request has no param list");
404 return result;
405 }
406
407 HashMap<String, Serializable> params = GSXML.extractParams(paramList, true);
408
409 String username = (String) params.get("username");
410
411 if (username == null)
412 {
413 GSXML.addError(result, getErrorTextString(ERROR_USERNAME_NOT_SPECIFIED, lang));
414 return result;
415 }
416
417 DerbyWrapper derbyWrapper = openDatabase();
418
419 UserQueryResult userQueryResult = derbyWrapper.findUser(username);
420 String editEnabled = derbyWrapper.getUserData(username, "USER_EDIT_ENABLED");
421
422 Vector<UserTermInfo> terms = userQueryResult.getUserTerms();
423
424 if (terms.size() == 0)
425 {
426 GSXML.addError(result, getErrorTextString(ERROR_USER_NOT_FOUND, lang));
427 return result;
428 }
429
430 UserTermInfo userInfo = terms.get(0);
431 Element userInfoList = result_doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
432 result.appendChild(userInfoList);
433
434 Element usernameField = GSXML.createParameter(result_doc, "username", userInfo.username);
435 Element passwordField = GSXML.createParameter(result_doc, "password", userInfo.password);
436 Element groupsField = GSXML.createParameter(result_doc, "groups", userInfo.groups);
437 Element accountStatusField = GSXML.createParameter(result_doc, "accountstatus", userInfo.accountstatus);
438 Element commentField = GSXML.createParameter(result_doc, "comment", userInfo.comment);
439
440 if (editEnabled != null)
441 {
442 Element editEnabledElem = GSXML.createParameter(result_doc, "editEnabled", editEnabled);
443 userInfoList.appendChild(editEnabledElem);
444 }
445
446 userInfoList.appendChild(usernameField);
447 userInfoList.appendChild(passwordField);
448 userInfoList.appendChild(groupsField);
449 userInfoList.appendChild(accountStatusField);
450 userInfoList.appendChild(commentField);
451
452 derbyWrapper.closeDatabase();
453
454 return result;
455 }
456
457 protected Element processAuthentication(Element request)
458 {
459 checkAdminUserExists();
460
461 // Create a new (empty) result message
462 Document result_doc = XMLConverter.newDOM();
463 Element result = result_doc.createElement(GSXML.RESPONSE_ELEM);
464 result.setAttribute(GSXML.FROM_ATT, AUTHENTICATION_SERVICE);
465 result.setAttribute(GSXML.TYPE_ATT, GSXML.REQUEST_TYPE_PROCESS);
466
467 // Create an Authentication node put into the result
468 Element authenNode = result_doc.createElement(GSXML.AUTHEN_NODE_ELEM);
469 result.appendChild(authenNode);
470 result.appendChild(getCollectList(result_doc, this.site_home + File.separatorChar + "collect"));
471
472 // Create a service node added into the Authentication node
473 Element serviceNode = result_doc.createElement(GSXML.SERVICE_ELEM);
474 authenNode.appendChild(serviceNode);
475
476 // Get the parameters of the request
477 String lang = request.getAttribute(GSXML.LANG_ATT);
478 Element param_list = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
479 if (param_list == null)
480 {
481 serviceNode.setAttribute("operation", LOGIN);
482 logger.error("Authentication request has no param list");
483 return result; // Return the empty result
484 }
485 HashMap<String, Serializable> paramMap = GSXML.extractParams(param_list, false);
486 String op = (String) paramMap.get("authpage");
487 serviceNode.setAttribute("operation", op);
488
489 String username = null;
490 String groups = null;
491
492 Element userInformation = (Element) GSXML.getChildByTagName(request, GSXML.USER_INFORMATION_ELEM);
493 if (userInformation != null)
494 {
495 username = userInformation.getAttribute(GSXML.USERNAME_ATT);
496 groups = userInformation.getAttribute(GSXML.GROUPS_ATT);
497 }
498
499 if ((userInformation == null || username == null) && _userOpList.contains(op))
500 {
501 // its an operation that requires the user to be logged on - direct them to login page
502 serviceNode.setAttribute("operation", LOGIN);
503 GSXML.addError(result, getErrorTextString(ERROR_NOT_LOGGED_IN, lang));
504 return result;
505 }
506
507 if (_adminOpList.contains(op) && (groups == null || !groups.matches(".*\\badministrator\\b.*")))
508 {
509 // actually, the user needs to be an admin user and they are not
510 serviceNode.setAttribute("operation", LOGIN);
511 GSXML.addError(result, getErrorTextString(ERROR_ADMIN_NOT_LOGGED_IN, lang));
512 return result;
513 }
514
515 if (op.equals(LIST_USERS))
516 {
517 int error = addUserInformationToNode(null, serviceNode);
518 if (error != NO_ERROR)
519 {
520 serviceNode.setAttribute("operation", BLANK);
521 GSXML.addError(result, getErrorTextString(error, lang));
522 }
523 return result;
524
525 }
526
527 if (op.equals(PERFORM_ADD))
528 {
529 String newUsername = (String) paramMap.get("username");
530 String newPassword = (String) paramMap.get("password");
531 String newGroups = (String) paramMap.get("groups");
532 String newStatus = (String) paramMap.get("status");
533 String newComment = (String) paramMap.get("comment");
534 String newEmail = (String) paramMap.get("email");
535
536 //Check the given user name
537 int error;
538 if ((error = checkUsername(newUsername)) != NO_ERROR)
539 {
540 GSXML.addError(result, getErrorTextString(error, lang));
541 return result;
542 }
543
544 //Check the given password
545 if ((error = checkPassword(newPassword)) != NO_ERROR)
546 {
547 GSXML.addError(result, getErrorTextString(error, lang));
548 return result;
549 }
550
551 newPassword = hashPassword(newPassword);
552
553 error = addUser(newUsername, newPassword, newGroups, newStatus, newComment, newEmail);
554 if (error != NO_ERROR)
555 {
556 serviceNode.setAttribute("operation", ADD_USER);
557 GSXML.addError(result, getErrorTextString(error, lang));
558 }
559 else
560 {
561 addUserInformationToNode(null, serviceNode);
562 serviceNode.setAttribute("operation", LIST_USERS);
563 }
564 return result;
565 }
566
567 if (op.equals(PERFORM_REGISTER))
568 {
569 String newUsername = (String) paramMap.get("username");
570 String newPassword = (String) paramMap.get("password");
571 String newEmail = (String) paramMap.get("email");
572
573 //Check the given user name
574 int error;
575 if ((error = checkUsername(newUsername)) != NO_ERROR)
576 {
577 GSXML.addError(result, getErrorTextString(error, lang));
578 return result;
579 }
580
581 //Check the given password
582 if ((error = checkPassword(newPassword)) != NO_ERROR)
583 {
584 GSXML.addError(result, getErrorTextString(error, lang));
585 return result;
586 }
587
588 newPassword = hashPassword(newPassword);
589
590 // check the recaptcha
591 if (_recaptchaSiteKey != null && _recaptchaSecretKey.length() > 0) {
592
593 String user_response = (String) paramMap.get("g-recaptcha-response");
594 int recaptcha_error = verifyRecaptcha(_recaptchaSecretKey, user_response);
595 if (recaptcha_error != NO_ERROR) {
596 serviceNode.setAttribute("operation", REGISTER);
597 GSXML.addError(result, getErrorTextString(recaptcha_error, lang));
598 return result;
599 }
600 }
601
602 error = addUser(newUsername, newPassword, "", "true", "", newEmail);
603 if (error != NO_ERROR)
604 {
605 serviceNode.setAttribute("operation", REGISTER);
606 GSXML.addError(result, getErrorTextString(error, lang));
607 }
608 return result;
609 }
610
611 else if (op.equals(PERFORM_EDIT))
612 {
613 String previousUsername = (String) paramMap.get("prevUsername");
614 String newUsername = (String) paramMap.get("newUsername");
615 String newPassword = (String) paramMap.get("password");
616 String newGroups = (String) paramMap.get("groups");
617 String newStatus = (String) paramMap.get("status");
618 String newComment = (String) paramMap.get("comment");
619 String newEmail = (String) paramMap.get("newEmail");
620
621 //Check the given user name
622 int error;
623 if ((error = checkUsername(newUsername)) != NO_ERROR)
624 {
625 GSXML.addError(result, getErrorTextString(error, lang));
626 return result;
627 }
628
629 if (newPassword == null)
630 {
631 newPassword = retrieveDataForUser(previousUsername, "password");
632 }
633 else
634 {
635 //Check the given password
636 if ((error = checkPassword(newPassword)) != NO_ERROR)
637 {
638 GSXML.addError(result, getErrorTextString(error, lang));
639 return result;
640 }
641
642 newPassword = hashPassword(newPassword);
643 }
644
645 error = removeUser(previousUsername);
646 if (error != NO_ERROR)
647 {
648 if (error == ERROR_USERNAME_NOT_SPECIFIED)
649 {
650 addUserInformationToNode(null, serviceNode);
651 serviceNode.setAttribute("operation", LIST_USERS);
652 }
653 else
654 {
655 serviceNode.setAttribute("operation", EDIT_USER);
656 GSXML.addError(result, getErrorTextString(error, lang));
657 }
658 return result;
659 }
660
661 error = addUser(newUsername, newPassword, newGroups, newStatus, newComment, newEmail);
662 if (error != NO_ERROR)
663 {
664 serviceNode.setAttribute("operation", EDIT_USER);
665 GSXML.addError(result, getErrorTextString(error, lang));
666 }
667 else
668 {
669 addUserInformationToNode(null, serviceNode);
670 serviceNode.setAttribute("operation", LIST_USERS);
671 }
672 }
673 // this operation is done by a user when editing their own details. Should not return userNode info.
674 else if (op.equals(PERFORM_ACCOUNT_EDIT))
675 {
676 String previousUsername = (String) paramMap.get("prevUsername");
677 String newUsername = (String) paramMap.get("newUsername");
678 String oldPassword = (String) paramMap.get("oldPassword");
679 String newPassword = (String) paramMap.get("newPassword");
680 String newEmail = (String) paramMap.get("newEmail");
681
682 //Make sure the user name does not already exist
683 if (!previousUsername.equals(newUsername) && checkUserExists(newUsername))
684 {
685 addUserInformationToNode(previousUsername, serviceNode);
686 serviceNode.setAttribute("operation", ACCOUNT_SETTINGS);
687 GSXML.addError(result, getErrorTextString(ERROR_USER_ALREADY_EXISTS, lang));
688 return result;
689 }
690
691 String prevPassword = retrieveDataForUser(previousUsername, "password");
692
693 if (newPassword != null)
694 {
695 oldPassword = hashPassword(oldPassword);
696
697 if (oldPassword == null || !oldPassword.equals(prevPassword))
698 {
699 addUserInformationToNode(previousUsername, serviceNode);
700 serviceNode.setAttribute("operation", ACCOUNT_SETTINGS);
701 GSXML.addError(result, getErrorTextString(ERROR_INCORRECT_PASSWORD, lang), "INCORRECT_PASSWORD");
702 return result;
703 }
704
705 //Check the given password
706 int error;
707 if ((error = checkPassword(newPassword)) != NO_ERROR)
708 {
709 addUserInformationToNode(previousUsername, serviceNode);
710 serviceNode.setAttribute("operation", ACCOUNT_SETTINGS);
711 GSXML.addError(result, getErrorTextString(error, lang));
712 return result;
713 }
714
715 newPassword = hashPassword(newPassword);
716 }
717 else
718 {
719 newPassword = prevPassword;
720 }
721
722 //Check the given user name
723 int error;
724 if ((error = checkUsername(newUsername)) != NO_ERROR)
725 {
726 addUserInformationToNode(previousUsername, serviceNode);
727 serviceNode.setAttribute("operation", ACCOUNT_SETTINGS);
728 GSXML.addError(result, getErrorTextString(error, lang));
729 return result;
730 }
731
732 String prevGroups = retrieveDataForUser(previousUsername, "groups");
733 String prevStatus = retrieveDataForUser(previousUsername, "status");
734 String prevComment = retrieveDataForUser(previousUsername, "comment");
735
736 error = removeUser(previousUsername);
737 if (error != NO_ERROR)
738 {
739 addUserInformationToNode(previousUsername, serviceNode);
740 serviceNode.setAttribute("operation", ACCOUNT_SETTINGS);
741 GSXML.addError(result, getErrorTextString(error, lang));
742 return result;
743 }
744
745 error = addUser(newUsername, newPassword, prevGroups, prevStatus, prevComment, newEmail);
746 if (error != NO_ERROR)
747 {
748 serviceNode.setAttribute("operation", ACCOUNT_SETTINGS);
749 GSXML.addError(result, getErrorTextString(error, lang));
750 }
751
752 addUserInformationToNode(newUsername, serviceNode);
753 serviceNode.setAttribute("operation", ACCOUNT_SETTINGS);
754 GSXML.addError(result, getTextString("auth.success.account_settings", lang));
755
756 }
757 else if (op.equals(PERFORM_RETRIEVE_PASSWORD))
758 {
759
760 }
761 else if (op.equals(PERFORM_CHANGE_PASSWORD))
762 {
763 serviceNode.setAttribute("operation", PERFORM_CHANGE_PASSWORD);
764 String user_name = (String) paramMap.get("username");
765 String oldPassword = (String) paramMap.get("oldPassword");
766 String newPassword = (String) paramMap.get("newPassword");
767 if (user_name == null || oldPassword == null || newPassword == null)
768 {
769 GSXML.addError(result, getErrorTextString(ERROR_MISSING_PARAMS, lang));
770 return result;
771 }
772
773 String prevPassword = retrieveDataForUser(user_name, "password");
774 if (!hashPassword(oldPassword).equals(prevPassword))
775 {
776 addUserInformationToNode(user_name, serviceNode);
777 GSXML.addError(result, getErrorTextString(ERROR_INCORRECT_PASSWORD, lang), "INCORRECT_PASSWORD");
778 return result;
779 }
780
781 //Check the given password
782 int error;
783 if ((error = checkPassword(newPassword)) != NO_ERROR)
784 {
785 GSXML.addError(result, getErrorTextString(error, lang));
786 return result;
787 }
788
789 DerbyWrapper derbyWrapper = openDatabase();
790 String chpa_groups = retrieveDataForUser(user_name, "groups");
791 String chpa_comment = "password_changed_by_user";
792 String info = derbyWrapper.modifyUserInfo(user_name, hashPassword(newPassword), chpa_groups, null, chpa_comment, null);
793 derbyWrapper.closeDatabase();
794 if (info != "succeed")
795 {//see DerbyWrapper.modifyUserInfo
796 GSXML.addError(result, info);
797 return result;
798 }
799 }
800 else if (op.equals(EDIT_USER))
801 {
802 String editUsername = (String) paramMap.get("username");
803 int error = addUserInformationToNode(editUsername, serviceNode);
804 if (error != NO_ERROR)
805 {
806 GSXML.addError(result, getErrorTextString(error, lang));
807 }
808 }
809 else if (op.equals(ACCOUNT_SETTINGS))
810 {
811 String editUsername = (String) paramMap.get("username");
812
813 if (editUsername == null)
814 {
815 serviceNode.setAttribute("operation", "");
816 GSXML.addError(result, getErrorTextString(ERROR_USERNAME_NOT_SPECIFIED, lang));
817 return result;
818 }
819
820 if (!editUsername.equals(username))
821 {
822 serviceNode.setAttribute("operation", LOGIN);
823 GSXML.addError(result, getErrorTextString(ERROR_NOT_AUTHORISED, lang));
824 return result;
825 }
826 int error = addUserInformationToNode(editUsername, serviceNode);
827 if (error != NO_ERROR)
828 {
829 GSXML.addError(result, getErrorTextString(error, lang));
830 }
831 }
832 else if (op.equals(PERFORM_RESET_PASSWORD))
833 {
834 String passwordResetUser = (String) paramMap.get("username");
835
836 String newPassword = UUID.randomUUID().toString();
837 newPassword = newPassword.substring(0, newPassword.indexOf("-"));
838
839 String email = retrieveDataForUser(passwordResetUser, "email");
840 String from = "[email protected]";
841 String host = request.getAttribute("remoteAddress");
842
843 //TODO: FINISH THIS
844 }
845 else if (op.equals(REGISTER))
846 {
847 }
848 else if (op.equals(PERFORM_DELETE_USER))
849 {
850 String usernameToDelete = (String) paramMap.get("username");
851 int error = removeUser(usernameToDelete);
852 if (error != NO_ERROR)
853 {
854 GSXML.addError(result, getErrorTextString(error, lang));
855 }
856 addUserInformationToNode(null, serviceNode);
857 serviceNode.setAttribute("operation", LIST_USERS);
858 }
859
860 return result;
861 }
862
863 public int checkUsernameAndPassword(String username, String password)
864 {
865 int uResult = checkUsername(username);
866 int pResult = checkPassword(password);
867
868 return (uResult != NO_ERROR ? uResult : (pResult != NO_ERROR ? pResult : NO_ERROR));
869 }
870
871 public int checkUsername(String username)
872 {
873 //Check the given user name
874 if ((username == null) || (username.length() < USERNAME_MIN_LENGTH) || (username.length() > USERNAME_MAX_LENGTH) || (!(Pattern.matches("[a-zA-Z0-9//_//.]+", username))))
875 {
876 return ERROR_INVALID_USERNAME;
877 }
878 return NO_ERROR;
879 }
880
881 public int checkPassword(String password)
882 {
883 //Check the given password
884 if (password == null)
885 {
886 return ERROR_PASSWORD_NOT_ENTERED;
887 }
888 else if (password.length() < PASSWORD_MIN_LENGTH)
889 {
890 return ERROR_PASSWORD_TOO_SHORT;
891 }
892 else if (password.length() > PASSWORD_MAX_LENGTH)
893 {
894 return ERROR_PASSWORD_TOO_LONG;
895 }
896 else if (!(Pattern.matches("[\\p{ASCII}]+", password)))
897 {
898 return ERROR_PASSWORD_USES_ILLEGAL_CHARACTERS;
899 }
900 return NO_ERROR;
901 }
902
903 public static String hashPassword(String password)
904 {
905 return DigestUtils.sha1Hex(password);
906 }
907
908 public int verifyRecaptcha(String secret_key, String user_response) {
909
910 if (user_response == null || user_response.length() == 0) {
911 return ERROR_CAPTCHA_MISSING;
912 }
913
914 try{
915
916 URL obj = new URL("https://www.google.com/recaptcha/api/siteverify");
917 HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
918
919 // add reuqest header
920 con.setRequestMethod("POST");
921 con.setRequestProperty("User-Agent", "Mozilla/5.0");
922 con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
923
924 String postParams = "secret=" + secret_key + "&response="
925 + user_response;
926
927 // Send post request
928 con.setDoOutput(true);
929 DataOutputStream wr = new DataOutputStream(con.getOutputStream());
930 wr.writeBytes(postParams);
931 wr.flush();
932 wr.close();
933
934 int responseCode = con.getResponseCode();
935 //System.out.println("\nSending 'POST' request to URL : https://www.google.com/recaptcha/api/siteverify");// + url);
936 //System.out.println("Post parameters : " + postParams);
937 //System.out.println("Response Code : " + responseCode);
938
939 BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
940 String inputLine;
941 StringBuffer response = new StringBuffer();
942
943 while ((inputLine = in.readLine()) != null) {
944 response.append(inputLine);
945 }
946 in.close();
947
948 // print result
949 //System.out.println(response.toString());
950
951 JSONObject json_obj = new JSONObject(response.toString());
952 boolean res = json_obj.getBoolean("success");
953 if (res) {
954 return NO_ERROR;
955 } else {
956 return ERROR_CAPTCHA_FAILED;
957 }
958 }catch(Exception e){
959 e.printStackTrace();
960 return ERROR_CAPTCHA_FAILED;
961 }
962
963 }
964 // This method can also be used for printing out the password in hex (in case
965 // the password used the UTF-8 Charset), or the hex values in any unicode string.
966 // From http://stackoverflow.com/questions/923863/converting-a-string-to-hexadecimal-in-java
967 public static String toHex(String arg)
968 {
969 try
970 {
971 return String.format("%x", new BigInteger(arg.getBytes("US-ASCII"))); // set to same charset as used by hashPassword
972 }
973 catch (Exception e)
974 { // UnsupportedEncodingException
975 e.printStackTrace();
976 }
977 return "Unable to print";
978 }
979
980 private void checkAdminUserExists()
981 {
982 DerbyWrapper derbyWrapper = openDatabase();
983 UserQueryResult userQueryResult = derbyWrapper.findUser(null, null);
984 derbyWrapper.closeDatabase();
985
986 if (userQueryResult != null)
987 {
988 Vector userInfo = userQueryResult.users;
989
990 boolean adminFound = false;
991 for (int i = 0; i < userQueryResult.getSize(); i++)
992 {
993 if (((UserTermInfo) userInfo.get(i)).groups != null && ((UserTermInfo) userInfo.get(i)).groups.matches(".*\\badministrator\\b.*"))
994 {
995 adminFound = true;
996 }
997 }
998
999 if (!adminFound)
1000 {
1001 addUser("admin", "admin", "administrator", "true", "Change the password for this account as soon as possible", "");
1002 }
1003 }
1004 }
1005
1006 private DerbyWrapper openDatabase()
1007 {
1008 // check the usersDb database, if it isn't existing, check the etc dir, create the etc dir if it isn't existing, then create the user database and add a "admin" user
1009 String usersDB_dir = GlobalProperties.getGSDL3Home() + File.separatorChar + "etc" + File.separatorChar + "usersDB";
1010 DerbyWrapper derbyWrapper = new DerbyWrapper(usersDB_dir);
1011 return derbyWrapper;
1012 }
1013
1014 private int addUserInformationToNode(String username, Element serviceNode)
1015 {
1016 DerbyWrapper derbyWrapper = openDatabase();
1017 UserQueryResult userQueryResult = derbyWrapper.findUser(username, null);
1018 derbyWrapper.closeDatabase();
1019
1020 if (userQueryResult != null)
1021 {
1022 Element user_node = getUserNodeList(serviceNode.getOwnerDocument(), userQueryResult);
1023 serviceNode.appendChild(user_node);
1024 return NO_ERROR;
1025 }
1026
1027 return ERROR_COULD_NOT_GET_USER_INFO;
1028 }
1029
1030 private int removeUser(String username)
1031 {
1032 if (username == null)
1033 {
1034 return ERROR_USERNAME_NOT_SPECIFIED;
1035 }
1036
1037 DerbyWrapper derbyWrapper = openDatabase();
1038 boolean success = derbyWrapper.deleteUser(username);
1039 derbyWrapper.closeDatabase();
1040
1041 if (success)
1042 {
1043 return NO_ERROR;
1044 }
1045
1046 return ERROR_REMOVING_USER;
1047 }
1048
1049 private int addUser(String newUsername, String newPassword, String newGroups, String newStatus, String newComment, String newEmail)
1050 {
1051 newGroups = newGroups.replaceAll(" ", "");
1052
1053 //Check if the user already exists
1054 DerbyWrapper derbyWrapper = openDatabase();
1055 UserQueryResult userQueryResult = derbyWrapper.findUser(newUsername, null);
1056
1057 if (userQueryResult != null)
1058 {
1059 derbyWrapper.closeDatabase();
1060 return ERROR_USER_ALREADY_EXISTS;
1061 }
1062 else
1063 {
1064 boolean success = derbyWrapper.addUser(newUsername, newPassword, newGroups, newStatus, newComment, newEmail);
1065 derbyWrapper.closeDatabase();
1066
1067 if (!success)
1068 {
1069 return ERROR_ADDING_USER;
1070 }
1071 }
1072
1073 return NO_ERROR;
1074 }
1075
1076 private boolean checkUserExists(String username)
1077 {
1078 boolean check_status = false;
1079
1080 DerbyWrapper derbyWrapper = openDatabase();
1081 try
1082 {
1083 UserQueryResult result = derbyWrapper.findUser(username);
1084
1085 if (result != null)
1086 {
1087 check_status = true;
1088 }
1089
1090 }
1091 catch (Exception ex)
1092 {
1093 // some error occurred accessing the database
1094 ex.printStackTrace();
1095 }
1096 derbyWrapper.closeDatabase();
1097
1098 return check_status;
1099 }
1100
1101 private String retrieveDataForUser(String username, String dataType)
1102 {
1103 openDatabase();
1104
1105 String data = null;
1106
1107 try
1108 {
1109 DerbyWrapper derbyWrapper = openDatabase();
1110 UserQueryResult result = derbyWrapper.findUser(username);
1111 derbyWrapper.closeDatabase();
1112 Vector userInfo = result.users;
1113
1114 for (int i = 0; i < result.getSize(); i++)
1115 {
1116 if (dataType.equals("password"))
1117 {
1118 data = ((UserTermInfo) userInfo.get(i)).password;
1119 break;
1120 }
1121 else if (dataType.equals("groups"))
1122 {
1123 data = ((UserTermInfo) userInfo.get(i)).groups;
1124 break;
1125 }
1126 else if (dataType.equals("status"))
1127 {
1128 data = ((UserTermInfo) userInfo.get(i)).accountstatus;
1129 break;
1130 }
1131 else if (dataType.equals("comment"))
1132 {
1133 data = ((UserTermInfo) userInfo.get(i)).comment;
1134 break;
1135 }
1136 else if (dataType.equals("email"))
1137 {
1138 data = ((UserTermInfo) userInfo.get(i)).email;
1139 break;
1140 }
1141 }
1142 }
1143 catch (Exception ex)
1144 {
1145 ex.printStackTrace();
1146 }
1147
1148 return data;
1149 }
1150
1151 private Element getUserNodeList(Document doc, UserQueryResult userQueryResult)
1152 {
1153 Element user_list_node = doc.createElement(GSXML.USER_NODE_ELEM + GSXML.LIST_MODIFIER);
1154
1155 Vector userInfo = userQueryResult.users;
1156
1157 for (int i = 0; i < userQueryResult.getSize(); i++)
1158 {
1159 Element user_node = doc.createElement(GSXML.USER_NODE_ELEM);
1160 String username = ((UserTermInfo) userInfo.get(i)).username;
1161 String groups = ((UserTermInfo) userInfo.get(i)).groups;
1162 String accountstatus = ((UserTermInfo) userInfo.get(i)).accountstatus;
1163 String comment = ((UserTermInfo) userInfo.get(i)).comment;
1164 String email = ((UserTermInfo) userInfo.get(i)).email;
1165 user_node.setAttribute("username", username);
1166 user_node.setAttribute("groups", groups);
1167 user_node.setAttribute("status", accountstatus);
1168 user_node.setAttribute("comment", comment);
1169 user_node.setAttribute("email", email);
1170
1171 user_list_node.appendChild(user_node);
1172 }
1173 return user_list_node;
1174 }
1175
1176 private Element getCollectList(Document doc, String collect)
1177 {
1178 Element collect_list_node = doc.createElement(GSXML.COLLECTION_ELEM + GSXML.LIST_MODIFIER);
1179 File[] collect_dir = (new File(collect)).listFiles();
1180 if (collect_dir != null && collect_dir.length > 0)
1181 {
1182 for (int i = 0; i < collect_dir.length; i++)
1183 {
1184 if (collect_dir[i].isDirectory() && (!collect_dir[i].getName().startsWith(".svn")))
1185 {
1186 Element collect_node = doc.createElement(GSXML.COLLECTION_ELEM);
1187 collect_node.setAttribute(GSXML.NAME_ATT, collect_dir[i].getName());
1188 collect_list_node.appendChild(collect_node);
1189 }
1190 }
1191 }
1192 return collect_list_node;
1193 }
1194
1195
1196 // main() method - calls hashPassword() on any String argument, printing this to stdout
1197 // This main() is invoked by gliserver.pl perl code to encrypt passwords identically to Java code.
1198 public static void main(String[] args)
1199 {
1200 if (args.length < 1)
1201 {
1202 System.err.println("Usage: Authentication <string to encrypt>");
1203 System.exit(-1);
1204 }
1205 // just hash the first argument
1206 String hash = Authentication.hashPassword(args[0]);
1207 System.out.println(hash);
1208 }
1209}
Note: See TracBrowser for help on using the repository browser.