001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com> and
003: * Steven Grimm <koreth[remove] at midwinter dot com>
004: * Distributed under the terms of either:
005: * - the common development and distribution license (CDDL), v1.0; or
006: * - the GNU Lesser General Public License, v2.1 or later
007: * $Id: MemoryUsers.java 3732 2007-05-02 20:45:59Z gbevin $
008: */
009: package com.uwyn.rife.authentication.credentialsmanagers;
010:
011: import com.uwyn.rife.authentication.credentialsmanagers.exceptions.*;
012:
013: import com.uwyn.rife.authentication.Credentials;
014: import com.uwyn.rife.authentication.CredentialsManager;
015: import com.uwyn.rife.authentication.PasswordEncrypting;
016: import com.uwyn.rife.authentication.credentials.RoleUserCredentials;
017: import com.uwyn.rife.authentication.credentialsmanagers.RoleUserAttributes;
018: import com.uwyn.rife.authentication.exceptions.CredentialsManagerException;
019: import com.uwyn.rife.rep.Participant;
020: import com.uwyn.rife.rep.Rep;
021: import com.uwyn.rife.resources.ResourceFinder;
022: import com.uwyn.rife.tools.FileUtils;
023: import com.uwyn.rife.tools.SortListComparables;
024: import com.uwyn.rife.tools.StringEncryptor;
025: import com.uwyn.rife.tools.StringUtils;
026: import com.uwyn.rife.tools.exceptions.FileUtilsErrorException;
027: import com.uwyn.rife.xml.exceptions.XmlErrorException;
028: import java.io.File;
029: import java.net.URL;
030: import java.net.URLDecoder;
031: import java.security.NoSuchAlgorithmException;
032: import java.util.ArrayList;
033: import java.util.Collection;
034: import java.util.HashMap;
035: import java.util.Map;
036: import java.util.TreeMap;
037:
038: public class MemoryUsers implements CredentialsManager,
039: RoleUsersManager, PasswordEncrypting {
040: public final static String DEFAULT_PARTICIPANT_NAME = "ParticipantMemoryUsers";
041:
042: private Map<Long, String> mUserIdMapping = new HashMap<Long, String>();
043: private Map<String, RoleUserAttributes> mUsers = new TreeMap<String, RoleUserAttributes>();
044: private Map<String, ArrayList<String>> mRoles = new TreeMap<String, ArrayList<String>>();
045: private long mUserIdSequence = 0;
046: private String mXmlPath = null;
047: private ResourceFinder mResourceFinder = null;
048:
049: protected StringEncryptor mPasswordEncryptor = null;
050:
051: public MemoryUsers() {
052: }
053:
054: public MemoryUsers(String xmlPath, ResourceFinder resourceFinder)
055: throws CredentialsManagerException {
056: if (null == xmlPath)
057: throw new IllegalArgumentException("xmlPath can't be null.");
058: if (0 == xmlPath.length())
059: throw new IllegalArgumentException(
060: "xmlPath can't be empty.");
061: if (null == resourceFinder)
062: throw new IllegalArgumentException(
063: "resourceFinder can't be null.");
064:
065: mXmlPath = xmlPath;
066: mResourceFinder = resourceFinder;
067:
068: initialize();
069: }
070:
071: /**
072: * Retrieves the path of the XML document that populated this
073: * {@code MemoryUsers} instance.
074: *
075: * @return the path of the XML document that populated this
076: * {@code MemoryUsers} instance
077: *
078: * @since 1.0
079: */
080: public String getXmlPath() {
081: return mXmlPath;
082: }
083:
084: public StringEncryptor getPasswordEncryptor() {
085: return mPasswordEncryptor;
086: }
087:
088: public void setPasswordEncryptor(StringEncryptor passwordEncryptor) {
089: mPasswordEncryptor = passwordEncryptor;
090: }
091:
092: public static boolean hasRepInstance() {
093: return Rep.hasParticipant(DEFAULT_PARTICIPANT_NAME);
094: }
095:
096: public static MemoryUsers getRepInstance() {
097: Participant participant = Rep
098: .getParticipant(DEFAULT_PARTICIPANT_NAME);
099: if (null == participant) {
100: return null;
101: }
102:
103: return (MemoryUsers) participant.getObject();
104: }
105:
106: public long verifyCredentials(Credentials credentials)
107: throws CredentialsManagerException {
108: RoleUserCredentials role_user = null;
109: if (credentials instanceof RoleUserCredentials) {
110: role_user = (RoleUserCredentials) credentials;
111: } else {
112: throw new UnsupportedCredentialsTypeException(credentials);
113: }
114:
115: synchronized (this ) {
116: if (null == role_user.getLogin()) {
117: return -1;
118: }
119:
120: RoleUserAttributes user_attributes = mUsers.get(role_user
121: .getLogin());
122:
123: if (null == user_attributes) {
124: return -1;
125: }
126:
127: // correctly handle encoded passwords
128: String password = null;
129: try {
130: password = StringEncryptor.adaptiveEncrypt(role_user
131: .getPassword(), user_attributes.getPassword());
132: } catch (NoSuchAlgorithmException e) {
133: throw new VerifyCredentialsErrorException(credentials,
134: e);
135: }
136:
137: // handle roles
138: if (role_user.getRole() != null) {
139: if (user_attributes.isValid(password, role_user
140: .getRole())) {
141: return mUsers.get(role_user.getLogin()).getUserId();
142: }
143: } else {
144: if (user_attributes.isValid(password)) {
145: return mUsers.get(role_user.getLogin()).getUserId();
146: }
147: }
148: }
149:
150: return -1;
151: }
152:
153: public MemoryUsers addRole(String role)
154: throws CredentialsManagerException {
155: if (null == role || 0 == role.length()) {
156: throw new AddRoleErrorException(role);
157: }
158:
159: if (mRoles.containsKey(role)) {
160: throw new DuplicateRoleException(role);
161: }
162:
163: mRoles.put(role, new ArrayList<String>());
164:
165: return this ;
166: }
167:
168: public long countRoles() {
169: return mRoles.size();
170: }
171:
172: public boolean containsRole(String role) {
173: if (null == role || 0 == role.length()) {
174: return false;
175: }
176:
177: return mRoles.containsKey(role);
178: }
179:
180: public MemoryUsers addUser(String login,
181: RoleUserAttributes attributes)
182: throws CredentialsManagerException {
183: if (null == login || 0 == login.length() || null == attributes) {
184: throw new AddUserErrorException(login, attributes);
185: }
186:
187: synchronized (this ) {
188: // throw an exception if the user already exists
189: if (mUsers.containsKey(login)) {
190: throw new DuplicateLoginException(login);
191: }
192:
193: // correctly handle implicit and specific user ids
194: if (-1 == attributes.getUserId()) {
195: while (mUserIdMapping.containsKey(new Long(
196: mUserIdSequence))) {
197: // FIXME: check for long overflow
198: mUserIdSequence++;
199: }
200:
201: attributes.setUserId(mUserIdSequence);
202: attributes.setAutomaticUserId(true);
203: mUserIdMapping.put(new Long(mUserIdSequence), login);
204: } else {
205: if (mUserIdMapping.containsKey(new Long(attributes
206: .getUserId()))) {
207: throw new DuplicateUserIdException(attributes
208: .getUserId());
209: }
210:
211: mUserIdMapping.put(new Long(attributes.getUserId()),
212: login);
213: }
214:
215: // correctly handle password encoding
216: RoleUserAttributes attributes_clone = attributes.clone();
217: if (mPasswordEncryptor != null
218: && !attributes_clone.getPassword().startsWith(
219: mPasswordEncryptor.toString())) {
220: try {
221: attributes_clone.setPassword(mPasswordEncryptor
222: .encrypt(attributes_clone.getPassword()));
223: } catch (NoSuchAlgorithmException e) {
224: throw new AddUserErrorException(login, attributes,
225: e);
226: }
227: }
228:
229: mUsers.put(login, attributes_clone);
230:
231: // create reverse links from the roles to the logins
232: createRoleLinks(login, attributes_clone);
233: }
234:
235: return this ;
236: }
237:
238: private void createRoleLinks(String login,
239: RoleUserAttributes attributes)
240: throws CredentialsManagerException {
241: assert login != null;
242: assert login.length() > 0;
243:
244: if (attributes.getRoles() != null
245: && attributes.getRoles().size() > 0) {
246: ArrayList<String> logins = null;
247: for (String role : attributes.getRoles()) {
248: if (!mRoles.containsKey(role)) {
249: throw new UnknownRoleErrorException(role, login,
250: attributes);
251: } else {
252: logins = mRoles.get(role);
253:
254: if (!logins.contains(login)) {
255: logins.add(login);
256: }
257: }
258: }
259: }
260: }
261:
262: public RoleUserAttributes getAttributes(String login) {
263: if (null == login || 0 == login.length()) {
264: return null;
265: }
266:
267: return mUsers.get(login);
268: }
269:
270: public long countUsers() {
271: return mUsers.size();
272: }
273:
274: public boolean listRoles(ListRoles processor) {
275: if (null == processor) {
276: return false;
277: }
278:
279: if (0 == mRoles.size()) {
280: return true;
281: }
282:
283: boolean result = false;
284:
285: for (String role : mRoles.keySet()) {
286: result = true;
287:
288: if (!processor.foundRole(role)) {
289: break;
290: }
291: }
292:
293: return result;
294: }
295:
296: public boolean listUsers(ListUsers processor) {
297: if (null == processor) {
298: return false;
299: }
300:
301: if (0 == mUsers.size()) {
302: return false;
303: }
304:
305: boolean result = false;
306:
307: RoleUserAttributes attributes = null;
308: for (String login : mUsers.keySet()) {
309: result = true;
310:
311: attributes = mUsers.get(login);
312:
313: if (!processor.foundUser(attributes.getUserId(), login,
314: attributes.getPassword())) {
315: break;
316: }
317: }
318:
319: return result;
320: }
321:
322: public boolean listUsers(ListUsers processor, int limit, int offset) {
323: if (null == processor || limit <= 0 || 0 == mUsers.size()) {
324: return false;
325: }
326:
327: boolean result = false;
328:
329: RoleUserAttributes attributes = null;
330: int count = 0;
331: for (String login : mUsers.keySet()) {
332: if (count < offset) {
333: count++;
334: continue;
335: }
336:
337: if (count - offset >= limit) {
338: break;
339: }
340:
341: count++;
342: result = true;
343:
344: attributes = mUsers.get(login);
345:
346: if (!processor.foundUser(attributes.getUserId(), login,
347: attributes.getPassword())) {
348: break;
349: }
350: }
351:
352: return result;
353: }
354:
355: public boolean containsUser(String login) {
356: if (null == login || 0 == login.length()) {
357: return false;
358: }
359:
360: synchronized (this ) {
361: return mUsers.containsKey(login);
362: }
363: }
364:
365: public boolean listUsersInRole(ListUsers processor, String role)
366: throws CredentialsManagerException {
367: if (null == processor) {
368: return false;
369: }
370:
371: if (null == role || 0 == role.length()) {
372: return false;
373: }
374:
375: if (0 == mUsers.size()) {
376: return false;
377: }
378:
379: boolean result = false;
380:
381: RoleUserAttributes attributes = null;
382: for (String login : mUsers.keySet()) {
383: attributes = mUsers.get(login);
384: if (null == attributes.getRoles()
385: || !attributes.getRoles().contains(role)) {
386: continue;
387: }
388:
389: result = true;
390: if (!processor.foundUser(attributes.getUserId(), login,
391: attributes.getPassword())) {
392: break;
393: }
394: }
395:
396: return result;
397: }
398:
399: public boolean isUserInRole(long userId, String role) {
400: if (userId < 0 || null == role || 0 == role.length()) {
401: return false;
402: }
403:
404: synchronized (this ) {
405: String login = mUserIdMapping.get(new Long(userId));
406:
407: if (null == login) {
408: return false;
409: }
410:
411: RoleUserAttributes user_attributes = mUsers.get(login);
412:
413: if (null == user_attributes) {
414: return false;
415: }
416:
417: return user_attributes.isInRole(role);
418: }
419: }
420:
421: public String getLogin(long userId) {
422: if (userId < 0) {
423: return null;
424: }
425:
426: String login = null;
427:
428: synchronized (this ) {
429: login = mUserIdMapping.get(new Long(userId));
430: }
431:
432: return login;
433: }
434:
435: public long getUserId(String login) {
436: if (null == login || 0 == login.length()) {
437: return -1;
438: }
439:
440: long userid = -1;
441:
442: synchronized (this ) {
443: RoleUserAttributes attributes = mUsers.get(login);
444: if (attributes != null) {
445: userid = attributes.getUserId();
446: }
447: }
448:
449: return userid;
450: }
451:
452: public boolean updateUser(String login,
453: RoleUserAttributes attributes)
454: throws CredentialsManagerException {
455: if (null == login || 0 == login.length() || null == attributes) {
456: throw new UpdateUserErrorException(login, attributes);
457: }
458:
459: synchronized (this ) {
460: if (!mUsers.containsKey(login)) {
461: return false;
462: }
463:
464: // get the current attributes
465: RoleUserAttributes current_attributes = mUsers.get(login);
466:
467: // set the current password if it has not been provided
468: RoleUserAttributes attributes_clone = attributes.clone();
469: if (null == attributes_clone.getPassword()) {
470: attributes_clone.setPassword(current_attributes
471: .getPassword());
472: } else {
473: // correctly handle password encoding
474: if (mPasswordEncryptor != null
475: && !attributes_clone.getPassword().startsWith(
476: mPasswordEncryptor.toString())) {
477: try {
478: attributes_clone
479: .setPassword(mPasswordEncryptor
480: .encrypt(attributes_clone
481: .getPassword()));
482: } catch (NoSuchAlgorithmException e) {
483: throw new UpdateUserErrorException(login,
484: attributes, e);
485: }
486: }
487: }
488:
489: // ensure that the user id remains the same
490: attributes_clone.setUserId(current_attributes.getUserId());
491:
492: // update the reverse link from the roles collection
493: removeRoleLinks(login);
494:
495: // store the new user attributes
496: mUsers.put(login, attributes_clone);
497:
498: // create reverse links from the roles to the logins
499: createRoleLinks(login, attributes_clone);
500: }
501:
502: return true;
503: }
504:
505: public boolean removeUser(String login) {
506: if (null == login || 0 == login.length()) {
507: return false;
508: }
509:
510: synchronized (this ) {
511: // update the reverse link from the roles collection
512: removeRoleLinks(login);
513:
514: // remove the user
515: return null != mUsers.remove(login);
516:
517: }
518: }
519:
520: public boolean removeUser(long userId) {
521: if (userId < 0) {
522: return false;
523: }
524:
525: String login = null;
526:
527: synchronized (this ) {
528: if (null == mUserIdMapping.get(userId)) {
529: return false;
530: }
531:
532: else {
533: login = mUserIdMapping.get(userId);
534:
535: // update the reverse link from the roles collection
536: removeRoleLinks(login);
537:
538: // remove the user
539: return null != mUsers.remove(login);
540:
541: }
542: }
543: }
544:
545: public boolean removeRole(String name) {
546: if (null == name || 0 == name.length()) {
547: return false;
548: }
549:
550: synchronized (this ) {
551: if (mRoles.remove(name) == null) {
552: return false;
553: }
554:
555: for (String key : mUsers.keySet()) {
556: Collection<String> roles = mUsers.get(key).getRoles();
557:
558: if (roles != null && roles.contains(name)) {
559: roles.remove(name);
560: }
561: }
562: }
563: return true;
564: }
565:
566: private void removeRoleLinks(String login) {
567: assert login != null;
568: assert login.length() > 0;
569:
570: RoleUserAttributes attributes = mUsers.get(login);
571: if (attributes != null && attributes.getRoles() != null
572: && attributes.getRoles().size() > 0) {
573: // remove the login from the roles it's registered for
574: ArrayList<String> logins = null;
575: ArrayList<String> roles_to_delete = null;
576: for (String role : attributes.getRoles()) {
577: logins = mRoles.get(role);
578: logins.remove(login);
579: if (0 == logins.size()) {
580: if (null == roles_to_delete) {
581: roles_to_delete = new ArrayList<String>();
582: }
583:
584: roles_to_delete.add(role);
585: }
586: }
587:
588: // remove the roles that now don't have any logins anymore
589: if (roles_to_delete != null) {
590: for (String role : roles_to_delete) {
591: mRoles.remove(role);
592: }
593: }
594: }
595: }
596:
597: public void clearUsers() {
598: synchronized (this ) {
599: mUsers = new TreeMap<String, RoleUserAttributes>();
600: mRoles = new TreeMap<String, ArrayList<String>>();
601: }
602: }
603:
604: public boolean listUserRoles(String login, ListRoles processor)
605: throws CredentialsManagerException {
606: if (null == mUsers.get(login)) {
607: return false;
608: }
609:
610: if (null == processor) {
611: return false;
612: }
613:
614: if (0 == mRoles.size()) {
615: return true;
616: }
617:
618: boolean result = false;
619:
620: for (String role : mRoles.keySet()) {
621: RoleUserAttributes attributes = null;
622:
623: synchronized (this ) {
624: attributes = mUsers.get(login);
625: }
626:
627: if (attributes.isInRole(role)) {
628: result = true;
629:
630: if (!processor.foundRole(role)) {
631: break;
632: }
633: }
634: }
635:
636: return result;
637: }
638:
639: private void initialize() throws CredentialsManagerException {
640: try {
641: Xml2MemoryUsers xml_memoryusers = new Xml2MemoryUsers();
642: xml_memoryusers.processXml(mXmlPath, mResourceFinder);
643: synchronized (this ) {
644: RoleUserAttributes attributes;
645: for (Map.Entry<String, RoleUserAttributes> user_entry : xml_memoryusers
646: .getUsers().entrySet()) {
647: attributes = user_entry.getValue();
648: for (String role : attributes.getRoles()) {
649: if (!containsRole(role)) {
650: addRole(role);
651: }
652: }
653: addUser(user_entry.getKey(), attributes);
654: }
655: }
656: } catch (XmlErrorException e) {
657: throw new InitializationErrorException(mXmlPath, e);
658: }
659: }
660:
661: public String toXml() {
662: StringBuilder xml_output = new StringBuilder();
663:
664: xml_output.append("<credentials>\n");
665:
666: SortListComparables arraylist_sort = new SortListComparables();
667: ArrayList<String> logins_list = new ArrayList<String>(mUsers
668: .keySet());
669: RoleUserAttributes user_attributes = null;
670:
671: arraylist_sort.sort(logins_list);
672: for (String login : logins_list) {
673: user_attributes = mUsers.get(login);
674: xml_output.append("\t<user login=\"").append(
675: StringUtils.encodeXml(login)).append("\"");
676: if (!user_attributes.isAutomaticUserId()) {
677: xml_output.append(" userid=\"").append(
678: user_attributes.getUserId()).append("\"");
679: }
680: xml_output.append(">\n");
681: xml_output.append("\t\t<password>").append(
682: StringUtils
683: .encodeXml(user_attributes.getPassword()))
684: .append("</password>\n");
685: if (user_attributes.getRoles() != null
686: && user_attributes.getRoles().size() > 0) {
687: ArrayList<String> roles = new ArrayList<String>(
688: user_attributes.getRoles());
689: arraylist_sort.sort(roles);
690: for (String role : roles) {
691: xml_output.append("\t\t<role>").append(
692: StringUtils.encodeXml(role)).append(
693: "</role>\n");
694: }
695: }
696: xml_output.append("\t</user>\n");
697: }
698:
699: xml_output.append("</credentials>\n");
700:
701: return xml_output.toString();
702: }
703:
704: public void storeToXml() throws CredentialsManagerException {
705: String xmlpath = null;
706: URL xmlpath_resource = null;
707:
708: xmlpath = getXmlPath();
709: if (null == xmlpath) {
710: throw new MissingXmlPathException();
711: }
712:
713: xmlpath_resource = mResourceFinder.getResource(xmlpath);
714: if (null == xmlpath_resource) {
715: throw new CantFindXmlPathException(xmlpath);
716: }
717:
718: storeToXml(new File(URLDecoder.decode(xmlpath_resource
719: .getPath())));
720: }
721:
722: public synchronized void storeToXml(File destination)
723: throws CredentialsManagerException {
724: if (null == destination || destination.exists()
725: && !destination.canWrite()) {
726: throw new CantWriteToDestinationException(destination);
727: }
728:
729: StringBuilder content = new StringBuilder(
730: "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n");
731: content
732: .append("<!DOCTYPE credentials SYSTEM \"/dtd/users.dtd\">\n");
733: content.append(toXml());
734: try {
735: FileUtils.writeString(content.toString(), destination);
736: } catch (FileUtilsErrorException e) {
737: throw new StoreXmlErrorException(destination, e);
738: }
739:
740: }
741: }
|