001: //userDB.java
002: //-------------------------------------
003: //part of YACY
004: //
005: //(C) 2005, 2006 by Martin Thelian
006: // Alexander Schier
007: //
008: //last change: $LastChangedDate: 2008-01-29 10:12:48 +0000 (Di, 29 Jan 2008) $ by $LastChangedBy: orbiter $
009: //Revision: $LastChangedRevision: 4414 $
010: //
011: //This program is free software; you can redistribute it and/or modify
012: //it under the terms of the GNU General Public License as published by
013: //the Free Software Foundation; either version 2 of the License, or
014: //(at your option) any later version.
015: //
016: //This program is distributed in the hope that it will be useful,
017: //but WITHOUT ANY WARRANTY; without even the implied warranty of
018: //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
019: //GNU General Public License for more details.
020: //
021: //You should have received a copy of the GNU General Public License
022: //along with this program; if not, write to the Free Software
023: //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024: //
025: //Using this software in any meaning (reading, learning, copying, compiling,
026: //running) means that you agree that the Author(s) is (are) not responsible
027: //for cost, loss of data or any harm that may be caused directly or indirectly
028: //by usage of this softare or this documentation. The usage of this software
029: //is on your own risk. The installation and usage (starting/running) of this
030: //software may allow other people or application to access your computer and
031: //any attached devices and is highly dependent on the configuration of the
032: //software which must be done by the user of the software; the author(s) is
033: //(are) also not responsible for proper configuration and usage of the
034: //software, even if provoked by documentation provided together with
035: //the software.
036: //
037: //Any changes to this file according to the GPL as documented in the file
038: //gpl.txt aside this file in the shipment you received can be done to the
039: //lines that follows this copyright notice here, but changes must not be
040: //done inside the copyright notive above. A re-distribution must contain
041: //the intact and unchanged copyright notice.
042: //Contributions and changes to the program code must be marked as such.
043:
044: package de.anomic.data;
045:
046: import java.io.File;
047: import java.io.IOException;
048: import java.util.Calendar;
049: import java.util.Date;
050: import java.util.HashMap;
051: import java.util.HashSet;
052: import java.util.Iterator;
053: import java.util.Map;
054: import java.util.Random;
055:
056: import de.anomic.http.httpHeader;
057: import de.anomic.kelondro.kelondroBase64Order;
058: import de.anomic.kelondro.kelondroCloneableIterator;
059: import de.anomic.kelondro.kelondroDyn;
060: import de.anomic.kelondro.kelondroException;
061: import de.anomic.kelondro.kelondroMapObjects;
062: import de.anomic.kelondro.kelondroNaturalOrder;
063: import de.anomic.server.serverCodings;
064:
065: public final class userDB {
066:
067: public static final int USERNAME_MAX_LENGTH = 128;
068: public static final int USERNAME_MIN_LENGTH = 4;
069:
070: kelondroMapObjects userTable;
071: private final File userTableFile;
072: private long preloadTime;
073: private HashMap<String, String> ipUsers = new HashMap<String, String>();
074: private HashMap<String, Object> cookieUsers = new HashMap<String, Object>();
075:
076: public userDB(File userTableFile, long preloadTime) {
077: this .userTableFile = userTableFile;
078: this .preloadTime = preloadTime;
079: userTableFile.getParentFile().mkdirs();
080: this .userTable = new kelondroMapObjects(new kelondroDyn(
081: userTableFile, true, true, preloadTime, 128, 256, '_',
082: kelondroNaturalOrder.naturalOrder, true, false, false),
083: 10);
084: }
085:
086: void resetDatabase() {
087: // deletes the database and creates a new one
088: if (userTable != null)
089: userTable.close();
090: if (!(userTableFile.delete()))
091: throw new RuntimeException("cannot delete user database");
092: userTableFile.getParentFile().mkdirs();
093: userTable = new kelondroMapObjects(new kelondroDyn(
094: userTableFile, true, true, preloadTime, 256, 512, '_',
095: kelondroNaturalOrder.naturalOrder, true, false, false),
096: 10);
097: }
098:
099: public void close() {
100: userTable.close();
101: }
102:
103: public int size() {
104: return userTable.size();
105: }
106:
107: public void removeEntry(String hostName) {
108: try {
109: userTable.remove(hostName.toLowerCase());
110: } catch (IOException e) {
111: }
112: }
113:
114: public Entry getEntry(String userName) {
115: if (userName.length() > 128) {
116: userName = userName.substring(0, 127);
117: }
118: HashMap<String, String> record = userTable.getMap(userName);
119: if (record == null)
120: return null;
121: return new Entry(userName, record);
122: }
123:
124: public Entry createEntry(String userName,
125: HashMap<String, String> userProps)
126: throws IllegalArgumentException {
127: Entry entry = new Entry(userName, userProps);
128: return entry;
129: }
130:
131: public String addEntry(Entry entry) {
132: try {
133: userTable.set(entry.userName, entry.mem);
134: return entry.userName;
135: } catch (IOException e) {
136: return null;
137: }
138: }
139:
140: /**
141: * use a ProxyAuth String to authenticate a user
142: * @param auth a base64 Encoded String, which contains "username:pw".
143: */
144: public Entry proxyAuth(String auth) {
145: if (auth == null)
146: return null;
147: Entry entry = null;
148: auth = auth.trim().substring(6);
149:
150: try {
151: auth = kelondroBase64Order.standardCoder.decodeString(auth,
152: "de.anomic.data.userDB.proxyAuth()");
153: } catch (RuntimeException e) {
154: } //no valid Base64
155: String[] tmp = auth.split(":");
156: if (tmp.length == 2) {
157: entry = this .passwordAuth(tmp[0], tmp[1]);
158: if (entry != null) {
159: //return entry;
160: } else { //wrong/no auth, so auth is removed from browser
161: /*FIXME: This cannot work
162: try{
163: entry.setProperty(Entry.LOGGED_OUT, "false");
164: }catch(IOException e){}*/
165: }
166: return entry;
167: }
168: return null;
169: }
170:
171: public Entry getUser(httpHeader header) {
172: return getUser((String) header.get(httpHeader.AUTHORIZATION),
173: (String) header.get("CLIENTIP"), header
174: .getHeaderCookies());
175: }
176:
177: public Entry getUser(String auth, String ip, String cookies) {
178: Entry entry = null;
179: if (auth != null)
180: entry = proxyAuth(auth);
181: if (entry == null)
182: entry = cookieAuth(cookies);
183: return entry;
184: }
185:
186: /**
187: * determinate, if a user has Adminrights from a authorisation http-headerfield
188: * it tests both userDB and oldstyle adminpw.
189: * @param auth the http-headerline for authorisation
190: */
191: public boolean hasAdminRight(String auth, String ip, String cookies) {
192: Entry entry = getUser(auth, ip, cookies);
193: if (entry != null)
194: return entry.hasAdminRight();
195: else if (entry != null && cookieAdminAuth(cookies))
196: return entry.hasAdminRight();
197: else
198: return false;
199: }
200:
201: /**
202: * use a ProxyAuth String to authenticate a user and save the ip/username for ipAuth
203: * @param auth a base64 Encoded String, which contains "username:pw".
204: * @param ip an ip.
205: */
206: public Entry proxyAuth(String auth, String ip) {
207: Entry entry = proxyAuth(auth);
208: if (entry == null) {
209: return null;
210: }
211: entry.updateLastAccess(false);
212: this .ipUsers.put(ip, entry.getUserName());
213: return entry;
214: }
215:
216: /**
217: * authenticate a user by ip, if he had used proxyAuth in the last 10 Minutes
218: * @param ip the IP of the User
219: */
220: public Entry ipAuth(String ip) {
221: if (this .ipUsers.containsKey(ip)) {
222: String user = this .ipUsers.get(ip);
223: Entry entry = this .getEntry(user);
224: Long entryTimestamp = entry.getLastAccess();
225: if (entryTimestamp == null
226: || (System.currentTimeMillis() - entryTimestamp
227: .longValue()) > (1000 * 60 * 10)) { //no timestamp or older than 10 Minutes
228: return null;
229: }
230: return entry; //All OK
231: }
232: return null;
233: }
234:
235: public Entry passwordAuth(String user, String password) {
236: Entry entry = this .getEntry(user);
237: if (entry != null
238: && entry.getMD5EncodedUserPwd().equals(
239: serverCodings.encodeMD5Hex(user + ":"
240: + password))) {
241: if (entry.isLoggedOut()) {
242: try {
243: entry.setProperty(Entry.LOGGED_OUT, "false");
244: } catch (IOException e) {
245: }
246: return null;
247: }
248: return entry;
249: }
250: return null;
251: }
252:
253: public Entry passwordAuth(String user, String password, String ip) {
254: Entry entry = passwordAuth(user, password);
255: if (entry == null) {
256: return null;
257: }
258: entry.updateLastAccess(false);
259: this .ipUsers.put(ip, entry.getUserName()); //XXX: This is insecure. TODO: use cookieauth
260: return entry;
261: }
262:
263: public Entry md5Auth(String user, String md5) {
264: Entry entry = this .getEntry(user);
265: if (entry != null && entry.getMD5EncodedUserPwd().equals(md5)) {
266: if (entry.isLoggedOut()) {
267: try {
268: entry.setProperty(Entry.LOGGED_OUT, "false");
269: } catch (IOException e) {
270: }
271: return null;
272: }
273: return entry;
274: }
275: return null;
276: }
277:
278: public Entry cookieAuth(String cookieString) {
279: String token = getLoginToken(cookieString);
280: if (cookieUsers.containsKey(token)) {
281: Object entry = cookieUsers.get(token);
282: if (entry instanceof Entry) //String would mean static Admin
283: return (Entry) entry;
284: }
285: return null;
286: }
287:
288: public boolean cookieAdminAuth(String cookieString) {
289: String token = getLoginToken(cookieString);
290: if (cookieUsers.containsKey(token)) {
291: Object entry = cookieUsers.get(token);
292: if (entry instanceof String && entry.equals("admin"))
293: return true;
294: }
295: return false;
296: }
297:
298: public String getCookie(Entry entry) {
299: Random r = new Random();
300: String token = Long.toString(Math.abs(r.nextLong()), 36);
301: cookieUsers.put(token, entry);
302: return token;
303: }
304:
305: public String getAdminCookie() {
306: Random r = new Random();
307: String token = Long.toString(Math.abs(r.nextLong()), 36);
308: cookieUsers.put(token, "admin");
309: return token;
310: }
311:
312: public static String getLoginToken(String cookies) {
313: String[] cookie = cookies.split(";"); //TODO: Mozilla uses "; "
314: String[] pair;
315: for (int i = 0; i < cookie.length; i++) {
316: pair = cookie[i].split("=");
317: if (pair[0].trim().equals("login")) {
318: return pair[1].trim();
319: }
320: }
321: return "";
322: }
323:
324: public void adminLogout(String logintoken) {
325: if (cookieUsers.containsKey(logintoken)) {
326: //XXX: We could check, if its == "admin", but we want to logout anyway.
327: cookieUsers.remove(logintoken);
328: }
329: }
330:
331: public class Entry {
332: public static final String MD5ENCODED_USERPWD_STRING = "MD5_user:pwd";
333: public static final String AUTHENTICATION_METHOD = "auth_method";
334: public static final String LOGGED_OUT = "loggedOut";
335: public static final String USER_FIRSTNAME = "firstName";
336: public static final String USER_LASTNAME = "lastName";
337: public static final String USER_ADDRESS = "address";
338: public static final String LAST_ACCESS = "lastAccess";
339: public static final String TIME_USED = "timeUsed";
340: public static final String TIME_LIMIT = "timeLimit";
341: public static final String TRAFFIC_SIZE = "trafficSize";
342: public static final String TRAFFIC_LIMIT = "trafficLimit";
343: public static final String UPLOAD_RIGHT = "uploadRight";
344: public static final String DOWNLOAD_RIGHT = "downloadRight";
345: public static final String ADMIN_RIGHT = "adminRight";
346: public static final String PROXY_RIGHT = "proxyRight";
347: public static final String BLOG_RIGHT = "blogRight";
348: public static final String WIKIADMIN_RIGHT = "wikiAdminRight";
349: public static final String BOOKMARK_RIGHT = "bookmarkRight";
350:
351: //to create new rights, you just need to edit this strings
352: public static final String RIGHT_TYPES = ADMIN_RIGHT + ","
353: + DOWNLOAD_RIGHT + "," + UPLOAD_RIGHT + ","
354: + PROXY_RIGHT + "," + BLOG_RIGHT + "," + BOOKMARK_RIGHT
355: + "," + WIKIADMIN_RIGHT;
356: public static final String RIGHT_NAMES = "Admin,Download,Upload,Proxy usage,Blog,Bookmark,Wiki Admin,SOAP";
357:
358: public static final int PROXY_ALLOK = 0; //can Surf
359: public static final int PROXY_ERROR = 1; //unknown error
360: public static final int PROXY_NORIGHT = 2; //no proxy right
361: public static final int PROXY_TIMELIMIT_REACHED = 3;
362:
363: // this is a simple record structure that hold all properties of a user
364: private HashMap<String, String> mem;
365: private String userName;
366: private Calendar oldDate, newDate;
367:
368: public Entry(String userName, HashMap<String, String> mem)
369: throws IllegalArgumentException {
370: if ((userName == null) || (userName.length() == 0))
371: throw new IllegalArgumentException("Username needed.");
372: if (userName.length() > 128) {
373: throw new IllegalArgumentException("Username too long!");
374: }
375:
376: this .userName = userName.trim();
377: if (this .userName.length() < USERNAME_MIN_LENGTH)
378: throw new IllegalArgumentException(
379: "Username to short. Length should be >= "
380: + USERNAME_MIN_LENGTH);
381:
382: if (mem == null)
383: this .mem = new HashMap<String, String>();
384: else
385: this .mem = mem;
386:
387: if (!mem.containsKey(AUTHENTICATION_METHOD))
388: this .mem.put(AUTHENTICATION_METHOD, "yacy");
389: this .oldDate = Calendar.getInstance();
390: this .newDate = Calendar.getInstance();
391: }
392:
393: public String getUserName() {
394: return this .userName;
395: }
396:
397: public String getFirstName() {
398: return (this .mem.containsKey(USER_FIRSTNAME) ? this .mem
399: .get(USER_FIRSTNAME) : null);
400: }
401:
402: public String getLastName() {
403: return (this .mem.containsKey(USER_LASTNAME) ? this .mem
404: .get(USER_LASTNAME) : null);
405: }
406:
407: public String getAddress() {
408: return (this .mem.containsKey(USER_ADDRESS) ? this .mem
409: .get(USER_ADDRESS) : null);
410: }
411:
412: public long getTimeUsed() {
413: if (this .mem.containsKey(TIME_USED)) {
414: try {
415: return Long.valueOf(this .mem.get(TIME_USED))
416: .longValue();
417: } catch (NumberFormatException e) {
418: return 0;
419: }
420: }
421: try {
422: this .setProperty(TIME_USED, "0");
423: } catch (IOException e) {
424: }
425: return 0;
426: }
427:
428: public long getTimeLimit() {
429: if (this .mem.containsKey(TIME_LIMIT)) {
430: try {
431: return Long.valueOf(this .mem.get(TIME_LIMIT))
432: .longValue();
433: } catch (NumberFormatException e) {
434: return 0;
435: }
436: }
437: try {
438: this .setProperty(TIME_LIMIT, "0");
439: } catch (IOException e) {
440: }
441: return 0;
442: }
443:
444: public long getTrafficSize() {
445: if (this .mem.containsKey(TRAFFIC_SIZE)) {
446: return Long.valueOf(this .mem.get(TRAFFIC_SIZE))
447: .longValue();
448: }
449: try {
450: this .setProperty(TRAFFIC_SIZE, "0");
451: } catch (IOException e) {
452: e.printStackTrace();
453: }
454: return 0;
455: }
456:
457: public Long getTrafficLimit() {
458: return (this .mem.containsKey(TRAFFIC_LIMIT) ? Long
459: .valueOf(this .mem.get(TRAFFIC_LIMIT)) : null);
460: }
461:
462: public long updateTrafficSize(long responseSize) {
463: if (responseSize < 0)
464: throw new IllegalArgumentException(
465: "responseSize must be greater or equal zero.");
466:
467: long currentTrafficSize = getTrafficSize();
468: long newTrafficSize = currentTrafficSize + responseSize;
469: try {
470: this .setProperty(TRAFFIC_SIZE, Long
471: .toString(newTrafficSize));
472: } catch (IOException e) {
473: e.printStackTrace();
474: }
475: return newTrafficSize;
476: }
477:
478: public Long getLastAccess() {
479: return (this .mem.containsKey(LAST_ACCESS) ? Long
480: .valueOf(this .mem.get(LAST_ACCESS)) : null);
481: }
482:
483: public int surfRight() {
484: long timeUsed = this .updateLastAccess(true);
485: if (this .hasProxyRight() == false)
486: return PROXY_NORIGHT;
487:
488: if (!(this .getTimeLimit() <= 0 || (timeUsed < this
489: .getTimeLimit()))) { //no timelimit or timelimit not reached
490: return PROXY_TIMELIMIT_REACHED;
491: }
492: return PROXY_ALLOK;
493: }
494:
495: public boolean canSurf() {
496: if (this .surfRight() == PROXY_ALLOK)
497: return true;
498: return false;
499: }
500:
501: public long updateLastAccess(boolean incrementTimeUsed) {
502: return updateLastAccess(System.currentTimeMillis(),
503: incrementTimeUsed);
504: }
505:
506: public long updateLastAccess(long timeStamp,
507: boolean incrementTimeUsed) {
508: if (timeStamp < 0)
509: throw new IllegalArgumentException();
510:
511: Long lastAccess = this .getLastAccess();
512: long oldTimeUsed = getTimeUsed();
513: long newTimeUsed = oldTimeUsed;
514:
515: if (incrementTimeUsed) {
516: if ((lastAccess == null)
517: || ((lastAccess != null) && (timeStamp
518: - lastAccess.longValue() >= 1000 * 60))) { //1 minute
519: //this.mem.put(TIME_USED,Long.toString(newTimeUsed = ++oldTimeUsed));
520: newTimeUsed = ++oldTimeUsed;
521: if (lastAccess != null) {
522: this .oldDate.setTime(new Date(lastAccess
523: .longValue()));
524: this .newDate.setTime(new Date(System
525: .currentTimeMillis()));
526: if (this .oldDate.get(Calendar.DAY_OF_MONTH) != this .newDate
527: .get(Calendar.DAY_OF_MONTH)
528: || this .oldDate.get(Calendar.MONTH) != this .newDate
529: .get(Calendar.MONTH)
530: || this .oldDate.get(Calendar.YEAR) != this .newDate
531: .get(Calendar.YEAR)) { //new Day, reset time
532: newTimeUsed = 0;
533: }
534: } else { //no access so far
535: newTimeUsed = 0;
536: }
537: this .mem.put(TIME_USED, Long.toString(newTimeUsed));
538: this .mem.put(LAST_ACCESS, Long.toString(timeStamp)); //update Timestamp
539: }
540: } else {
541: this .mem.put(LAST_ACCESS, Long.toString(timeStamp)); //update Timestamp
542: }
543:
544: try {
545: userDB.this .userTable.set(getUserName(), this .mem);
546: } catch (Exception e) {
547: e.printStackTrace();
548: }
549: return newTimeUsed;
550: }
551:
552: public String getMD5EncodedUserPwd() {
553: return (this .mem.containsKey(MD5ENCODED_USERPWD_STRING) ? this .mem
554: .get(MD5ENCODED_USERPWD_STRING)
555: : null);
556: }
557:
558: public Map<String, String> getProperties() {
559: return this .mem;
560: }
561:
562: public void setProperty(String propName, String newValue)
563: throws IOException {
564: this .mem.put(propName, newValue);
565: userDB.this .userTable.set(getUserName(), this .mem);
566: }
567:
568: public String getProperty(String propName, String defaultValue) {
569: return (this .mem.containsKey(propName) ? this .mem
570: .get(propName) : defaultValue);
571: }
572:
573: public boolean hasRight(String rightName) {
574: return (this .mem.containsKey(rightName) ? this .mem.get(
575: rightName).equals("true") : false);
576: }
577:
578: /**
579: * @deprecated use hasRight(UPLOAD_RIGHT) instead
580: */
581: public boolean hasUploadRight() {
582: return this .hasRight(UPLOAD_RIGHT);
583: }
584:
585: /**
586: * @deprecated use hasRight(DOWNLOAD_RIGHT) instead
587: */
588: public boolean hasDownloadRight() {
589: return this .hasRight(DOWNLOAD_RIGHT);
590: }
591:
592: /**
593: * @deprecated use hasRight(PROXY_RIGHT) instead
594: */
595: public boolean hasProxyRight() {
596: return this .hasRight(PROXY_RIGHT);
597: }
598:
599: /**
600: * @deprecated use hasRight(ADMIN_RIGHT) instead
601: */
602: public boolean hasAdminRight() {
603: return this .hasRight(ADMIN_RIGHT);
604: }
605:
606: /**
607: * @deprecated use hasRight(BLOG_RIGHT) instead
608: */
609: public boolean hasBlogRight() {
610: return this .hasRight(BLOG_RIGHT);
611: }
612:
613: /**
614: * @deprecated use hasRight(WIKIADMIN_RIGHT) instead
615: */
616: public boolean hasWikiAdminRight() {
617: return this .hasRight(WIKIADMIN_RIGHT);
618: }
619:
620: /**
621: * @deprecated use hasRight(BOOKMARK_RIGHT) instead
622: */
623: public boolean hasBookmarkRight() {
624: return this .hasRight(BOOKMARK_RIGHT);
625: }
626:
627: public boolean isLoggedOut() {
628: return (this .mem.containsKey(LOGGED_OUT) ? this .mem.get(
629: LOGGED_OUT).equals("true") : false);
630: }
631:
632: public void logout(String ip, String logintoken) {
633: logout(ip);
634: if (cookieUsers.containsKey(logintoken)) {
635: cookieUsers.remove(logintoken);
636: }
637: }
638:
639: public void logout(String ip) {
640: try {
641: setProperty(LOGGED_OUT, "true");
642: if (ipUsers.containsKey(ip)) {
643: ipUsers.remove(ip);
644: }
645: } catch (IOException e) {
646: }
647: }
648:
649: public void logout() {
650: logout("xxxxxx");
651: }
652:
653: public String toString() {
654: StringBuffer str = new StringBuffer();
655: str
656: .append(
657: (this .userName == null) ? "null"
658: : this .userName).append(": ");
659:
660: if (this .mem != null) {
661: str.append(this .mem.toString());
662: }
663:
664: return new String(str);
665: }
666:
667: }
668:
669: public Iterator<Entry> iterator(boolean up) {
670: // enumerates users
671: try {
672: return new userIterator(up);
673: } catch (IOException e) {
674: return new HashSet<Entry>().iterator();
675: }
676: }
677:
678: public class userIterator implements Iterator<Entry> {
679: // the iterator iterates all userNames
680: kelondroCloneableIterator<String> userIter;
681: userDB.Entry nextEntry;
682:
683: public userIterator(boolean up) throws IOException {
684: this .userIter = userDB.this .userTable.keys(up, false);
685: this .nextEntry = null;
686: }
687:
688: public boolean hasNext() {
689: try {
690: return this .userIter.hasNext();
691: } catch (kelondroException e) {
692: resetDatabase();
693: return false;
694: }
695: }
696:
697: public Entry next() {
698: try {
699: return getEntry((String) this .userIter.next());
700: } catch (kelondroException e) {
701: resetDatabase();
702: return null;
703: }
704: }
705:
706: public void remove() {
707: if (this .nextEntry != null) {
708: try {
709: Object userName = this .nextEntry.getUserName();
710: if (userName != null)
711: removeEntry((String) userName);
712: } catch (kelondroException e) {
713: resetDatabase();
714: }
715: }
716: }
717: }
718:
719: }
|