You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2019/06/24 18:06:06 UTC

[tomcat] 02/02: Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=58590

This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 8.5.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit f2dfcb07f3aa00676f97e9a9da9ed4f1349e2ab2
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Mon Jun 24 18:01:31 2019 +0100

    Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=58590
    
    Add the ability for a MemoryUserDatabase to monitor the backing XML file
    for changes and reload the source file if a change in the last modified
    time is detected. This is enabled by default meaning that changes to
    $CATALINA_BASE/conf/tomcat-users.xml will now take effect a short time
    after the file is saved.
---
 .../apache/catalina/realm/UserDatabaseRealm.java   |   9 ++
 .../apache/catalina/users/LocalStrings.properties  |   5 +
 .../catalina/users/LocalStrings_es.properties      |   2 +
 .../catalina/users/LocalStrings_fr.properties      |  14 +-
 .../catalina/users/LocalStrings_ja.properties      |  12 ++
 .../apache/catalina/users/MemoryUserDatabase.java  | 156 +++++++++++++++------
 .../catalina/users/MemoryUserDatabaseFactory.java  |   5 +
 webapps/docs/changelog.xml                         |   7 +
 webapps/docs/jndi-resources-howto.xml              |   8 +-
 9 files changed, 176 insertions(+), 42 deletions(-)

diff --git a/java/org/apache/catalina/realm/UserDatabaseRealm.java b/java/org/apache/catalina/realm/UserDatabaseRealm.java
index 064ac59..38f8822 100644
--- a/java/org/apache/catalina/realm/UserDatabaseRealm.java
+++ b/java/org/apache/catalina/realm/UserDatabaseRealm.java
@@ -29,6 +29,7 @@ import org.apache.catalina.Role;
 import org.apache.catalina.User;
 import org.apache.catalina.UserDatabase;
 import org.apache.catalina.Wrapper;
+import org.apache.catalina.users.MemoryUserDatabase;
 import org.apache.tomcat.util.ExceptionUtils;
 
 /**
@@ -151,6 +152,14 @@ public class UserDatabaseRealm extends RealmBase {
     }
 
 
+    @Override
+    public void backgroundProcess() {
+        if (database instanceof MemoryUserDatabase) {
+            ((MemoryUserDatabase) database).backgroundProcess();
+        }
+    }
+
+
     /**
      * Return the password associated with the given principal's user name.
      */
diff --git a/java/org/apache/catalina/users/LocalStrings.properties b/java/org/apache/catalina/users/LocalStrings.properties
index 7b76a1d..84d2754 100644
--- a/java/org/apache/catalina/users/LocalStrings.properties
+++ b/java/org/apache/catalina/users/LocalStrings.properties
@@ -13,13 +13,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+memoryUserDatabase.fileClose=Failed to close [{0}]
+memoryUserDatabase.fileDelete=Failed to delete [{0}]
 memoryUserDatabase.fileNotFound=The specified user database [{0}] could not be found
 memoryUserDatabase.notPersistable=User database is not persistable - no write permissions on directory
 memoryUserDatabase.nullGroup=Null or zero length group name specified. The group will be ignored.
 memoryUserDatabase.nullRole=Null or zero length role name specified. The role will be ignored.
 memoryUserDatabase.nullUser=Null or zero length user name specified. The user will be ignored.
 memoryUserDatabase.readOnly=User database has been configured to be read only. Changes cannot be saved
+memoryUserDatabase.reload=Reloading memory user database [{0}] from updated source [{1}]
+memoryUserDatabase.reloadError=Error reloading memory user database [{0}] from updated source [{1}]
 memoryUserDatabase.renameNew=Cannot rename new file to [{0}]
 memoryUserDatabase.renameOld=Cannot rename original file to [{0}]
+memoryUserDatabase.restoreOrig=Cannot restore [{0}] to original file
 memoryUserDatabase.writeException=IOException writing to [{0}]
 memoryUserDatabase.xmlFeatureEncoding=Exception configuring digester to permit java encoding names in XML files. Only IANA encoding names will be supported.
