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