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="craig...@apache.org">Craig R. McClanahan</author> <author email="yo...@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-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org