diff --git a/java/org/apache/catalina/users/LocalStrings_es.properties b/java/org/apache/catalina/users/LocalStrings_es.properties
index 40d5426..e9ab723 100644
--- a/java/org/apache/catalina/users/LocalStrings_es.properties
+++ b/java/org/apache/catalina/users/LocalStrings_es.properties
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+memoryUserDatabase.fileNotFound=El usuario de base de datos especificado [{0}] no pudo ser encontrado
 memoryUserDatabase.notPersistable=La base de datos de usuario no es persistible - no hay permisos de grabación sobre el directorio
 memoryUserDatabase.nullGroup=Se ha especificado un nombre de grupo nulo o de tamaño cero. Se ignora el grupo.
 memoryUserDatabase.nullRole=Se ha especificado un nombre rol nulo o de tamaño cero. Se ignora el rol.
@@ -20,5 +21,6 @@ memoryUserDatabase.nullUser=Se ha especificado un nombre de usuario nulo o de ta
 memoryUserDatabase.readOnly=User database has been configured to be read only. Changes cannot be saved
 memoryUserDatabase.renameNew=Imposible de renombrar el archivo nuevo a [{0}]
 memoryUserDatabase.renameOld=Imposible de renombrar el archivo original a [{0}]
+memoryUserDatabase.restoreOrig=No se puede restablecer [{0}] al archivo original
 memoryUserDatabase.writeException=IOException durante la escritura hacia [{0}]
 memoryUserDatabase.xmlFeatureEncoding=Excepción al configurar el resumidor para permitir nombres codificados en java en los ficheros XML. Sólo se soportarán los nombres con codificación IANA.
diff --git a/java/org/apache/catalina/users/LocalStrings_fr.properties b/java/org/apache/catalina/users/LocalStrings_fr.properties
index a2f8b7d..e31177a 100644
--- a/java/org/apache/catalina/users/LocalStrings_fr.properties
+++ b/java/org/apache/catalina/users/LocalStrings_fr.properties
@@ -13,6 +13,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+memoryUserDatabase.fileClose=Echec de fermeture [{0}]
+memoryUserDatabase.fileDelete=Impossible d''effacer [{0}]
+memoryUserDatabase.fileNotFound=La base d''utilisateurs spécifiée [{0}] n''a pas été trouvée
+memoryUserDatabase.notPersistable=La base de donnée des utilisateurs ne peut pas être persistée, il n'y a pas de permissions d'écriture sur le répertoire
+memoryUserDatabase.nullGroup=Un nom de groupe nul ou vide a été spécifié, le groupe sera ignoré
+memoryUserDatabase.nullRole=Le nom du rôle spécifié est nul ou a une taille de zéro. Le rôle sera ignoré.
+memoryUserDatabase.nullUser=Le nom d'utilisateur est null ou a une longueur de zéro, il sera ignoré
+memoryUserDatabase.readOnly=La base de donnée utilisateurs a été configurée en mode lecture seule, les modifications ne peuvent être sauvegardées
+memoryUserDatabase.reload=Rechargement de la base de données des utilisateurs [{0}] à partir de la source mise à jour [{1}]
+memoryUserDatabase.reloadError=Erreur de rechargement de la base de donnée utilisateurs [{0}] à partir de la source mise à jour [{1}]
 memoryUserDatabase.renameNew=Impossible de renommer le nouveau fichier en [{0}]
-memoryUserDatabase.renameOld=Impossible de renommer le fichier original en [{0}]
+memoryUserDatabase.renameOld=Impossible de renommer le fichier d''origine en [{0}]
+memoryUserDatabase.restoreOrig=Impossible de restaurer [{0}] vers le fichier d''origine
 memoryUserDatabase.writeException=IOException lors de l''écriture vers [{0}]
