1
17 package org.apache.catalina.users;
18
19 import java.io.File;
20 import java.io.FileNotFoundException;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.io.OutputStreamWriter;
24 import java.io.PrintWriter;
25 import java.net.URI;
26 import java.net.URL;
27 import java.net.URLConnection;
28 import java.nio.charset.StandardCharsets;
29 import java.util.ArrayList;
30 import java.util.Iterator;
31 import java.util.Map;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.concurrent.locks.Lock;
34 import java.util.concurrent.locks.ReentrantReadWriteLock;
35
36 import org.apache.catalina.Globals;
37 import org.apache.catalina.Group;
38 import org.apache.catalina.Role;
39 import org.apache.catalina.User;
40 import org.apache.catalina.UserDatabase;
41 import org.apache.juli.logging.Log;
42 import org.apache.juli.logging.LogFactory;
43 import org.apache.tomcat.util.digester.AbstractObjectCreationFactory;
44 import org.apache.tomcat.util.digester.Digester;
45 import org.apache.tomcat.util.file.ConfigFileLoader;
46 import org.apache.tomcat.util.file.ConfigurationSource;
47 import org.apache.tomcat.util.res.StringManager;
48 import org.xml.sax.Attributes;
49
50
65
82 public class MemoryUserDatabase implements UserDatabase {
83
84 private static final Log log = LogFactory.getLog(MemoryUserDatabase.class);
85 private static final StringManager sm = StringManager.getManager(MemoryUserDatabase.class);
86
87
88
89
90
93 public MemoryUserDatabase() {
94 this(null);
95 }
96
97
98
103 public MemoryUserDatabase(String id) {
104 this.id = id;
105 }
106
107
108
109
112 protected final Map<String, Group> groups = new ConcurrentHashMap<>();
113
114
117 protected final String id;
118
119
123 protected String pathname = "conf/tomcat-users.xml";
124
125
129 protected String pathnameOld = pathname + ".old";
130
131
135 protected String pathnameNew = pathname + ".new";
136
137
140 protected boolean readonly = true;
141
142
145 protected final Map<String, Role> roles = new ConcurrentHashMap<>();
146
147
150 protected final Map<String, User> users = new ConcurrentHashMap<>();
151
152 private final ReentrantReadWriteLock dbLock = new ReentrantReadWriteLock();
153 private final Lock readLock = dbLock.readLock();
154 private final Lock writeLock = dbLock.writeLock();
155
156 private volatile long lastModified = 0;
157 private boolean watchSource = true;
158
159
160
161
162
165 @Override
166 public Iterator<Group> getGroups() {
167 readLock.lock();
168 try {
169 return new ArrayList<>(groups.values()).iterator();
170 } finally {
171 readLock.unlock();
172 }
173 }
174
175
176
179 @Override
180 public String getId() {
181 return this.id;
182 }
183
184
185
188 public String getPathname() {
189 return this.pathname;
190 }
191
192
193
198 public void setPathname(String pathname) {
199 this.pathname = pathname;
200 this.pathnameOld = pathname + ".old";
201 this.pathnameNew = pathname + ".new";
202 }
203
204
205
208 public boolean getReadonly() {
209 return this.readonly;
210 }
211
212
213
218 public void setReadonly(boolean readonly) {
219 this.readonly = readonly;
220 }
221
222
223 public boolean getWatchSource() {
224 return watchSource;
225 }
226
227
228
229 public void setWatchSource(boolean watchSource) {
230 this.watchSource = watchSource;
231 }
232
233
234
237 @Override
238 public Iterator<Role> getRoles() {
239 readLock.lock();
240 try {
241 return new ArrayList<>(roles.values()).iterator();
242 } finally {
243 readLock.unlock();
244 }
245 }
246
247
248
251 @Override
252 public Iterator<User> getUsers() {
253 readLock.lock();
254 try {
255 return new ArrayList<>(users.values()).iterator();
256 } finally {
257 readLock.unlock();
258 }
259 }
260
261
262
263
264
269 @Override
270 public void close() throws Exception {
271
272 writeLock.lock();
273 try {
274 save();
275 users.clear();
276 groups.clear();
277 roles.clear();
278 } finally {
279 writeLock.unlock();
280 }
281 }
282
283
284
290 @Override
291 public Group createGroup(String groupname, String description) {
292 if (groupname == null || groupname.length() == 0) {
293 String msg = sm.getString("memoryUserDatabase.nullGroup");
294 log.warn(msg);
295 throw new IllegalArgumentException(msg);
296 }
297
298 MemoryGroup group = new MemoryGroup(this, groupname, description);
299 readLock.lock();
300 try {
301 groups.put(group.getGroupname(), group);
302 } finally {
303 readLock.unlock();
304 }
305 return group;
306 }
307
308
309
315 @Override
316 public Role createRole(String rolename, String description) {
317 if (rolename == null || rolename.length() == 0) {
318 String msg = sm.getString("memoryUserDatabase.nullRole");
319 log.warn(msg);
320 throw new IllegalArgumentException(msg);
321 }
322
323 MemoryRole role = new MemoryRole(this, rolename, description);
324 readLock.lock();
325 try {
326 roles.put(role.getRolename(), role);
327 } finally {
328 readLock.unlock();
329 }
330 return role;
331 }
332
333
334
341 @Override
342 public User createUser(String username, String password, String fullName) {
343
344 if (username == null || username.length() == 0) {
345 String msg = sm.getString("memoryUserDatabase.nullUser");
346 log.warn(msg);
347 throw new IllegalArgumentException(msg);
348 }
349
350 MemoryUser user = new MemoryUser(this, username, password, fullName);
351 readLock.lock();
352 try {
353 users.put(user.getUsername(), user);
354 } finally {
355 readLock.unlock();
356 }
357 return user;
358 }
359
360
361
367 @Override
368 public Group findGroup(String groupname) {
369 readLock.lock();
370 try {
371 return groups.get(groupname);
372 } finally {
373 readLock.unlock();
374 }
375 }
376
377
378
384 @Override
385 public Role findRole(String rolename) {
386 readLock.lock();
387 try {
388 return roles.get(rolename);
389 } finally {
390 readLock.unlock();
391 }
392 }
393
394
395
401 @Override
402 public User findUser(String username) {
403 readLock.lock();
404 try {
405 return users.get(username);
406 } finally {
407 readLock.unlock();
408 }
409 }
410
411
412
417 @Override
418 public void open() throws Exception {
419 writeLock.lock();
420 try {
421
422 users.clear();
423 groups.clear();
424 roles.clear();
425
426 String pathName = getPathname();
427 try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getResource(pathName)) {
428 this.lastModified = resource.getURI().toURL().openConnection().getLastModified();
429
430
431 Digester digester = new Digester();
432 try {
433 digester.setFeature(
434 "http:, true);
435 } catch (Exception e) {
436 log.warn(sm.getString("memoryUserDatabase.xmlFeatureEncoding"), e);
437 }
438 digester.addFactoryCreate("tomcat-users/group",
439 new MemoryGroupCreationFactory(this), true);
440 digester.addFactoryCreate("tomcat-users/role",
441 new MemoryRoleCreationFactory(this), true);
442 digester.addFactoryCreate("tomcat-users/user",
443 new MemoryUserCreationFactory(this), true);
444
445
446 digester.parse(resource.getInputStream());
447 } catch (IOException ioe) {
448 log.error(sm.getString("memoryUserDatabase.fileNotFound", pathName));
449 } catch (Exception e) {
450
451 users.clear();
452 groups.clear();
453 roles.clear();
454 throw e;
455 }
456 } finally {
457 writeLock.unlock();
458 }
459 }
460
461
462
467 @Override
468 public void removeGroup(Group group) {
469 readLock.lock();
470 try {
471 Iterator<User> users = getUsers();
472 while (users.hasNext()) {
473 User user = users.next();
474 user.removeGroup(group);
475 }
476 groups.remove(group.getGroupname());
477 } finally {
478 readLock.unlock();
479 }
480 }
481
482
483
488 @Override
489 public void removeRole(Role role) {
490 readLock.lock();
491 try {
492 Iterator<Group> groups = getGroups();
493 while (groups.hasNext()) {
494 Group group = groups.next();
495 group.removeRole(role);
496 }
497 Iterator<User> users = getUsers();
498 while (users.hasNext()) {
499 User user = users.next();
500 user.removeRole(role);
501 }
502 roles.remove(role.getRolename());
503 } finally {
504 readLock.unlock();
505 }
506 }
507
508
509
514 @Override
515 public void removeUser(User user) {
516 readLock.lock();
517 try {
518 users.remove(user.getUsername());
519 } finally {
520 readLock.unlock();
521 }
522 }
523
524
525
531 public boolean isWriteable() {
532
533 File file = new File(pathname);
534 if (!file.isAbsolute()) {
535 file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), pathname);
536 }
537 File dir = file.getParentFile();
538 return dir.exists() && dir.isDirectory() && dir.canWrite();
539 }
540
541
542
548 @Override
549 public void save() throws Exception {
550
551 if (getReadonly()) {
552 log.error(sm.getString("memoryUserDatabase.readOnly"));
553 return;
554 }
555
556 if (!isWriteable()) {
557 log.warn(sm.getString("memoryUserDatabase.notPersistable"));
558 return;
559 }
560
561
562 File fileNew = new File(pathnameNew);
563 if (!fileNew.isAbsolute()) {
564 fileNew = new File(System.getProperty(Globals.CATALINA_BASE_PROP), pathnameNew);
565 }
566
567 writeLock.lock();
568 try {
569 try (FileOutputStream fos = new FileOutputStream(fileNew);
570 OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
571 PrintWriter writer = new PrintWriter(osw)) {
572
573
574 writer.println("<?xml version='1.0' encoding='utf-8'?>");
575 writer.println("<tomcat-users xmlns=\"http:
576 writer.print(" ");
577 writer.println("xmlns:xsi=\"http:
578 writer.print(" ");
579 writer.println("xsi:schemaLocation=\"http:
580 writer.println(" version=\"1.0\">");
581
582
583 Iterator<?> values = null;
584 values = getRoles();
585 while (values.hasNext()) {
586 writer.print(" ");
587 writer.println(values.next());
588 }
589 values = getGroups();
590 while (values.hasNext()) {
591 writer.print(" ");
592 writer.println(values.next());
593 }
594 values = getUsers();
595 while (values.hasNext()) {
596 writer.print(" ");
597 writer.println(((MemoryUser) values.next()).toXml());
598 }
599
600
601 writer.println("</tomcat-users>");
602
603
604 if (writer.checkError()) {
605 throw new IOException(sm.getString("memoryUserDatabase.writeException",
606 fileNew.getAbsolutePath()));
607 }
608 } catch (IOException e) {
609 if (fileNew.exists() && !fileNew.delete()) {
610 log.warn(sm.getString("memoryUserDatabase.fileDelete", fileNew));
611 }
612 throw e;
613 }
614 this.lastModified = fileNew.lastModified();
615 } finally {
616 writeLock.unlock();
617 }
618
619
620 File fileOld = new File(pathnameOld);
621 if (!fileOld.isAbsolute()) {
622 fileOld = new File(System.getProperty(Globals.CATALINA_BASE_PROP), pathnameOld);
623 }
624 if (fileOld.exists() && !fileOld.delete()) {
625 throw new IOException(sm.getString("memoryUserDatabase.fileDelete", fileOld));
626 }
627 File fileOrig = new File(pathname);
628 if (!fileOrig.isAbsolute()) {
629 fileOrig = new File(System.getProperty(Globals.CATALINA_BASE_PROP), pathname);
630 }
631 if (fileOrig.exists()) {
632 if (!fileOrig.renameTo(fileOld)) {
633 throw new IOException(sm.getString("memoryUserDatabase.renameOld",
634 fileOld.getAbsolutePath()));
635 }
636 }
637 if (!fileNew.renameTo(fileOrig)) {
638 if (fileOld.exists()) {
639 if (!fileOld.renameTo(fileOrig)) {
640 log.warn(sm.getString("memoryUserDatabase.restoreOrig", fileOld));
641 }
642 }
643 throw new IOException(sm.getString("memoryUserDatabase.renameNew",
644 fileOrig.getAbsolutePath()));
645 }
646 if (fileOld.exists() && !fileOld.delete()) {
647 throw new IOException(sm.getString("memoryUserDatabase.fileDelete", fileOld));
648 }
649 }
650
651
652 @Override
653 public void backgroundProcess() {
654 if (!watchSource) {
655 return;
656 }
657
658 URI uri = ConfigFileLoader.getSource().getURI(getPathname());
659 URLConnection uConn = null;
660 try {
661 URL url = uri.toURL();
662 uConn = url.openConnection();
663
664 if (this.lastModified != uConn.getLastModified()) {
665 writeLock.lock();
666 try {
667 long detectedLastModified = uConn.getLastModified();
668
669
670
671 if (this.lastModified != detectedLastModified &&
672 detectedLastModified + 2000 < System.currentTimeMillis()) {
673 log.info(sm.getString("memoryUserDatabase.reload", id, uri));
674 open();
675 }
676 } finally {
677 writeLock.unlock();
678 }
679 }
680 } catch (Exception ioe) {
681 log.error(sm.getString("memoryUserDatabase.reloadError", id, uri), ioe);
682 } finally {
683 if (uConn != null) {
684 try {
685
686 uConn.getInputStream().close();
687 } catch (FileNotFoundException fnfe) {
688
689
690
691 this.lastModified = 0;
692 } catch (IOException ioe) {
693 log.warn(sm.getString("memoryUserDatabase.fileClose", pathname), ioe);
694 }
695 }
696 }
697 }
698
699
700
703 @Override
704 public String toString() {
705 StringBuilder sb = new StringBuilder("MemoryUserDatabase[id=");
706 sb.append(this.id);
707 sb.append(",pathname=");
708 sb.append(pathname);
709 sb.append(",groupCount=");
710 sb.append(this.groups.size());
711 sb.append(",roleCount=");
712 sb.append(this.roles.size());
713 sb.append(",userCount=");
714 sb.append(this.users.size());
715 sb.append("]");
716 return sb.toString();
717 }
718 }
719
720
721
724 class MemoryGroupCreationFactory extends AbstractObjectCreationFactory {
725
726 public MemoryGroupCreationFactory(MemoryUserDatabase database) {
727 this.database = database;
728 }
729
730
731 @Override
732 public Object createObject(Attributes attributes) {
733 String groupname = attributes.getValue("groupname");
734 if (groupname == null) {
735 groupname = attributes.getValue("name");
736 }
737 String description = attributes.getValue("description");
738 String roles = attributes.getValue("roles");
739 Group group = database.createGroup(groupname, description);
740 if (roles != null) {
741 while (roles.length() > 0) {
742 String rolename = null;
743 int comma = roles.indexOf(',');
744 if (comma >= 0) {
745 rolename = roles.substring(0, comma).trim();
746 roles = roles.substring(comma + 1);
747 } else {
748 rolename = roles.trim();
749 roles = "";
750 }
751 if (rolename.length() > 0) {
752 Role role = database.findRole(rolename);
753 if (role == null) {
754 role = database.createRole(rolename, null);
755 }
756 group.addRole(role);
757 }
758 }
759 }
760 return group;
761 }
762
763 private final MemoryUserDatabase database;
764 }
765
766
767
770 class MemoryRoleCreationFactory extends AbstractObjectCreationFactory {
771
772 public MemoryRoleCreationFactory(MemoryUserDatabase database) {
773 this.database = database;
774 }
775
776
777 @Override
778 public Object createObject(Attributes attributes) {
779 String rolename = attributes.getValue("rolename");
780 if (rolename == null) {
781 rolename = attributes.getValue("name");
782 }
783 String description = attributes.getValue("description");
784 Role role = database.createRole(rolename, description);
785 return role;
786 }
787
788 private final MemoryUserDatabase database;
789 }
790
791
792
795 class MemoryUserCreationFactory extends AbstractObjectCreationFactory {
796
797 public MemoryUserCreationFactory(MemoryUserDatabase database) {
798 this.database = database;
799 }
800
801
802 @Override
803 public Object createObject(Attributes attributes) {
804 String username = attributes.getValue("username");
805 if (username == null) {
806 username = attributes.getValue("name");
807 }
808 String password = attributes.getValue("password");
809 String fullName = attributes.getValue("fullName");
810 if (fullName == null) {
811 fullName = attributes.getValue("fullname");
812 }
813 String groups = attributes.getValue("groups");
814 String roles = attributes.getValue("roles");
815 User user = database.createUser(username, password, fullName);
816 if (groups != null) {
817 while (groups.length() > 0) {
818 String groupname = null;
819 int comma = groups.indexOf(',');
820 if (comma >= 0) {
821 groupname = groups.substring(0, comma).trim();
822 groups = groups.substring(comma + 1);
823 } else {
824 groupname = groups.trim();
825 groups = "";
826 }
827 if (groupname.length() > 0) {
828 Group group = database.findGroup(groupname);
829 if (group == null) {
830 group = database.createGroup(groupname, null);
831 }
832 user.addGroup(group);
833 }
834 }
835 }
836 if (roles != null) {
837 while (roles.length() > 0) {
838 String rolename = null;
839 int comma = roles.indexOf(',');
840 if (comma >= 0) {
841 rolename = roles.substring(0, comma).trim();
842 roles = roles.substring(comma + 1);
843 } else {
844 rolename = roles.trim();
845 roles = "";
846 }
847 if (rolename.length() > 0) {
848 Role role = database.findRole(rolename);
849 if (role == null) {
850 role = database.createRole(rolename, null);
851 }
852 user.addRole(role);
853 }
854 }
855 }
856 return user;
857 }
858
859 private final MemoryUserDatabase database;
860 }
861