From 192437c928a43324a8143c43a3c8e73a05c89bb8 Mon Sep 17 00:00:00 2001 From: Alex O'Ree Date: Fri, 28 Nov 2025 16:38:54 -0500 Subject: [PATCH] JSPWIKI-130 see JIRA for specific details. This commit adds a new dependency on commons codec, deprecates one api and resolves both reported issues --- UPGRADING | 23 ++++++++- jspwiki-main/pom.xml | 6 +++ .../java/org/apache/wiki/WikiContext.java | 2 +- .../java/org/apache/wiki/WikiSession.java | 8 ++- .../wiki/auth/AuthorizationManager.java | 3 +- .../auth/DefaultAuthorizationManager.java | 4 +- .../apache/wiki/auth/DefaultUserManager.java | 12 +++-- .../apache/wiki/auth/user/UserDatabase.java | 4 ++ .../wiki/auth/user/XMLUserDatabase.java | 50 +++++++++++++++++++ .../wiki/auth/AuthorizationManagerTest.java | 4 +- .../org/apache/wiki/auth/UserManagerTest.java | 12 ++++- .../wiki/auth/user/XMLUserDatabaseTest.java | 50 ++++++++++++++++++- .../org/apache/wiki/plugin/GroupsTest.java | 18 ++++++- .../org/apache/wiki/plugin/IfPluginTest.java | 4 +- pom.xml | 8 +++ 15 files changed, 188 insertions(+), 20 deletions(-) diff --git a/UPGRADING b/UPGRADING index a14f33ece9..e3e6f21d62 100644 --- a/UPGRADING +++ b/UPGRADING @@ -1,5 +1,5 @@ -Apache JSPWiki 2.12.0 - Upgrading Notes +Apache JSPWiki 3.0.0 - Upgrading Notes ================================================== Licensed to the Apache Software Foundation (ASF) under one @@ -21,6 +21,27 @@ Apache JSPWiki 2.12.0 - Upgrading Notes The license file can be found in LICENSE. + +Upgrading JSPWiki to 3.0.0 +--------------------------- +Please see https://jspwiki-wiki.apache.org/Wiki.jsp?page=NewIn3.0.0 for details + +1. New requirements + * Java 17 needed to run JSPWiki + +2. Backwards incompatible changes: + * Page level access controls (i.e. [ALLOW edit/view/etc User/Role] ) logic has been changed. + Prior to this release, wiki name, full name, login name, role name or group names were allowed. + With this release and moving forward, login name, role name and group names are utilitized only. + Wiki and Full names are no longer honored. see JSPWIKI-130 for additional details. This also includes + the IF plugin. + + Review all wiki pages looking for the ALLOW/DENY and IF plugins and adjust as necessary. + +3. Many new security features have been added and enabled by default. Please review the default + jspwiki.properties file + + Upgrading JSPWiki to 2.12.0 --------------------------- diff --git a/jspwiki-main/pom.xml b/jspwiki-main/pom.xml index 2e9d3d6b2e..b2cc8e3589 100644 --- a/jspwiki-main/pom.xml +++ b/jspwiki-main/pom.xml @@ -31,6 +31,12 @@ Apache JSPWiki Main Jar + + + commons-codec + commons-codec + + ${project.groupId} jspwiki-util diff --git a/jspwiki-main/src/main/java/org/apache/wiki/WikiContext.java b/jspwiki-main/src/main/java/org/apache/wiki/WikiContext.java index ffa4868fbd..83351b6957 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/WikiContext.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/WikiContext.java @@ -545,7 +545,7 @@ public Principal getCurrentUser() { // This shouldn't happen, really... return WikiPrincipal.GUEST; } - return m_session.getUserPrincipal(); + return m_session.getLoginPrincipal(); } /** diff --git a/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java b/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java index d14fe3de5b..d52eac0385 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/WikiSession.java @@ -417,7 +417,7 @@ protected void injectUserProfilePrincipals() { throw new IllegalStateException( "User database cannot be null." ); } try { - final UserProfile profile = database.find( searchId ); + final UserProfile profile = database.findByLoginName( searchId ); final Principal[] principals = database.getPrincipals( profile.getLoginName() ); for( final Principal principal : principals ) { // Add the Principal to the Subject @@ -426,6 +426,12 @@ protected void injectUserProfilePrincipals() { // Set the user principal if needed; we prefer FullName, but the WikiName will also work final boolean isFullNamePrincipal = ( principal instanceof WikiPrincipal && ( ( WikiPrincipal )principal ).getType().equals( WikiPrincipal.FULL_NAME ) ); + if (( principal instanceof WikiPrincipal && + ( ( WikiPrincipal )principal ).getType().equals( WikiPrincipal.LOGIN_NAME )) ){ + m_loginPrincipal = principal; + } + + if ( isFullNamePrincipal ) { m_userPrincipal = principal; } else if ( !( m_userPrincipal instanceof WikiPrincipal ) ) { diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/AuthorizationManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/AuthorizationManager.java index bd3833759e..794c9ce96a 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/AuthorizationManager.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/AuthorizationManager.java @@ -233,7 +233,8 @@ default boolean hasAccess( final Context context, final HttpServletResponse resp *
  • Finally, if a user cannot be found, manufacture and return a generic {@link org.apache.wiki.auth.acl.UnresolvedPrincipal}
  • * * - * @param name the name of the Principal to resolve + * @param name the name of the Principal to resolve. Note: as of v3.0.0, the + * underlying behavior has changed. Principals can be resolved via login names only. * @return the fully-resolved Principal */ Principal resolvePrincipal( final String name ); diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthorizationManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthorizationManager.java index 8fe4e84075..47c9e84eb9 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthorizationManager.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultAuthorizationManager.java @@ -350,11 +350,9 @@ public Principal resolvePrincipal( final String name ) { // Ok, no luck---this must be a user principal final Principal[] principals; - final UserProfile profile; final UserDatabase db = m_engine.getManager( UserManager.class ).getUserDatabase(); try { - profile = db.find( name ); - principals = db.getPrincipals( profile.getLoginName() ); + principals = db.getPrincipals( name ); for( final Principal value : principals ) { principal = value; if( principal.getName().equals( name ) ) { diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java index 512196a189..7dcbd402f6 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java @@ -153,7 +153,7 @@ public UserProfile getUserProfile( final Session session ) { if ( session.isAuthenticated() ) { user = session.getUserPrincipal(); try { - profile = getUserDatabase().find( user.getName() ); + profile = getUserDatabase().findByWikiName( user.getName()); newProfile = false; } catch( final NoSuchPrincipalException e ) { } } @@ -162,6 +162,8 @@ public UserProfile getUserProfile( final Session session ) { profile = getUserDatabase().newProfile(); if ( user != null ) { profile.setLoginName( user.getName() ); + } else { + LOG.warn("new profile however the user principal is null. this shouldn't happen"); } if ( !profile.isNew() ) { throw new IllegalStateException( "New profile should be marked 'new'. Check your UserProfile implementation." ); @@ -372,8 +374,10 @@ public void validateProfile( final Context context, final UserProfile profile ) final String email = profile.getEmail(); // It's illegal to use as a full name someone else's login name + //FIXME this check seems dubious at best. can't be two John Smith's in the same + //database? try { - otherProfile = getUserDatabase().find( fullName ); + otherProfile = getUserDatabase().findByFullName( fullName ); if( otherProfile != null && !profile.equals( otherProfile ) && !fullName.equals( otherProfile.getFullname() ) ) { final Object[] args = { fullName }; session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString( "security.error.illegalfullname" ), args ) ); @@ -382,7 +386,7 @@ public void validateProfile( final Context context, final UserProfile profile ) // It's illegal to use as a login name someone else's full name try { - otherProfile = getUserDatabase().find( loginName ); + otherProfile = getUserDatabase().findByFullName(loginName ); if( otherProfile != null && !profile.equals( otherProfile ) && !loginName.equals( otherProfile.getLoginName() ) ) { final Object[] args = { loginName }; session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString( "security.error.illegalloginname" ), args ) ); @@ -479,7 +483,7 @@ public void service( final HttpServletRequest req, final HttpServletResponse res */ public UserProfile getUserInfo( final String uid ) throws NoSuchPrincipalException { if( m_manager != null ) { - return m_manager.getUserDatabase().find( uid ); + return m_manager.getUserDatabase().findByUid( uid ); } throw new IllegalStateException( "The manager is offline." ); diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/user/UserDatabase.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/user/UserDatabase.java index f604e50244..bb9ebbd13d 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/user/UserDatabase.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/user/UserDatabase.java @@ -77,7 +77,11 @@ public interface UserDatabase { * that supplied the name is unknown. * * @param index the login name, full name, or wiki name + * @deprecated depending on the use case, this API's usage can be dangerous. + * Recommend using other APIs for more explicit lookup types. see JSPWIKI-130 + * for additional details. */ + @Deprecated UserProfile find( String index ) throws NoSuchPrincipalException; /** diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/user/XMLUserDatabase.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/user/XMLUserDatabase.java index 5e622b98e7..641ef9a861 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/user/XMLUserDatabase.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/user/XMLUserDatabase.java @@ -37,6 +37,7 @@ Licensed to the Apache Software Foundation (ASF) under one import javax.xml.parsers.ParserConfigurationException; import java.io.BufferedWriter; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStreamWriter; @@ -47,6 +48,7 @@ Licensed to the Apache Software Foundation (ASF) under one import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; import java.util.Map; import java.util.Properties; @@ -55,6 +57,8 @@ Licensed to the Apache Software Foundation (ASF) under one import java.util.stream.Collectors; import java.util.stream.IntStream; import javax.xml.XMLConstants; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; /** *

    Manages {@link DefaultUserProfile} objects using XML files for persistence. Passwords are hashed using SHA1. User entries are simple @@ -193,7 +197,36 @@ public void initialize( final Engine engine, final Properties props ) throws NoR } LOG.info( "XML user database at " + c_file.getAbsolutePath() ); + File checkFile = new File(c_file.getParent(), c_file.getName() + ".check"); + if (checkFile.exists()) { + + FileInputStream fis = null; + byte[] computedHash = null; + byte[] storedHash = null; + try { + fis = new FileInputStream(c_file); + computedHash = DigestUtils.sha256(fis); + storedHash = FileUtils.readFileToByteArray(checkFile); + } catch (Exception ex) { + throw new RuntimeException("Failed to compute integrity check. ", ex); + } finally { + if (fis != null) + try { + fis.close(); + } catch (IOException ex) { + LOG.debug(ex.getMessage()); + } + } + if (Arrays.equals(computedHash, storedHash)) { + LOG.info("XML user database hash check passed. no modifications detected."); + } else { + throw new RuntimeException("XML user database has been modified outside of JSP Wiki. Refusing start up. An administrator will need to restore the file from backup"); + } + } else { + LOG.info("XML user database check file does not exist. This is normal if JSPWIki was just installed."); + } + buildDOM(); sanitizeDOM(); } @@ -303,6 +336,23 @@ private void saveDOM() throws WikiSecurityException { } LOG.error( "Could not save database: " + c_file + ". Check the file permissions" ); } + FileInputStream fis = null; + try { + fis = new FileInputStream(c_file); + byte[] hash = DigestUtils.sha256(fis); + File checkFile = new File(c_file.getParent(), c_file.getName() + ".check"); + FileUtils.writeByteArrayToFile(checkFile, hash); + } catch (Exception ex) { + LOG.warn("Failed to recompute and/or save the check file", ex); + } finally { + if (fis!=null) { + try { + fis.close(); + } catch (IOException ex) { + LOG.debug(ex.getMessage()); + } + } + } } private long c_lastCheck; diff --git a/jspwiki-main/src/test/java/org/apache/wiki/auth/AuthorizationManagerTest.java b/jspwiki-main/src/test/java/org/apache/wiki/auth/AuthorizationManagerTest.java index d015525f3b..07127dd324 100644 --- a/jspwiki-main/src/test/java/org/apache/wiki/auth/AuthorizationManagerTest.java +++ b/jspwiki-main/src/test/java/org/apache/wiki/auth/AuthorizationManagerTest.java @@ -519,8 +519,8 @@ public void testResolveUsers() throws WikiException Assertions.fail( "Failed save: " + e.getLocalizedMessage() ); } Assertions.assertEquals( new WikiPrincipal( "authmanagertest", WikiPrincipal.LOGIN_NAME ), m_auth.resolvePrincipal( "authmanagertest" ) ); - Assertions.assertEquals( new WikiPrincipal( "AuthorizationManagerTest User", WikiPrincipal.FULL_NAME ), m_auth.resolvePrincipal( "AuthorizationManagerTest User" ) ); - Assertions.assertEquals( new WikiPrincipal( "AuthorizationManagerTestUser", WikiPrincipal.WIKI_NAME ), m_auth.resolvePrincipal( "AuthorizationManagerTestUser" ) ); + //Assertions.assertEquals( new WikiPrincipal( "AuthorizationManagerTest User", WikiPrincipal.FULL_NAME ), m_auth.resolvePrincipal( "AuthorizationManagerTest User" ) ); + //Assertions.assertEquals( new WikiPrincipal( "AuthorizationManagerTestUser", WikiPrincipal.WIKI_NAME ), m_auth.resolvePrincipal( "AuthorizationManagerTestUser" ) ); try { m_engine.getManager( UserManager.class ).getUserDatabase().deleteByLoginName( "authmanagertest" ); diff --git a/jspwiki-main/src/test/java/org/apache/wiki/auth/UserManagerTest.java b/jspwiki-main/src/test/java/org/apache/wiki/auth/UserManagerTest.java index f6d2027def..97ba5c0a71 100644 --- a/jspwiki-main/src/test/java/org/apache/wiki/auth/UserManagerTest.java +++ b/jspwiki-main/src/test/java/org/apache/wiki/auth/UserManagerTest.java @@ -20,6 +20,7 @@ Licensed to the Apache Software Foundation (ASF) under one import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; +import java.io.File; import org.apache.commons.lang3.ArrayUtils; import org.apache.wiki.HttpMockFactory; import org.apache.wiki.TestEngine; @@ -52,6 +53,9 @@ Licensed to the Apache Software Foundation (ASF) under one import java.util.Collection; import java.util.List; import java.util.Properties; +import java.util.UUID; +import org.apache.commons.io.FileUtils; +import org.apache.wiki.WikiEngine; class UserManagerTest { @@ -60,7 +64,7 @@ class UserManagerTest { UserManager m_mgr; UserDatabase m_db; String m_groupName; - + File target; /** * */ @@ -71,8 +75,11 @@ void setUp() throws Exception { // Make sure user profile save workflow is OFF props.remove( "jspwiki.approver" + WorkflowManager.WF_UP_CREATE_SAVE_APPROVER ); + target = new File("target/XMLUserDatabaseTest" + UUID.randomUUID().toString() + ".xml"); + FileUtils.copyFile(new File("src/test/resources/userdatabase.xml" ), target); + props.put( XMLUserDatabase.PROP_USERDATABASE, target.getAbsolutePath() ); // Make sure we are using the XML user database - props.put( XMLUserDatabase.PROP_USERDATABASE, "target/test-classes/userdatabase.xml" ); + m_engine = new TestEngine( props ); m_mgr = m_engine.getManager( UserManager.class ); m_db = m_mgr.getUserDatabase(); @@ -277,6 +284,7 @@ void testSetUserProfile() throws Exception { // Create a new user with random name final Context context = Wiki.context().create( m_engine, HttpMockFactory.createHttpRequest(), "" ); final String loginName = "TestUser" + System.currentTimeMillis(); + UserProfile profile = m_db.newProfile(); profile.setEmail( "jspwiki.tests@mailinator.com" ); profile.setLoginName( loginName ); diff --git a/jspwiki-main/src/test/java/org/apache/wiki/auth/user/XMLUserDatabaseTest.java b/jspwiki-main/src/test/java/org/apache/wiki/auth/user/XMLUserDatabaseTest.java index 3ea0fdd38f..b41a04cac2 100644 --- a/jspwiki-main/src/test/java/org/apache/wiki/auth/user/XMLUserDatabaseTest.java +++ b/jspwiki-main/src/test/java/org/apache/wiki/auth/user/XMLUserDatabaseTest.java @@ -18,6 +18,8 @@ Licensed to the Apache Software Foundation (ASF) under one */ package org.apache.wiki.auth.user; +import java.io.File; +import java.io.IOException; import org.apache.commons.lang3.ArrayUtils; import org.apache.wiki.TestEngine; import org.apache.wiki.WikiEngine; @@ -28,13 +30,16 @@ Licensed to the Apache Software Foundation (ASF) under one import org.apache.wiki.util.CryptoUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.Serializable; +import java.nio.charset.StandardCharsets; import java.security.Principal; import java.util.Map; import java.util.Properties; +import java.util.UUID; +import org.apache.commons.io.FileUtils; +import org.apache.wiki.auth.authorize.XMLGroupDatabase; public class XMLUserDatabaseTest { @@ -44,7 +49,10 @@ public class XMLUserDatabaseTest { @BeforeEach public void setUp() throws Exception { final Properties props = TestEngine.getTestProperties(); - props.put( XMLUserDatabase.PROP_USERDATABASE, "target/test-classes/userdatabase.xml" ); + File target = new File("target/XMLUserDatabaseTest" + UUID.randomUUID().toString() + ".xml"); + FileUtils.copyFile(new File("src/test/resources/userdatabase.xml" ), target); + props.put( XMLUserDatabase.PROP_USERDATABASE, target.getAbsolutePath() ); + final WikiEngine engine = new TestEngine( props ); m_db = new XMLUserDatabase(); m_db.initialize( engine, props ); @@ -313,4 +321,42 @@ public void testValidatePassword() { Assertions.assertTrue( m_db.validatePassword( "user", "password" ) ); } + + @Test + public void JSPWIKI_130() throws Exception { + final Properties props = TestEngine.getTestProperties(); + File target = new File("target/JSPWIKI_130" + UUID.randomUUID().toString() + ".xml"); + FileUtils.copyFile(new File("src/test/resources/userdatabase.xml"), target); + props.put(XMLUserDatabase.PROP_USERDATABASE, target.getAbsolutePath()); + final WikiEngine engine = new TestEngine(props); + XMLUserDatabase m_db = new XMLUserDatabase(); + m_db.initialize(engine, props); + //create a user and save it the changes. + // Create new user & verify it saved ok + UserProfile profile = m_db.newProfile(); + profile.setEmail( "renamed@mailinator.com" ); + profile.setFullname( "Renamed User" ); + profile.setLoginName( "olduser" ); + profile.setPassword( "password" ); + m_db.save( profile ); + + //confirm the check file exists + File f = new File(target.getParent(), target.getName() + ".check"); + Assertions.assertTrue(f.exists()); + + //shutdown the database manager + + //change a bit or two in the check file. + FileUtils.writeStringToFile(f, "you've been hacked", StandardCharsets.UTF_8); + + + //start it back up again, expecting it to barf + try{ + m_db.initialize(engine, props); + Assertions.fail("xml database check did not trigger"); + }catch (RuntimeException ex) { + //this is expected + } + + } } diff --git a/jspwiki-main/src/test/java/org/apache/wiki/plugin/GroupsTest.java b/jspwiki-main/src/test/java/org/apache/wiki/plugin/GroupsTest.java index 104c1ed6e4..b5cb7cdb0a 100644 --- a/jspwiki-main/src/test/java/org/apache/wiki/plugin/GroupsTest.java +++ b/jspwiki-main/src/test/java/org/apache/wiki/plugin/GroupsTest.java @@ -19,17 +19,33 @@ Licensed to the Apache Software Foundation (ASF) under one package org.apache.wiki.plugin; +import java.io.File; +import java.io.IOException; +import java.util.Properties; +import java.util.UUID; +import org.apache.commons.io.FileUtils; import org.apache.wiki.TestEngine; +import org.apache.wiki.auth.authorize.XMLGroupDatabase; import org.apache.wiki.pages.PageManager; import org.apache.wiki.render.RenderingManager; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class GroupsTest { + static TestEngine testEngine; - TestEngine testEngine = TestEngine.build(); + @BeforeAll + public static void init() throws IOException { + final Properties props = TestEngine.getTestProperties(); + File target = new File("target/XMLUserDatabaseTest" + UUID.randomUUID().toString() + ".xml"); + FileUtils.copyFile(new File("src/test/resources/groupdatabase.xml"), target); + props.put(XMLGroupDatabase.PROP_DATABASE, target.getAbsolutePath()); + testEngine = TestEngine.build(); + } + @AfterEach public void tearDown() throws Exception { diff --git a/jspwiki-main/src/test/java/org/apache/wiki/plugin/IfPluginTest.java b/jspwiki-main/src/test/java/org/apache/wiki/plugin/IfPluginTest.java index b7a99ca8de..0f06c5a43b 100644 --- a/jspwiki-main/src/test/java/org/apache/wiki/plugin/IfPluginTest.java +++ b/jspwiki-main/src/test/java/org/apache/wiki/plugin/IfPluginTest.java @@ -67,7 +67,7 @@ Context getJanneBasedWikiContextFor( final Page page ) throws WikiException { */ @Test void testIfPluginUserAllowed() throws WikiException { - final String src = "[{IfPlugin user='Janne Jalkanen'\n\nContent visible for Janne Jalkanen}]"; + final String src = "[{IfPlugin user='janne'\n\nContent visible for Janne Jalkanen}]"; final String expected = "

    Content visible for Janne Jalkanen

    \n"; testEngine.saveText( "Test", src ); @@ -85,7 +85,7 @@ void testIfPluginUserAllowed() throws WikiException { */ @Test void testIfPluginUserNotAllowed() throws WikiException { - final String src = "[{IfPlugin user='!Janne Jalkanen'\n\nContent NOT visible for Janne Jalkanen}]"; + final String src = "[{IfPlugin user='!janne'\n\nContent NOT visible for Janne Jalkanen}]"; final String expected = "\n"; testEngine.saveText( "Test", src ); diff --git a/pom.xml b/pom.xml index 8569bf65cf..232d8840f8 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,7 @@ 4.3.0 1.12.565 4.5.0 + 1.20.0 2.0.0-M4 2.20.0 3.19.0 @@ -168,6 +169,13 @@ selenide ${selenide.version}
    + + + commons-codec + commons-codec + ${commons-codec.version} + + commons-net commons-net