001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.catalina.users;
019:
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.FileOutputStream;
023: import java.io.IOException;
024: import java.io.OutputStreamWriter;
025: import java.io.PrintWriter;
026: import java.util.HashMap;
027: import java.util.Iterator;
028: import org.apache.catalina.Group;
029: import org.apache.catalina.Role;
030: import org.apache.catalina.User;
031: import org.apache.catalina.UserDatabase;
032: import org.apache.catalina.util.StringManager;
033: import org.apache.juli.logging.Log;
034: import org.apache.juli.logging.LogFactory;
035: import org.apache.tomcat.util.digester.Digester;
036: import org.apache.tomcat.util.digester.ObjectCreationFactory;
037: import org.xml.sax.Attributes;
038:
039: /**
040: * <p>Concrete implementation of {@link UserDatabase} that loads all
041: * defined users, groups, and roles into an in-memory data structure,
042: * and uses a specified XML file for its persistent storage.</p>
043: *
044: * @author Craig R. McClanahan
045: * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
046: * @since 4.1
047: */
048:
049: public class MemoryUserDatabase implements UserDatabase {
050:
051: private static Log log = LogFactory
052: .getLog(MemoryUserDatabase.class);
053:
054: // ----------------------------------------------------------- Constructors
055:
056: /**
057: * Create a new instance with default values.
058: */
059: public MemoryUserDatabase() {
060:
061: super ();
062:
063: }
064:
065: /**
066: * Create a new instance with the specified values.
067: *
068: * @param id Unique global identifier of this user database
069: */
070: public MemoryUserDatabase(String id) {
071:
072: super ();
073: this .id = id;
074:
075: }
076:
077: // ----------------------------------------------------- Instance Variables
078:
079: /**
080: * The set of {@link Group}s defined in this database, keyed by
081: * group name.
082: */
083: protected HashMap groups = new HashMap();
084:
085: /**
086: * The unique global identifier of this user database.
087: */
088: protected String id = null;
089:
090: /**
091: * The relative (to <code>catalina.base</code>) or absolute pathname to
092: * the XML file in which we will save our persistent information.
093: */
094: protected String pathname = "conf/tomcat-users.xml";
095:
096: /**
097: * The relative or absolute pathname to the file in which our old
098: * information is stored while renaming is in progress.
099: */
100: protected String pathnameOld = pathname + ".old";
101:
102: /**
103: * The relative or absolute pathname ot the file in which we write
104: * our new information prior to renaming.
105: */
106: protected String pathnameNew = pathname + ".new";
107:
108: /**
109: * A flag, indicating if the user database is read only.
110: */
111: protected boolean readonly = false;
112:
113: /**
114: * The set of {@link Role}s defined in this database, keyed by
115: * role name.
116: */
117: protected HashMap roles = new HashMap();
118:
119: /**
120: * The string manager for this package.
121: */
122: private static StringManager sm = StringManager
123: .getManager(Constants.Package);
124:
125: /**
126: * The set of {@link User}s defined in this database, keyed by
127: * user name.
128: */
129: protected HashMap users = new HashMap();
130:
131: // ------------------------------------------------------------- Properties
132:
133: /**
134: * Return the set of {@link Group}s defined in this user database.
135: */
136: public Iterator getGroups() {
137:
138: synchronized (groups) {
139: return (groups.values().iterator());
140: }
141:
142: }
143:
144: /**
145: * Return the unique global identifier of this user database.
146: */
147: public String getId() {
148:
149: return (this .id);
150:
151: }
152:
153: /**
154: * Return the relative or absolute pathname to the persistent storage file.
155: */
156: public String getPathname() {
157:
158: return (this .pathname);
159:
160: }
161:
162: /**
163: * Set the relative or absolute pathname to the persistent storage file.
164: *
165: * @param pathname The new pathname
166: */
167: public void setPathname(String pathname) {
168:
169: this .pathname = pathname;
170: this .pathnameOld = pathname + ".old";
171: this .pathnameNew = pathname + ".new";
172:
173: }
174:
175: /**
176: * Returning the readonly status of the user database
177: */
178: public boolean getReadonly() {
179:
180: return (this .readonly);
181:
182: }
183:
184: /**
185: * Setting the readonly status of the user database
186: *
187: * @param pathname The new pathname
188: */
189: public void setReadonly(boolean readonly) {
190:
191: this .readonly = readonly;
192:
193: }
194:
195: /**
196: * Return the set of {@link Role}s defined in this user database.
197: */
198: public Iterator getRoles() {
199:
200: synchronized (roles) {
201: return (roles.values().iterator());
202: }
203:
204: }
205:
206: /**
207: * Return the set of {@link User}s defined in this user database.
208: */
209: public Iterator getUsers() {
210:
211: synchronized (users) {
212: return (users.values().iterator());
213: }
214:
215: }
216:
217: // --------------------------------------------------------- Public Methods
218:
219: /**
220: * Finalize access to this user database.
221: *
222: * @exception Exception if any exception is thrown during closing
223: */
224: public void close() throws Exception {
225:
226: save();
227:
228: synchronized (groups) {
229: synchronized (users) {
230: users.clear();
231: groups.clear();
232: }
233: }
234:
235: }
236:
237: /**
238: * Create and return a new {@link Group} defined in this user database.
239: *
240: * @param groupname The group name of the new group (must be unique)
241: * @param description The description of this group
242: */
243: public Group createGroup(String groupname, String description) {
244:
245: MemoryGroup group = new MemoryGroup(this , groupname,
246: description);
247: synchronized (groups) {
248: groups.put(group.getGroupname(), group);
249: }
250: return (group);
251:
252: }
253:
254: /**
255: * Create and return a new {@link Role} defined in this user database.
256: *
257: * @param rolename The role name of the new group (must be unique)
258: * @param description The description of this group
259: */
260: public Role createRole(String rolename, String description) {
261:
262: MemoryRole role = new MemoryRole(this , rolename, description);
263: synchronized (roles) {
264: roles.put(role.getRolename(), role);
265: }
266: return (role);
267:
268: }
269:
270: /**
271: * Create and return a new {@link User} defined in this user database.
272: *
273: * @param username The logon username of the new user (must be unique)
274: * @param password The logon password of the new user
275: * @param fullName The full name of the new user
276: */
277: public User createUser(String username, String password,
278: String fullName) {
279:
280: MemoryUser user = new MemoryUser(this , username, password,
281: fullName);
282: synchronized (users) {
283: users.put(user.getUsername(), user);
284: }
285: return (user);
286:
287: }
288:
289: /**
290: * Return the {@link Group} with the specified group name, if any;
291: * otherwise return <code>null</code>.
292: *
293: * @param groupname Name of the group to return
294: */
295: public Group findGroup(String groupname) {
296:
297: synchronized (groups) {
298: return ((Group) groups.get(groupname));
299: }
300:
301: }
302:
303: /**
304: * Return the {@link Role} with the specified role name, if any;
305: * otherwise return <code>null</code>.
306: *
307: * @param rolename Name of the role to return
308: */
309: public Role findRole(String rolename) {
310:
311: synchronized (roles) {
312: return ((Role) roles.get(rolename));
313: }
314:
315: }
316:
317: /**
318: * Return the {@link User} with the specified user name, if any;
319: * otherwise return <code>null</code>.
320: *
321: * @param username Name of the user to return
322: */
323: public User findUser(String username) {
324:
325: synchronized (users) {
326: return ((User) users.get(username));
327: }
328:
329: }
330:
331: /**
332: * Initialize access to this user database.
333: *
334: * @exception Exception if any exception is thrown during opening
335: */
336: public void open() throws Exception {
337:
338: synchronized (groups) {
339: synchronized (users) {
340:
341: // Erase any previous groups and users
342: users.clear();
343: groups.clear();
344: roles.clear();
345:
346: // Construct a reader for the XML input file (if it exists)
347: File file = new File(pathname);
348: if (!file.isAbsolute()) {
349: file = new File(
350: System.getProperty("catalina.base"),
351: pathname);
352: }
353: if (!file.exists()) {
354: return;
355: }
356: FileInputStream fis = new FileInputStream(file);
357:
358: // Construct a digester to read the XML input file
359: Digester digester = new Digester();
360: digester.addFactoryCreate("tomcat-users/group",
361: new MemoryGroupCreationFactory(this ));
362: digester.addFactoryCreate("tomcat-users/role",
363: new MemoryRoleCreationFactory(this ));
364: digester.addFactoryCreate("tomcat-users/user",
365: new MemoryUserCreationFactory(this ));
366:
367: // Parse the XML input file to load this database
368: try {
369: digester.parse(fis);
370: fis.close();
371: } catch (Exception e) {
372: try {
373: fis.close();
374: } catch (Throwable t) {
375: ;
376: }
377: throw e;
378: }
379:
380: }
381: }
382:
383: }
384:
385: /**
386: * Remove the specified {@link Group} from this user database.
387: *
388: * @param group The group to be removed
389: */
390: public void removeGroup(Group group) {
391:
392: synchronized (groups) {
393: Iterator users = getUsers();
394: while (users.hasNext()) {
395: User user = (User) users.next();
396: user.removeGroup(group);
397: }
398: groups.remove(group.getGroupname());
399: }
400:
401: }
402:
403: /**
404: * Remove the specified {@link Role} from this user database.
405: *
406: * @param role The role to be removed
407: */
408: public void removeRole(Role role) {
409:
410: synchronized (roles) {
411: Iterator groups = getGroups();
412: while (groups.hasNext()) {
413: Group group = (Group) groups.next();
414: group.removeRole(role);
415: }
416: Iterator users = getUsers();
417: while (users.hasNext()) {
418: User user = (User) users.next();
419: user.removeRole(role);
420: }
421: roles.remove(role.getRolename());
422: }
423:
424: }
425:
426: /**
427: * Remove the specified {@link User} from this user database.
428: *
429: * @param user The user to be removed
430: */
431: public void removeUser(User user) {
432:
433: synchronized (users) {
434: users.remove(user.getUsername());
435: }
436:
437: }
438:
439: /**
440: * Check for permissions to save this user database
441: * to persistent storage location
442: *
443: */
444: public boolean isWriteable() {
445:
446: File file = new File(pathname);
447: if (!file.isAbsolute()) {
448: file = new File(System.getProperty("catalina.base"),
449: pathname);
450: }
451: File dir = file.getParentFile();
452: return dir.exists() && dir.isDirectory() && dir.canWrite();
453:
454: }
455:
456: /**
457: * Save any updated information to the persistent storage location for
458: * this user database.
459: *
460: * @exception Exception if any exception is thrown during saving
461: */
462: public void save() throws Exception {
463:
464: if (getReadonly()) {
465: return;
466: }
467:
468: if (!isWriteable()) {
469: log.warn(sm.getString("memoryUserDatabase.notPersistable"));
470: return;
471: }
472:
473: // Write out contents to a temporary file
474: File fileNew = new File(pathnameNew);
475: if (!fileNew.isAbsolute()) {
476: fileNew = new File(System.getProperty("catalina.base"),
477: pathnameNew);
478: }
479: PrintWriter writer = null;
480: try {
481:
482: // Configure our PrintWriter
483: FileOutputStream fos = new FileOutputStream(fileNew);
484: OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF8");
485: writer = new PrintWriter(osw);
486:
487: // Print the file prolog
488: writer.println("<?xml version='1.0' encoding='utf-8'?>");
489: writer.println("<tomcat-users>");
490:
491: // Print entries for each defined role, group, and user
492: Iterator values = null;
493: values = getRoles();
494: while (values.hasNext()) {
495: writer.print(" ");
496: writer.println(values.next());
497: }
498: values = getGroups();
499: while (values.hasNext()) {
500: writer.print(" ");
501: writer.println(values.next());
502: }
503: values = getUsers();
504: while (values.hasNext()) {
505: writer.print(" ");
506: writer.println(values.next());
507: }
508:
509: // Print the file epilog
510: writer.println("</tomcat-users>");
511:
512: // Check for errors that occurred while printing
513: if (writer.checkError()) {
514: writer.close();
515: fileNew.delete();
516: throw new IOException(sm.getString(
517: "memoryUserDatabase.writeException", fileNew
518: .getAbsolutePath()));
519: }
520: writer.close();
521: } catch (IOException e) {
522: if (writer != null) {
523: writer.close();
524: }
525: fileNew.delete();
526: throw e;
527: }
528:
529: // Perform the required renames to permanently save this file
530: File fileOld = new File(pathnameOld);
531: if (!fileOld.isAbsolute()) {
532: fileOld = new File(System.getProperty("catalina.base"),
533: pathnameOld);
534: }
535: fileOld.delete();
536: File fileOrig = new File(pathname);
537: if (!fileOrig.isAbsolute()) {
538: fileOrig = new File(System.getProperty("catalina.base"),
539: pathname);
540: }
541: if (fileOrig.exists()) {
542: fileOld.delete();
543: if (!fileOrig.renameTo(fileOld)) {
544: throw new IOException(sm.getString(
545: "memoryUserDatabase.renameOld", fileOld
546: .getAbsolutePath()));
547: }
548: }
549: if (!fileNew.renameTo(fileOrig)) {
550: if (fileOld.exists()) {
551: fileOld.renameTo(fileOrig);
552: }
553: throw new IOException(sm.getString(
554: "memoryUserDatabase.renameNew", fileOrig
555: .getAbsolutePath()));
556: }
557: fileOld.delete();
558:
559: }
560:
561: /**
562: * Return a String representation of this UserDatabase.
563: */
564: public String toString() {
565:
566: StringBuffer sb = new StringBuffer("MemoryUserDatabase[id=");
567: sb.append(this .id);
568: sb.append(",pathname=");
569: sb.append(pathname);
570: sb.append(",groupCount=");
571: sb.append(this .groups.size());
572: sb.append(",roleCount=");
573: sb.append(this .roles.size());
574: sb.append(",userCount=");
575: sb.append(this .users.size());
576: sb.append("]");
577: return (sb.toString());
578:
579: }
580:
581: // -------------------------------------------------------- Package Methods
582:
583: /**
584: * Return the <code>StringManager</code> for use in looking up messages.
585: */
586: StringManager getStringManager() {
587:
588: return (sm);
589:
590: }
591:
592: }
593:
594: /**
595: * Digester object creation factory for group instances.
596: */
597: class MemoryGroupCreationFactory implements ObjectCreationFactory {
598:
599: public MemoryGroupCreationFactory(MemoryUserDatabase database) {
600: this .database = database;
601: }
602:
603: public Object createObject(Attributes attributes) {
604: String groupname = attributes.getValue("groupname");
605: if (groupname == null) {
606: groupname = attributes.getValue("name");
607: }
608: String description = attributes.getValue("description");
609: String roles = attributes.getValue("roles");
610: Group group = database.createGroup(groupname, description);
611: if (roles != null) {
612: while (roles.length() > 0) {
613: String rolename = null;
614: int comma = roles.indexOf(',');
615: if (comma >= 0) {
616: rolename = roles.substring(0, comma).trim();
617: roles = roles.substring(comma + 1);
618: } else {
619: rolename = roles.trim();
620: roles = "";
621: }
622: if (rolename.length() > 0) {
623: Role role = database.findRole(rolename);
624: if (role == null) {
625: role = database.createRole(rolename, null);
626: }
627: group.addRole(role);
628: }
629: }
630: }
631: return (group);
632: }
633:
634: private MemoryUserDatabase database = null;
635:
636: private Digester digester = null;
637:
638: public Digester getDigester() {
639: return (this .digester);
640: }
641:
642: public void setDigester(Digester digester) {
643: this .digester = digester;
644: }
645:
646: }
647:
648: /**
649: * Digester object creation factory for role instances.
650: */
651: class MemoryRoleCreationFactory implements ObjectCreationFactory {
652:
653: public MemoryRoleCreationFactory(MemoryUserDatabase database) {
654: this .database = database;
655: }
656:
657: public Object createObject(Attributes attributes) {
658: String rolename = attributes.getValue("rolename");
659: if (rolename == null) {
660: rolename = attributes.getValue("name");
661: }
662: String description = attributes.getValue("description");
663: Role role = database.createRole(rolename, description);
664: return (role);
665: }
666:
667: private MemoryUserDatabase database = null;
668:
669: private Digester digester = null;
670:
671: public Digester getDigester() {
672: return (this .digester);
673: }
674:
675: public void setDigester(Digester digester) {
676: this .digester = digester;
677: }
678:
679: }
680:
681: /**
682: * Digester object creation factory for user instances.
683: */
684: class MemoryUserCreationFactory implements ObjectCreationFactory {
685:
686: public MemoryUserCreationFactory(MemoryUserDatabase database) {
687: this .database = database;
688: }
689:
690: public Object createObject(Attributes attributes) {
691: String username = attributes.getValue("username");
692: if (username == null) {
693: username = attributes.getValue("name");
694: }
695: String password = attributes.getValue("password");
696: String fullName = attributes.getValue("fullName");
697: if (fullName == null) {
698: fullName = attributes.getValue("fullname");
699: }
700: String groups = attributes.getValue("groups");
701: String roles = attributes.getValue("roles");
702: User user = database.createUser(username, password, fullName);
703: if (groups != null) {
704: while (groups.length() > 0) {
705: String groupname = null;
706: int comma = groups.indexOf(',');
707: if (comma >= 0) {
708: groupname = groups.substring(0, comma).trim();
709: groups = groups.substring(comma + 1);
710: } else {
711: groupname = groups.trim();
712: groups = "";
713: }
714: if (groupname.length() > 0) {
715: Group group = database.findGroup(groupname);
716: if (group == null) {
717: group = database.createGroup(groupname, null);
718: }
719: user.addGroup(group);
720: }
721: }
722: }
723: if (roles != null) {
724: while (roles.length() > 0) {
725: String rolename = null;
726: int comma = roles.indexOf(',');
727: if (comma >= 0) {
728: rolename = roles.substring(0, comma).trim();
729: roles = roles.substring(comma + 1);
730: } else {
731: rolename = roles.trim();
732: roles = "";
733: }
734: if (rolename.length() > 0) {
735: Role role = database.findRole(rolename);
736: if (role == null) {
737: role = database.createRole(rolename, null);
738: }
739: user.addRole(role);
740: }
741: }
742: }
743: return (user);
744: }
745:
746: private MemoryUserDatabase database = null;
747:
748: private Digester digester = null;
749:
750: public Digester getDigester() {
751: return (this .digester);
752: }
753:
754: public void setDigester(Digester digester) {
755: this.digester = digester;
756: }
757:
758: }
|