+memoryUserDatabase.xmlFeatureEncoding=Exception lors de la configuration du Digester pour permettre des noms d'encodage Java dans les fichiers XML, seuls le noms IANA seront supportés
diff --git a/java/org/apache/catalina/users/LocalStrings_ja.properties b/java/org/apache/catalina/users/LocalStrings_ja.properties
index 279f966..6179f1f 100644
--- a/java/org/apache/catalina/users/LocalStrings_ja.properties
+++ b/java/org/apache/catalina/users/LocalStrings_ja.properties
@@ -13,6 +13,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+memoryUserDatabase.fileClose=[{0}]のクローズに失敗しました
+memoryUserDatabase.fileDelete=[{0}]を削除できませんでした
+memoryUserDatabase.fileNotFound=ユーザー情報データベースとして指定された [{0}] は存在しません。
+memoryUserDatabase.notPersistable=ユーザーデータベースは永続的ではありません - ディレクトリに対する書き込み権限がありません。
+memoryUserDatabase.nullGroup=Nullまたは長さゼロのグループ名が指定されています。 グループは無視されます。
+memoryUserDatabase.nullRole=NULLまたは長さゼロのロール名が指定されています。 ロールは無視されます。
+memoryUserDatabase.nullUser=Nullまたは長さゼロのユーザー名が指定されています。 ユーザーは無視されます。
+memoryUserDatabase.readOnly=ユーザー情報データベースは読み取り専用になっています。変更を保存できません。
+memoryUserDatabase.reload=更新されたソース[{1}]からメモリユーザーデータベース[{0}]を再ロードしています
+memoryUserDatabase.reloadError=更新されたソース[{1}]からメモリユーザーデータベース[{0}]を再ロード中にエラーが発生しました。
 memoryUserDatabase.renameNew=新しいファイル名を [{0}] に変更できません
 memoryUserDatabase.renameOld=元のファイル名を [{0}] に変更できません
+memoryUserDatabase.restoreOrig=[{0}]を元のファイルに復元できません
 memoryUserDatabase.writeException=[{0}] に書き込み中のIOExceptionです
+memoryUserDatabase.xmlFeatureEncoding=XMLファイルのJavaエンコーディング名を許可するためにdigesterを設定する際の例外。 IANAのエンコーディング名のみがサポートされます。
diff --git a/java/org/apache/catalina/users/MemoryUserDatabase.java b/java/org/apache/catalina/users/MemoryUserDatabase.java
index dccfff0..45e846c 100644
--- a/java/org/apache/catalina/users/MemoryUserDatabase.java
+++ b/java/org/apache/catalina/users/MemoryUserDatabase.java
@@ -22,6 +22,10 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.Map;
@@ -148,6 +152,9 @@ public class MemoryUserDatabase implements UserDatabase {
     private final Lock readLock = dbLock.readLock();
     private final Lock writeLock = dbLock.writeLock();
 
+    private volatile long lastModified = 0;
+    private boolean watchSource = true;
+
 
     // ------------------------------------------------------------- Properties
 
@@ -212,6 +219,17 @@ public class MemoryUserDatabase implements UserDatabase {
     }
 
 
+    public boolean getWatchSource() {
+        return watchSource;
+    }
+
+
+
+    public void setWatchSource(boolean watchSource) {
+        this.watchSource = watchSource;
+    }
+
+
     /**
      * @return the set of {@link Role}s defined in this user database.
      */
@@ -405,7 +423,16 @@ public class MemoryUserDatabase implements UserDatabase {
             roles.clear();
 
             String pathName = getPathname();
-            try (InputStream is = ConfigFileLoader.getInputStream(getPathname())) {
+            URI uri = ConfigFileLoader.getURI(pathName);
+            URLConnection uConn = null;
+
+            try {
+                URL url = uri.toURL();
+                uConn = url.openConnection();
+
+                InputStream is = uConn.getInputStream();
+                this.lastModified = uConn.getLastModified();
+
                 // Construct a digester to read the XML input file
                 Digester digester = new Digester();
                 try {
@@ -431,6 +458,15 @@ public class MemoryUserDatabase implements UserDatabase {
                 groups.clear();
                 roles.clear();
                 throw e;
+            } finally {
+                if (uConn != null) {
+                    try {
+                        // Can't close a uConn directly. Have to do it like this.
+                        uConn.getInputStream().close();
+                    } catch (IOException ioe) {
+                        log.warn(sm.getString("memoryUserDatabase.fileClose", pathname), ioe);
+                    }
+                }
             }
         } finally {
             writeLock.unlock();
@@ -542,25 +578,22 @@ public class MemoryUserDatabase implements UserDatabase {
         if (!fileNew.isAbsolute()) {
             fileNew = new File(System.getProperty(Globals.CATALINA_BASE_PROP), pathnameNew);
         }
-        PrintWriter writer = null;
+
+        writeLock.lock();
         try {
+            try (FileOutputStream fos = new FileOutputStream(fileNew);
+                    OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
+                    PrintWriter writer = new PrintWriter(osw)) {
+
+                // Print the file prolog
+                writer.println("<?xml version='1.0' encoding='utf-8'?>");
+                writer.println("<tomcat-users xmlns=\"http://tomcat.apache.org/xml\"");
+                writer.print("              ");
+                writer.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
+                writer.print("              ");
+                writer.println("xsi:schemaLocation=\"http://tomcat.apache.org/xml tomcat-users.xsd\"");
+                writer.println("              version=\"1.0\">");
 
-            // Configure our PrintWriter
-            FileOutputStream fos = new FileOutputStream(fileNew);
-            OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF8");
-            writer = new PrintWriter(osw);
-
-            // Print the file prolog
-            writer.println("<?xml version='1.0' encoding='utf-8'?>");
-            writer.println("<tomcat-users xmlns=\"http://tomcat.apache.org/xml\"");
-            writer.print("              ");
-            writer.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
-            writer.print("              ");
-            writer.println("xsi:schemaLocation=\"http://tomcat.apache.org/xml tomcat-users.xsd\"");
-            writer.println("              version=\"1.0\">");
-
-            writeLock.lock();
-            try {
                 // Print entries for each defined role, group, and user
                 Iterator<?> values = null;
                 values = getRoles();
@@ -578,27 +611,24 @@ public class MemoryUserDatabase implements UserDatabase {
                     writer.print("  ");
                     writer.println(((MemoryUser) values.next()).toXml());
                 }
-            } finally {
-                writeLock.unlock();
-            }
 
-            // Print the file epilog
-            writer.println("</tomcat-users>");
+                // Print the file epilog
+                writer.println("</tomcat-users>");
 
-            // Check for errors that occurred while printing
-            if (writer.checkError()) {
-                writer.close();
-                fileNew.delete();
-                throw new IOException(sm.getString("memoryUserDatabase.writeException",
-                        fileNew.getAbsolutePath()));
-            }
-            writer.close();
-        } catch (IOException e) {
-            if (writer != null) {
-                writer.close();
+                // Check for errors that occurred while printing
+                if (writer.checkError()) {
+                    throw new IOException(sm.getString("memoryUserDatabase.writeException",
+                            fileNew.getAbsolutePath()));
+                }
+            } catch (IOException e) {
+                if (fileNew.exists() && !fileNew.delete()) {
+                    log.warn(sm.getString("memoryUserDatabase.fileDelete", fileNew));
+                }
+                throw e;
             }
-            fileNew.delete();
-            throw e;
+            this.lastModified = fileNew.lastModified();
+        } finally {
+            writeLock.unlock();
         }
 
         // Perform the required renames to permanently save this file
@@ -606,13 +636,14 @@ public class MemoryUserDatabase implements UserDatabase {
         if (!fileOld.isAbsolute()) {
             fileOld = new File(System.getProperty(Globals.CATALINA_BASE_PROP), pathnameOld);
         }
-        fileOld.delete();
+        if (fileOld.exists() && !fileOld.delete()) {
+            throw new IOException(sm.getString("memoryUserDatabase.fileDelete", fileOld));
+        }
         File fileOrig = new File(pathname);
         if (!fileOrig.isAbsolute()) {
             fileOrig = new File(System.getProperty(Globals.CATALINA_BASE_PROP), pathname);
         }
         if (fileOrig.exists()) {
-            fileOld.delete();
             if (!fileOrig.renameTo(fileOld)) {
                 throw new IOException(sm.getString("memoryUserDatabase.renameOld",
                         fileOld.getAbsolutePath()));
@@ -620,13 +651,58 @@ public class MemoryUserDatabase implements UserDatabase {
         }
         if (!fileNew.renameTo(fileOrig)) {
             if (fileOld.exists()) {
-                fileOld.renameTo(fileOrig);
+                if (!fileOld.renameTo(fileOrig)) {
+                    log.warn(sm.getString("memoryUserDatabase.restoreOrig", fileOld));
+                }
             }
             throw new IOException(sm.getString("memoryUserDatabase.renameNew",
                     fileOrig.getAbsolutePath()));
         }
-        fileOld.delete();
+        if (fileOld.exists() && !fileOld.delete()) {
+            throw new IOException(sm.getString("memoryUserDatabase.fileDelete", fileOld));
+        }
+    }
+
+
+    public void backgroundProcess() {
+        if (!watchSource) {
+            return;
+        }
 
+        URI uri = ConfigFileLoader.getURI(getPathname());
+        URLConnection uConn = null;
+        try {
+            URL url = uri.toURL();
+            uConn = url.openConnection();
+
+            if (this.lastModified != uConn.getLastModified()) {
+                writeLock.lock();
+                try {
+                    long detectedLastModified = uConn.getLastModified();
+                    // Last modified as a resolution of 1s. Ensure that a write
+                    // to the file is not in progress by ensuring that the last
+                    // modified time is at least 2 seconds ago.
+                    if (this.lastModified != detectedLastModified &&
+                            detectedLastModified + 2000 < System.currentTimeMillis()) {
+                        log.info(sm.getString("memoryUserDatabase.reload", id, uri));
+                        open();
+                    }
+                } finally {
+                    writeLock.unlock();
+                }
+            }
+        } catch (Exception ioe) {
+            log.error(sm.getString("memoryUserDatabase.reloadError", id, uri), ioe);
+        } finally {
+            if (uConn != null) {
+                try {
+                    // Can't close a uConn directly. Have to do it like this.
+                    uConn.getInputStream().close();
+                } catch (IOException ioe) {
+                    log.warn(sm.getString("memoryUserDatabase.fileClose", pathname), ioe);
+                }
+            }
+        }
     }
 
 
diff --git a/java/org/apache/catalina/users/MemoryUserDatabaseFactory.java b/java/org/apache/catalina/users/MemoryUserDatabaseFactory.java
index ac75a54..6d01ac1 100644
--- a/java/org/apache/catalina/users/MemoryUserDatabaseFactory.java
+++ b/java/org/apache/catalina/users/MemoryUserDatabaseFactory.java
@@ -98,6 +98,11 @@ public class MemoryUserDatabaseFactory implements ObjectFactory {
             database.setReadonly(Boolean.parseBoolean(ra.getContent().toString()));
         }
 
+        ra = ref.get("watchSource");
+        if (ra != null) {
+            database.setWatchSource(Boolean.parseBoolean(ra.getContent().toString()));
+        }
+
         // Return the configured database instance
         database.open();
         // Don't try something we know won't work
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 32c49c4..2385a68 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -73,6 +73,13 @@
       <fix>Fix typo in UTF-32LE charset name. Patch by zhanhb vi Github.
         (fschumacher)
       </fix>
+      <add>
+        <bug>58590</bug>: Add the ability for a UserDatabase to monitor the
+        backing XML file for changes and reload the source file if a change in
+        the last modified time is detected. This is enabled by default meaning
+        that changes to <code>$CATALINA_BASE/conf/tomcat-users.xml</code> will
+        now take effect a short time after the file is saved. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Jasper">
diff --git a/webapps/docs/jndi-resources-howto.xml b/webapps/docs/jndi-resources-howto.xml
index d1bde74..4898619 100644
--- a/webapps/docs/jndi-resources-howto.xml
+++ b/webapps/docs/jndi-resources-howto.xml
@@ -25,7 +25,7 @@
     <properties>
       <author email="craigmcc@apache.org">Craig R. McClanahan</author>
       <author email="yoavs@apache.org">Yoav Shapira</author>
-      <title>JNDI Resources HOW-TO</title>
+      <title>JNDI Resources How-To</title>
     </properties>
 
 <body>
@@ -482,6 +482,12 @@ public class MyBean2 {
     is running as. Ensure that these are appropriate to maintain the security
     of your installation.</p>
 
+    <p>If referenced in a Realm, the MemoryUserDatabse will, by default, monitor
+    <code>pathname</code> for changes and reload the file if a change in the
+    last modified time is observed. This can be disabled by setting the
+    <code>watchSource</code> attribute to <code>false</code>.
+    </p>
+
     <h5>3.  Configure the Realm</h5>
 
     <p>Configure a UserDatabase Realm to use this resource as described in the


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org