FINERACT-1794: Address improvement in file upload APIs

@@ -19,6 +19,7 @@
 package org.apache.fineract.infrastructure.documentmanagement.contentrepository;

 import com.google.common.io.Files;
+import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
@@ -42,6 +43,12 @@ public class FileSystemContentRepository implements ContentRepository {

     public static final String FINERACT_BASE_DIR = System.getProperty("user.home") + File.separator + ".fineract";

+    private final FileSystemContentPathSanitizer pathSanitizer;
+
+    public FileSystemContentRepository(final FileSystemContentPathSanitizer pathSanitizer) {
+        this.pathSanitizer = pathSanitizer;
+    }
+
     @Override
     public String saveFile(final InputStream uploadedInputStream, final DocumentCommand documentCommand) {
         final String fileName = documentCommand.getFileName();
@@ -88,23 +95,29 @@ public void deleteFile(final String documentPath) {
     }

     private void deleteFileInternal(final String documentPath) {
-        final File fileToBeDeleted = new File(documentPath);
+        String path = pathSanitizer.sanitize(documentPath);
+
+        final File fileToBeDeleted = new File(path);
         final boolean fileDeleted = fileToBeDeleted.delete();
         if (!fileDeleted) {
             // no need to throw an Error, what's a caller going to do about it, so simply log a warning
-            LOG.warn("Unable to delete file {}", documentPath);
+            LOG.warn("Unable to delete file {}", path);
         }
     }

     @Override
     public FileData fetchFile(final DocumentData documentData) {
-        final File file = new File(documentData.fileLocation());
-        return new FileData(Files.asByteSource(file), documentData.fileName(), documentData.contentType());
+        String path = pathSanitizer.sanitize(documentData.fileLocation());
+
+        final File file = new File(path);
+        return new FileData(Files.asByteSource(file), file.getName(), documentData.contentType());
     }

     @Override
     public FileData fetchImage(final ImageData imageData) {
-        final File file = new File(imageData.location());
+        String path = pathSanitizer.sanitize(imageData.location());
+
+        final File file = new File(path);
         return new FileData(Files.asByteSource(file), imageData.getEntityDisplayName(), imageData.contentType().getValue());
     }

@@ -139,9 +152,10 @@ private void makeDirectories(final String uploadDocumentLocation) throws IOExcep
     }

     private void writeFileToFileSystem(final String fileName, final InputStream uploadedInputStream, final String fileLocation) {
-        try {
-            makeDirectories(fileLocation);
-            FileUtils.copyInputStreamToFile(uploadedInputStream, new File(fileLocation)); // NOSONAR
+        try (BufferedInputStream bis = new BufferedInputStream(uploadedInputStream)) {
+            String sanitizedPath = pathSanitizer.sanitize(fileLocation, bis);
+            makeDirectories(sanitizedPath);
+            FileUtils.copyInputStreamToFile(bis, new File(sanitizedPath)); // NOSONAR
         } catch (final IOException ioException) {
             LOG.warn("writeFileToFileSystem() IOException (logged because cause is not propagated in ContentManagementException)",
                     ioException);


XWIKI-19869: Improve user property storage

  - Provide a new API for sending text based email
  - Provide a migration and a listener for informing users about it

@@ -35,7 +35,6 @@

 import org.apache.commons.lang3.StringUtils;
 import org.xwiki.component.annotation.Component;
-import org.xwiki.component.manager.ComponentManager;
 import org.xwiki.localization.ContextualLocalizationManager;
 import org.xwiki.mail.MailListener;
 import org.xwiki.mail.MailSender;
@@ -48,6 +47,9 @@
 import org.xwiki.model.reference.EntityReference;
 import org.xwiki.model.reference.LocalDocumentReference;
 import org.xwiki.security.authentication.ResetPasswordException;
+import org.xwiki.user.UserProperties;
+import org.xwiki.user.UserPropertiesResolver;
+import org.xwiki.user.UserReference;

 import com.xpn.xwiki.XWikiContext;

@@ -64,6 +66,10 @@ public class ResetPasswordMailSender
     private static final LocalDocumentReference RESET_PASSWORD_MAIL_TEMPLATE_REFERENCE =
         new LocalDocumentReference("XWiki", "ResetPasswordMailContent");

+    private static final String NO_REPLY = "no-reply@";
+    private static final String FROM = "from";
+    private static final String TO = "to";
+
     @Inject
     private MailSenderConfiguration mailSenderConfiguration;

@@ -75,11 +81,11 @@ public class ResetPasswordMailSender
     private MimeMessageFactory<MimeMessage> mimeMessageFactory;

     @Inject
-    private MailSender mailSender;
+    @Named("text")
+    private MimeMessageFactory<MimeMessage> textMimeMessageFactory;

     @Inject
-    @Named("context")
-    private ComponentManager componentManager;
+    private MailSender mailSender;

     @Inject
     private SessionFactory sessionFactory;
@@ -94,6 +100,9 @@ public class ResetPasswordMailSender
     @Named("database")
     private Provider<MailListener> mailListenerProvider;

+    @Inject
+    private Provider<UserPropertiesResolver> userPropertiesResolverProvider;
+
     /**
      * Send the reset password information by email.
      *
@@ -108,13 +117,13 @@ public void sendResetPasswordEmail(String username, InternetAddress email, URL r
         XWikiContext context = this.contextProvider.get();
         String fromAddress = this.mailSenderConfiguration.getFromAddress();
         if (StringUtils.isEmpty(fromAddress)) {
-            fromAddress = "no-reply@" + context.getRequest().getServerName();
+            fromAddress = NO_REPLY + context.getRequest().getServerName();
         }

         Map<String, Object> parameters = new HashMap<>();
-        parameters.put("from", fromAddress);
-        parameters.put("to", email);
-        parameters.put("language", context.getLocale());
+        parameters.put(FROM, fromAddress);
+        parameters.put(TO, email);
+        parameters.put("language", this.contextProvider.get().getLocale());
         parameters.put("type", "Reset Password");
         Map<String, String> velocityVariables = new HashMap<>();
         velocityVariables.put("userName", username);
@@ -124,14 +133,56 @@ public void sendResetPasswordEmail(String username, InternetAddress email, URL r
         String localizedError =
             this.localizationManager.getTranslationPlain("xe.admin.passwordReset.error.emailFailed");

-        MimeMessage message;
         try {
-            message =
+            MimeMessage message =
                 this.mimeMessageFactory.createMessage(
                     this.documentReferenceResolver.resolve(RESET_PASSWORD_MAIL_TEMPLATE_REFERENCE), parameters);
+            this.sendMessage(message, localizedError);
         } catch (MessagingException e) {
             throw new ResetPasswordException(localizedError, e);
         }
+    }
+
+    /**
+     * Allows to send a text email for security purpose.
+     *
+     * @param userReference the user to whom to send the email.
+     * @param subject the localized subject of the email
+     * @param mailContent the localized content of the email
+     * @throws ResetPasswordException in case of problem for preparing or sending the email
+     * @since 14.6RC1
+     * @since 14.4.3
+     * @since 13.10.8
+     */
+    public void sendAuthenticationSecurityEmail(UserReference userReference, String subject, String mailContent)
+        throws ResetPasswordException
+    {
+        XWikiContext context = this.contextProvider.get();
+        String fromAddress = this.mailSenderConfiguration.getFromAddress();
+        if (StringUtils.isEmpty(fromAddress)) {
+            fromAddress = NO_REPLY + context.getRequest().getServerName();
+        }
+        UserProperties userProperties = this.userPropertiesResolverProvider.get().resolve(userReference);
+        InternetAddress email = userProperties.getEmail();
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put(FROM, fromAddress);
+        parameters.put(TO, email);
+        parameters.put("subject", subject);
+
+        String localizedError =
+            this.localizationManager.getTranslationPlain("security.authentication.security.email.error");
+
+        try {
+            MimeMessage message = this.textMimeMessageFactory.createMessage(mailContent, parameters);
+            this.sendMessage(message, localizedError);
+        } catch (MessagingException e) {
+            throw new ResetPasswordException(localizedError, e);
+        }
+    }
+
+    private void sendMessage(MimeMessage message, String localizedError) throws ResetPasswordException
+    {
         MailListener mailListener = this.mailListenerProvider.get();
         this.mailSender.sendAsynchronously(Collections.singleton(message),
             this.sessionFactory.create(Collections.emptyMap()),


fix: Better support for disallowed hosts (#16842)

@@ -1,11 +1,31 @@
 package com.appsmith.util;

+import io.netty.resolver.AddressResolver;
+import io.netty.resolver.AddressResolverGroup;
+import io.netty.resolver.InetNameResolver;
+import io.netty.resolver.InetSocketAddressResolver;
+import io.netty.util.concurrent.EventExecutor;
+import io.netty.util.concurrent.Promise;
+import io.netty.util.internal.SocketUtils;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.client.reactive.ReactorClientHttpConnector;
 import org.springframework.web.reactive.function.client.WebClient;
 import reactor.netty.http.client.HttpClient;

+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
 public class WebClientUtils {

+    private static final Set<String> DISALLOWED_HOSTS = Set.of(
+            "169.254.169.254",
+            "metadata.google.internal"
+    );
+
     private WebClientUtils() {
     }

@@ -31,15 +51,86 @@ public static WebClient.Builder builder() {

     public static WebClient.Builder builder(HttpClient httpClient) {
         return WebClient.builder()
-                .clientConnector(new ReactorClientHttpConnector(applyProxyIfConfigured(httpClient)));
+                .clientConnector(new ReactorClientHttpConnector(makeSafeHttpClient(httpClient)));
     }

-    private static HttpClient applyProxyIfConfigured(HttpClient httpClient) {
+    private static HttpClient makeSafeHttpClient(HttpClient httpClient) {
         if (shouldUseSystemProxy()) {
             httpClient = httpClient.proxyWithSystemProperties();
         }

-        return httpClient;
+        return httpClient.resolver(ResolverGroup.INSTANCE);
+    }
+
+    private static class ResolverGroup extends AddressResolverGroup<InetSocketAddress> {
+        public static final ResolverGroup INSTANCE = new ResolverGroup();
+
+        @Override
+        protected AddressResolver<InetSocketAddress> newResolver(EventExecutor executor) {
+            return new InetSocketAddressResolver(executor, new NameResolver(executor));
+        }
+    }
+
+    @Slf4j
+    private static class NameResolver extends InetNameResolver {
+
+        public NameResolver(EventExecutor executor) {
+            super(executor);
+        }
+
+        private static boolean isDisallowedAndFail(String host, Promise<?> promise) {
+            if (DISALLOWED_HOSTS.contains(host)) {
+                log.warn("Host {} is disallowed. Failing the request.", host);
+                promise.setFailure(new UnknownHostException("Host not allowed."));
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        protected void doResolve(String inetHost, Promise<InetAddress> promise) {
+            if (isDisallowedAndFail(inetHost, promise)) {
+                return;
+            }
+
+            final InetAddress address;
+            try {
+                address = SocketUtils.addressByName(inetHost);
+            } catch (UnknownHostException e) {
+                promise.setFailure(e);
+                return;
+            }
+
+            if (isDisallowedAndFail(address.getHostAddress(), promise)) {
+                return;
+            }
+
+            promise.setSuccess(address);
+        }
+
+        @Override
+        protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise) {
+            if (isDisallowedAndFail(inetHost, promise)) {
+                return;
+            }
+
+            final List<InetAddress> addresses;
+            try {
+                addresses = Arrays.asList(SocketUtils.allAddressesByName(inetHost));
+            } catch (UnknownHostException e) {
+                promise.setFailure(e);
+                return;
+            }
+
+            // Even if _one_ of the addresses is disallowed, we fail the request.
+            for (InetAddress address : addresses) {
+                if (isDisallowedAndFail(address.getHostAddress(), promise)) {
+                    return;
+                }
+            }
+
+            promise.setSuccess(addresses);
+        }
     }

 }


Merge pull request from GHSA-w3rx-m34v-wrqx

* Fix error handling while reading analyzer mapping rules

Add new parseWordList method that takes a parser as a parameter. It reads custom rules from settings or a file, parses and handles errors. Make error messages less verbose for rules files outside config directory.

Signed-off-by: Rabi Panda <adnapibar@gmail.com>

* Add CHANGELOG.md

Signed-off-by: Rabi Panda <adnapibar@gmail.com>

Signed-off-by: Rabi Panda <adnapibar@gmail.com>

@@ -0,0 +1,70 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ */
+
+package org.opensearch.analysis.common;
+
+import org.apache.lucene.analysis.CharFilter;
+import org.opensearch.common.settings.Settings;
+import org.opensearch.env.Environment;
+import org.opensearch.index.analysis.AnalysisTestsHelper;
+import org.opensearch.index.analysis.CharFilterFactory;
+import org.opensearch.test.OpenSearchTestCase;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Arrays;
+
+public class MappingCharFilterFactoryTests extends OpenSearchTestCase {
+    public static CharFilterFactory create(String... rules) throws IOException {
+        OpenSearchTestCase.TestAnalysis analysis = AnalysisTestsHelper.createTestAnalysisFromSettings(
+            Settings.builder()
+                .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString())
+                .put("index.analysis.analyzer.my_analyzer.tokenizer", "standard")
+                .put("index.analysis.analyzer.my_analyzer.char_filter", "my_mappings_char_filter")
+                .put("index.analysis.char_filter.my_mappings_char_filter.type", "mapping")
+                .putList("index.analysis.char_filter.my_mappings_char_filter.mappings", rules)
+                .build(),
+            new CommonAnalysisModulePlugin()
+        );
+
+        return analysis.charFilter.get("my_mappings_char_filter");
+    }
+
+    public void testRulesOk() throws IOException {
+        MappingCharFilterFactory mappingCharFilterFactory = (MappingCharFilterFactory) create(
+            "# This is a comment",
+            ":) => _happy_",
+            ":( => _sad_"
+        );
+        CharFilter inputReader = (CharFilter) mappingCharFilterFactory.create(new StringReader("I'm so :)"));
+        char[] tempBuff = new char[14];
+        StringBuilder output = new StringBuilder();
+        while (true) {
+            int length = inputReader.read(tempBuff);
+            if (length == -1) break;
+            output.append(tempBuff, 0, length);
+        }
+        assertEquals("I'm so _happy_", output.toString());
+    }
+
+    public void testRuleError() {
+        for (String rule : Arrays.asList(
+            "",        // empty
+            "a",       // no arrow
+            "a:>b"     // invalid delimiter
+        )) {
+            RuntimeException ex = expectThrows(RuntimeException.class, () -> create(rule));
+            assertEquals("Line [1]: Invalid mapping rule : [" + rule + "]", ex.getMessage());
+        }
+    }
+
+    public void testRulePartError() {
+        RuntimeException ex = expectThrows(RuntimeException.class, () -> create("# This is a comment", ":) => _happy_", "a:b"));
+        assertEquals("Line [3]: Invalid mapping rule : [a:b]", ex.getMessage());
+    }
+}


@@ -32,6 +32,8 @@

 package org.opensearch.index.analysis;

+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 import org.apache.lucene.analysis.CharArraySet;
 import org.apache.lucene.analysis.ar.ArabicAnalyzer;
 import org.apache.lucene.analysis.bg.BulgarianAnalyzer;
@@ -94,6 +96,7 @@
  * @opensearch.internal
  */
 public class Analysis {
+    private static final Logger LOGGER = LogManager.getLogger(Analysis.class);

     public static CharArraySet parseStemExclusion(Settings settings, CharArraySet defaultStemExclusion) {
         String value = settings.get("stem_exclusion");
@@ -166,7 +169,7 @@ public static CharArraySet parseWords(
                 return resolveNamedWords(settings.getAsList(name), namedWords, ignoreCase);
             }
         }
-        List<String> pathLoadedWords = getWordList(env, settings, name);
+        List<String> pathLoadedWords = parseWordList(env, settings, name, s -> s);
         if (pathLoadedWords != null) {
             return resolveNamedWords(pathLoadedWords, namedWords, ignoreCase);
         }
@@ -207,23 +210,56 @@ private static CharArraySet resolveNamedWords(Collection<String> words, Map<Stri
     }

     public static CharArraySet getWordSet(Environment env, Settings settings, String settingsPrefix) {
-        List<String> wordList = getWordList(env, settings, settingsPrefix);
+        List<String> wordList = parseWordList(env, settings, settingsPrefix, s -> s);
         if (wordList == null) {
             return null;
         }
         boolean ignoreCase = settings.getAsBoolean(settingsPrefix + "_case", false);
         return new CharArraySet(wordList, ignoreCase);
     }

+    public static <T> List<T> parseWordList(Environment env, Settings settings, String settingPrefix, CustomMappingRuleParser<T> parser) {
+        return parseWordList(env, settings, settingPrefix + "_path", settingPrefix, parser);
+    }
+
     /**
-     * Fetches a list of words from the specified settings file. The list should either be available at the key
-     * specified by settingsPrefix or in a file specified by settingsPrefix + _path.
+     * Parses a list of words from the specified settings or from a file, with the given parser.
      *
      * @throws IllegalArgumentException
      *          If the word list cannot be found at either key.
+     * @throws RuntimeException
+     *          If there is error parsing the words
      */
-    public static List<String> getWordList(Environment env, Settings settings, String settingPrefix) {
-        return getWordList(env, settings, settingPrefix + "_path", settingPrefix, true);
+    public static <T> List<T> parseWordList(
+        Environment env,
+        Settings settings,
+        String settingPath,
+        String settingList,
+        CustomMappingRuleParser<T> parser
+    ) {
+        List<String> words = getWordList(env, settings, settingPath, settingList);
+        if (words == null) {
+            return null;
+        }
+        List<T> rules = new ArrayList<>();
+        int lineNum = 0;
+        for (String word : words) {
+            lineNum++;
+            if (word.startsWith("#") == false) {
+                try {
+                    rules.add(parser.apply(word));
+                } catch (RuntimeException ex) {
+                    String wordListPath = settings.get(settingPath, null);
+                    if (wordListPath == null || isUnderConfig(env, wordListPath)) {
+                        throw new RuntimeException("Line [" + lineNum + "]: " + ex.getMessage());
+                    } else {
+                        LOGGER.error("Line [{}]: {}", lineNum, ex);
+                        throw new RuntimeException("Line [" + lineNum + "]: " + "Invalid rule");
+                    }
+                }
+            }
+        }
+        return rules;
     }

     /**
@@ -233,53 +269,41 @@ public static List<String> getWordList(Environment env, Settings settings, Strin
      * @throws IllegalArgumentException
      *          If the word list cannot be found at either key.
      */
-    public static List<String> getWordList(
-        Environment env,
-        Settings settings,
-        String settingPath,
-        String settingList,
-        boolean removeComments
-    ) {
+    private static List<String> getWordList(Environment env, Settings settings, String settingPath, String settingList) {
         String wordListPath = settings.get(settingPath, null);

         if (wordListPath == null) {
-            List<String> explicitWordList = settings.getAsList(settingList, null);
-            if (explicitWordList == null) {
-                return null;
-            } else {
-                return explicitWordList;
-            }
+            return settings.getAsList(settingList, null);
         }

-        final Path path = env.configDir().resolve(wordListPath);
+        final Path path = resolveAnalyzerPath(env, wordListPath);

         try {
-            return loadWordList(path, removeComments);
+            return loadWordList(path);
         } catch (CharacterCodingException ex) {
             String message = String.format(
                 Locale.ROOT,
-                "Unsupported character encoding detected while reading %s: %s - files must be UTF-8 encoded",
-                settingPath,
-                path.toString()
+                "Unsupported character encoding detected while reading %s: files must be UTF-8 encoded",
+                settingPath
             );
-            throw new IllegalArgumentException(message, ex);
+            LOGGER.error("{}: from file: {}, exception is: {}", message, path.toString(), ex);
+            throw new IllegalArgumentException(message);
         } catch (IOException ioe) {
-            String message = String.format(Locale.ROOT, "IOException while reading %s: %s", settingPath, path.toString());
-            throw new IllegalArgumentException(message, ioe);
+            String message = String.format(Locale.ROOT, "IOException while reading %s: file not readable", settingPath);
+            LOGGER.error("{}, from file: {}, exception is: {}", message, path.toString(), ioe);
+            throw new IllegalArgumentException(message);
         }
     }

-    private static List<String> loadWordList(Path path, boolean removeComments) throws IOException {
+    private static List<String> loadWordList(Path path) throws IOException {
         final List<String> result = new ArrayList<>();
         try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
             String word;
             while ((word = br.readLine()) != null) {
                 if (Strings.hasText(word) == false) {
                     continue;
                 }
-                if (removeComments == false || word.startsWith("#") == false) {
-                    result.add(word.trim());
-                }
+                result.add(word.trim());
             }
         }
         return result;
@@ -296,21 +320,34 @@ public static Reader getReaderFromFile(Environment env, Settings settings, Strin
         if (filePath == null) {
             return null;
         }
-        final Path path = env.configDir().resolve(filePath);
+        final Path path = resolveAnalyzerPath(env, filePath);
         try {
             return Files.newBufferedReader(path, StandardCharsets.UTF_8);
         } catch (CharacterCodingException ex) {
             String message = String.format(
                 Locale.ROOT,
-                "Unsupported character encoding detected while reading %s_path: %s files must be UTF-8 encoded",
-                settingPrefix,
-                path.toString()
+                "Unsupported character encoding detected while reading %s_path: files must be UTF-8 encoded",
+                settingPrefix
             );
-            throw new IllegalArgumentException(message, ex);
+            LOGGER.error("{}: from file: {}, exception is: {}", message, path.toString(), ex);
+            throw new IllegalArgumentException(message);
         } catch (IOException ioe) {
-            String message = String.format(Locale.ROOT, "IOException while reading %s_path: %s", settingPrefix, path.toString());
-            throw new IllegalArgumentException(message, ioe);
+            String message = String.format(Locale.ROOT, "IOException while reading %s_path: file not readable", settingPrefix);
+            LOGGER.error("{}, from file: {}, exception is: {}", message, path.toString(), ioe);
+            throw new IllegalArgumentException(message);
         }
     }

+    public static Path resolveAnalyzerPath(Environment env, String wordListPath) {
+        return env.configDir().resolve(wordListPath).normalize();
+    }
+
+    private static boolean isUnderConfig(Environment env, String wordListPath) {
+        try {
+            final Path path = env.configDir().resolve(wordListPath).normalize();
+            return path.startsWith(env.configDir().toAbsolutePath());
+        } catch (Exception ex) {
+            return false;
+        }
+    }
 }


vuln-fix: Temporary Directory Hijacking or Information Disclosure

This fixes either Temporary Directory Hijacking, or Temporary Directory Local Information Disclosure.

Weakness: CWE-379: Creation of Temporary File in Directory with Insecure Permissions
Severity: High
CVSSS: 7.3
Detection: CodeQL & OpenRewrite (https://public.moderne.io/recipes/org.openrewrite.java.security.UseFilesCreateTempDirectory)

Reported-by: Jonathan Leitschuh <Jonathan.Leitschuh@gmail.com>
Signed-off-by: Jonathan Leitschuh <Jonathan.Leitschuh@gmail.com>

Bug-tracker: https://github.com/JLLeitschuh/security-research/issues/10

Co-authored-by: Moderne <team@moderne.io>

@@ -9,6 +9,7 @@
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
@@ -80,9 +81,7 @@ public static void main (String[] args) throws URISyntaxException, IOException,
      * Returns the absolute path to the temp directory.
      */
     public static File createTempDir(int port) throws IOException {
-        File tempDir = File.createTempFile("portofino.tomcat.", "." + port);
-        tempDir.delete();
-        tempDir.mkdir();
+        File tempDir = Files.createTempDirectory("portofino.tomcat." + "." + port).toFile();
         tempDir.deleteOnExit();
         return tempDir;
     }


Fix pending job counters.

Run pending jobs on connection shutdown to align the counters.

Fixes issue #2065

Signed-off-by: Achim Kraus <achim.kraus@cloudcoap.net>

@@ -582,24 +582,15 @@ public boolean remove(final Connection connection, final boolean removeFromSessi
 		try {
 			removed = connections.remove(connection.getConnectionId(), connection) == connection;
 			if (removed) {
-				if (connection.isExecuting()) {
-					List<Runnable> pendings = connection.getExecutor().shutdownNow();
-					if (LOGGER.isTraceEnabled()) {
-						LOGGER.trace("{}connection: remove {} (size {}, left jobs: {})", tag, connection,
-								connections.size(), pendings.size(), new Throwable("connection removed!"));
-					} else if (pendings.isEmpty()) {
-						LOGGER.debug("{}connection: remove {} (size {})", tag, connection, connections.size());
-					} else {
-						LOGGER.debug("{}connection: remove {} (size {}, left jobs: {})", tag, connection,
-								connections.size(), pendings.size());
-					}
+				int pendings = connection.shutdown();
+				if (LOGGER.isTraceEnabled()) {
+					LOGGER.trace("{}connection: remove {} (size {}, left jobs: {})", tag, connection,
+							connections.size(), pendings, new Throwable("connection removed!"));
+				} else if (pendings == 0) {
+					LOGGER.debug("{}connection: remove {} (size {})", tag, connection, connections.size());
 				} else {
-					if (LOGGER.isTraceEnabled()) {
-						LOGGER.trace("{}connection: remove {} (size {})", tag, connection, connections.size(),
-								new Throwable("connection removed!"));
-					} else {
-						LOGGER.debug("{}connection: remove {} (size {})", tag, connection, connections.size());
-					}
+					LOGGER.debug("{}connection: remove {} (size {}, left jobs: {})", tag, connection,
+							connections.size(), pendings);
 				}
 				connection.startByClientHello(null);
 				removeByAddressConnections(connection);


OIDC-118: Fix bad handling of request parameters

@@ -19,20 +19,30 @@
  */
 package org.xwiki.contrib.oidc.auth.internal;

+import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.net.URL;
 import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;

 import org.junit.jupiter.api.Test;
 import org.xwiki.configuration.ConfigurationSource;
+import org.xwiki.container.Container;
+import org.xwiki.container.servlet.ServletRequest;
+import org.xwiki.contrib.oidc.provider.internal.OIDCManager;
+import org.xwiki.contrib.oidc.provider.internal.endpoint.TokenOIDCEndpoint;
+import org.xwiki.properties.ConverterManager;
 import org.xwiki.test.junit5.mockito.ComponentTest;
 import org.xwiki.test.junit5.mockito.InjectMockComponents;
 import org.xwiki.test.junit5.mockito.MockComponent;

+import com.xpn.xwiki.web.XWikiServletRequestStub;
+
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.when;
@@ -51,6 +61,15 @@ class OIDCClientConfigurationTest
     @MockComponent
     private ConfigurationSource sourceConfiguration;

+    @MockComponent
+    private Container container;
+
+    @MockComponent
+    private OIDCManager manager;
+
+    @MockComponent
+    private ConverterManager converterManager;
+
     @Test
     void getUserInfoOIDCEndpoint() throws URISyntaxException
     {
@@ -78,6 +97,39 @@ void getUserInfoOIDCEndpoint() throws URISyntaxException

         assertEquals(uri, endpoint.getURI());
         assertEquals(headers, endpoint.getHeaders());
+    }
+
+    @Test
+    void getPropertyOrder() throws MalformedURLException, URISyntaxException
+    {
+        String provider = "http://urlprovider";
+        URI urlauthorization = new URI("http://urlauthorization");
+
+        XWikiServletRequestStub requestStub = new XWikiServletRequestStub(new URL("http://url"), null);
+
+        when(this.container.getRequest()).thenReturn(new ServletRequest(requestStub));
+        when(this.sourceConfiguration.getProperty(OIDCClientConfiguration.PROP_SKIPPED, false)).thenReturn(false);
+
+        assertFalse(this.configuration.isSkipped());
+        assertNull(this.configuration.getXWikiProvider());
+        assertNull(this.configuration.getAuthorizationOIDCEndpoint());
+        assertNull(this.configuration.getAuthorizationOIDCEndpoint());
+        assertNull(this.configuration.getTokenOIDCEndpoint());
+
+        requestStub.put(OIDCClientConfiguration.PROP_SKIPPED, "true");
+        when(this.converterManager.convert(Boolean.class, "true")).thenReturn(true);
+
+        assertTrue(this.configuration.isSkipped());
+
+        requestStub.put(OIDCClientConfiguration.PROP_GROUPS_ALLOWED, "true");
+
+        assertNull(this.configuration.getAllowedGroups());
+
+        requestStub.put(OIDCClientConfiguration.PROP_XWIKIPROVIDER, provider.toString());
+        requestStub.put(OIDCClientConfiguration.PROP_ENDPOINT_AUTHORIZATION, urlauthorization.toString());
+        when(this.manager.createEndPointURI(provider, TokenOIDCEndpoint.HINT)).thenReturn(new URI(provider));

+        assertEquals(urlauthorization, this.configuration.getAuthorizationOIDCEndpoint().getURI());
+        assertEquals(provider, this.configuration.getTokenOIDCEndpoint().getURI().toString());
     }
 }


fix(测试跟踪): 缺陷平台请求转发添加白名单

@@ -15,9 +15,10 @@ public class IssueProxyResourceController {
     @Resource
     IssueProxyResourceService issueProxyResourceService;

-    @GetMapping(value = "/md/get/url")
-    public ResponseEntity<byte[]> getFileByUrl(@RequestParam ("url") String url, @RequestParam (value = "platform", required = false) String platform,
-                                               @RequestParam (value = "workspace_id", required = false) String workspaceId) {
-        return issueProxyResourceService.getMdImageByUrl(url, platform, workspaceId);
+    @GetMapping(value = "/md/get/path")
+    public ResponseEntity<byte[]> getFileByPath(@RequestParam ("path") String path,
+                                                @RequestParam (value = "platform") String platform,
+                                                @RequestParam (value = "workspaceId") String workspaceId) {
+        return issueProxyResourceService.getMdImageByPath(path, platform, workspaceId);
     }
 }


fix: CVE-2022-41967

Dragonfly v0.3.0-SNAPSHOT fails to properly configure the
DocumentBuilderFactory to prevent XML enternal entity (XXE) attacks when
parsing maven-metadata.xml files provided by external Maven repositories
during "SNAPSHOT" version resolution.

This patches CVE-2022-41967 by disabling features which may lead to XXE.
If you are currently using v0.3.0-SNAPSHOT it is STRONGLY advised to
update Dragonfly to v0.3.1-SNAPSHOT just to be safe.

@@ -41,8 +41,8 @@
 @Downloader(MavenDependency.class)
 public class MavenDownloader implements IDownloader<MavenDependency> {

-	private final MavenResolver resolver = new MavenResolver();
-	private final MavenSnapshotResolver snapshotResolver = new MavenSnapshotResolver();
+	private final @NotNull MavenResolver resolver = new MavenResolver();
+	private final @NotNull MavenSnapshotResolver snapshotResolver = new MavenSnapshotResolver();

 	@Override
 	public void download(@NotNull Dragonfly dragonfly, @NotNull MavenDependency dependency) throws DownloadFailureException {


@@ -40,7 +40,7 @@
  */
 public class MavenResolver implements IResolver<MavenDependency> {

-	private static final String FORMAT = "%s%s/%s/%s/%s-%s.jar";
+	private static final @NotNull String FORMAT = "%s%s/%s/%s/%s-%s.jar";

 	@Override
 	public @Nullable String resolve(@NotNull Dragonfly dragonfly, @NotNull MavenDependency dependency) throws ResolveFailureException {


COMMON-3969: Explicit alphanumeric n-gram indexes

Explicitly build alphanumeric n-gram indexes to avoid XSS due
to unescaped characters in the rendered n-gram indexes JSON

@@ -4,7 +4,11 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
-import com.google.common.collect.*;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.TreeMultimap;
 import com.indeed.util.varexport.VarExporter;
 import com.indeed.util.varexport.Variable;
 import com.indeed.util.varexport.VariableHost;
@@ -14,6 +18,7 @@
 import freemarker.template.Template;
 import org.apache.log4j.Logger;

+import javax.annotation.Nullable;
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
@@ -25,7 +30,18 @@
 import java.io.PrintWriter;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;

 /**
  * Servlet for displaying variables exported by {@link VarExporter}.
@@ -287,8 +303,7 @@ public void visit(Variable var) {
         }
         root.put("vars", varList);
         if (withIndex) {
-            final String varsIndex = buildIndex(varList);
-            root.put("varsIndex", varsIndex);
+            root.put("varsIndex", alphanumericNGramIndexesJSON(varList));
         }

         try {
@@ -298,19 +313,24 @@ public void visit(Variable var) {
         }
     }

-    private String buildIndex(final List<Variable> varList) {
-        final SetMultimap<String, Integer> uniGram = buildNGramIndex(varList, 1);
-        final SetMultimap<String, Integer> biGram = buildNGramIndex(varList, 2);
-        final SetMultimap<String, Integer> triGram = buildNGramIndex(varList, 3);
+    private String alphanumericNGramIndexesJSON(final List<Variable> varList) {
+        // The JSON we build here is valid as we only use alphanumeric n-grams
+
         final StringBuilder json = new StringBuilder();
+
         json.append('{').append('\n');
-        json.append("\"uniGram\":").append('\n');
-        appendTo(json, uniGram).append(',').append('\n');
+
+        json.append("\n\"uniGram\":").append('\n');
+        appendTo(json, buildAlphanumericNGramIndex(varList, 1)).append(',').append('\n');
+
         json.append("\"biGram\":").append('\n');
-        appendTo(json, biGram).append(',').append('\n');;
+        appendTo(json, buildAlphanumericNGramIndex(varList, 2)).append(',').append('\n');;
+
         json.append("\"triGram\":").append('\n');
-        appendTo(json, triGram);
+        appendTo(json, buildAlphanumericNGramIndex(varList, 3));
+
         json.append('}');
+
         return json.toString();
     }

@@ -333,19 +353,32 @@ private <K> StringBuilder appendTo(final StringBuilder json, final SetMultimap<K
         return json;
     }

-    private SetMultimap<String, Integer> buildNGramIndex(final List<Variable> varList, final int n) {
-        final TreeMultimap<String, Integer> uniGram = TreeMultimap.create();
-        for (int index = 0; index < varList.size(); index++) {
-            final Variable var = varList.get(index);
-            final String[] indexableNames = var.getIndexableNames();
-            for (final String indexableName : indexableNames) {
-                for (int i = 0; i < indexableName.length() - n + 1; i++) {
-                    final String key = indexableName.substring(i, i + n);
-                    uniGram.put(key, index);
-                }
-            }
+    @VisibleForTesting
+    static SetMultimap<String, Integer> buildAlphanumericNGramIndex(final List<Variable> varList, final int n) {
+        final TreeMultimap<String, Integer> nGramIndex = TreeMultimap.create();
+        IntStream
+                .range(0, varList.size())
+                .forEach(i ->
+                        alphanumericNGrams(varList.get(i).getName(), n)
+                                .forEach(ngram -> nGramIndex.put(ngram, i))
+                );
+        return nGramIndex;
+    }
+
+    private static final Pattern NON_ALPHANUMERIC = Pattern.compile("[^a-z0-9]+");
+
+    @VisibleForTesting
+    static Stream<String> alphanumericNGrams(@Nullable final String in, final int n) {
+        if (in == null) {
+            return Stream.empty();
         }
-        return uniGram;
+        return NON_ALPHANUMERIC
+                .splitAsStream(in.toLowerCase(Locale.US))
+                .flatMap(alnumSubString ->
+                        IntStream
+                                .rangeClosed(0, alnumSubString.length() - n)
+                                .mapToObj(i -> alnumSubString.substring(i, i + n))
+                );
     }

     private void addVariable(final Variable v, final List<Variable> out) {


Added CSRF protection to the togglz console via a CSRF token passed between the server and the clinet. This remediates the vulnerabilty CVE-2020-28191 by blocking CSRF attacks as the attcker will not be able to guess the CSRF token value. (#495)

This has been implemented with either the session timeout of the application the togglz console is embedded in. Or if no user session is available it defaults to a 10 minute timeout for the CSRF token.
This CSRF token does not interfere with either OWASP's CSRFGuard or Spring-Security's CSRF protection if they are used within the application.

Co-authored-by: Joseph Beeton <joseph.p.beeton1@aexp.com>

@@ -0,0 +1,9 @@
+package org.togglz.servlet.spi;
+
+import javax.servlet.http.HttpServletRequest;
+
+public interface CSRFTokenValidator {
+
+
+	boolean isTokenValid(HttpServletRequest request);
+}


Merge pull request #12 from 3dcitydb/hotfix-prevent-xxe-vulnerabilities

Prevent XXE vulnerabilities

@@ -4,6 +4,7 @@
 import net.opengis.fes._2.FilterType;
 import net.opengis.fes._2.ResourceIdType;
 import net.opengis.wfs._2.*;
+import org.citydb.util.xml.SecureXMLProcessors;
 import org.citygml4j.builder.jaxb.CityGMLBuilder;
 import org.citygml4j.model.module.citygml.CityGMLModule;
 import org.citygml4j.model.module.citygml.CityGMLModuleType;
@@ -27,6 +28,7 @@
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 import javax.xml.stream.XMLOutputFactory;
+import javax.xml.transform.TransformerConfigurationException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathExpression;
@@ -35,6 +37,7 @@
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.IOException;
+import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -58,16 +61,16 @@ public class StoredQueryManager {
 	private final WFSConfig wfsConfig;
 	private final MessageDigest md5;

-	public StoredQueryManager(CityGMLBuilder cityGMLBuilder, SAXParserFactory saxParserFactory, String path, WFSConfig wfsConfig) throws ParserConfigurationException, SAXException, NoSuchAlgorithmException, IOException {
+	public StoredQueryManager(CityGMLBuilder cityGMLBuilder, SAXParserFactory saxParserFactory, String path, WFSConfig wfsConfig) throws ParserConfigurationException, SAXException, NoSuchAlgorithmException, IOException, TransformerConfigurationException {
 		this.cityGMLBuilder = cityGMLBuilder;
 		this.saxParserFactory = saxParserFactory;
 		this.wfsConfig = wfsConfig;

 		md5 = MessageDigest.getInstance("MD5");
 		wfsFactory = new ObjectFactory();
-		transformerFactory = TransformerFactory.newInstance();
+		transformerFactory = SecureXMLProcessors.newTransformerFactory();
 		xmlOutputFactory = XMLOutputFactory.newInstance();
-		documentBuilderFactory = DocumentBuilderFactory.newInstance();
+		documentBuilderFactory = SecureXMLProcessors.newDocumentBuilderFactory();
 		documentBuilderFactory.setNamespaceAware(true);

 		storedQueriesPath = Paths.get(path);
@@ -83,9 +86,11 @@ public List<StoredQueryAdapter> listStoredQueries(String handle) throws WFSExcep
 		storedQueries.add(new StoredQueryAdapter(DEFAULT_QUERY.getId()));

 		try {
-			for (Path file : Files.newDirectoryStream(storedQueriesPath)) {
-				if (Files.isRegularFile(file))
-					storedQueries.add(new StoredQueryAdapter(file));
+			try (DirectoryStream<Path> stream = Files.newDirectoryStream(storedQueriesPath)) {
+				for (Path file : stream) {
+					if (Files.isRegularFile(file))
+						storedQueries.add(new StoredQueryAdapter(file));
+				}
 			}
 		} catch (IOException e) {
 			throw new WFSException(WFSExceptionCode.OPERATION_PROCESSING_FAILED, "Failed to list stored queries.", handle, e);


@@ -194,32 +194,24 @@ public Object query(String query) throws ApplicationException {
             return this;
         }

-        StringBuffer condition = new StringBuffer();
+        StringBuilder condition = new StringBuilder();
         int i = 0, j, k = 0;
-        String _keyword;
+        String[] _keywords = new String[keywords.length];
         while (i < keywords.length) {
-            _keyword = keywords[i];
-            if (_keyword.trim().length() > 0) {
-                if (condition.length() == 0) {
-                    condition.append(" bible.content like '%" + _keyword + "%' ");
-                } else {
-                    condition.append(" AND bible.content like '%" + _keyword + "%' ");
-                    /*
-                     * if(true)
-                     * condition.append(" AND bible.content like '%"+keywords[i]+"%' ");
-                     * else
-                     * condition.append(" OR bible.content like '%"+keywords[i]+"%' ");
-                     */
-                }
+            _keywords[i] = "%" + keywords[i] + "%";
+            if (condition.length() == 0) {
+                condition.append(" bible.content like ? ");
+            } else {
+                condition.append(" AND bible.content like ? ");
             }
             i++;
         }

         Locale locale = this.getLocale();
         if (condition.length() == 0)
-            condition.append(" book.language='" + locale + "' ");
+            condition.append(" book.language='").append(locale).append("' ");
         else
-            condition.append(" AND book.language='" + locale + "' ");
+            condition.append(" AND book.language='").append(locale).append("' ");

         book book = new book();
         bible bible = new bible();
@@ -240,7 +232,7 @@ public Object query(String query) throws ApplicationException {
                 + " as bible left join " + book.getTableName()
                 + " as book on bible.book_id=book.book_id where " + condition;

-        Table vtable = bible.find(SQL, new Object[]{});
+        Table vtable = bible.find(SQL, _keywords);
         boolean noResult = vtable.size() > 0;

         if (!noResult && query.length() > 0) {
@@ -266,7 +258,7 @@ public Object query(String query) throws ApplicationException {
             }
         }

-        Row found = bible.findOne(look, new Object[]{});
+        Row found = bible.findOne(look, _keywords);

         long startTime = System.currentTimeMillis();
         Pager pager = new Pager();
@@ -277,7 +269,7 @@ public Object query(String query) throws ApplicationException {
         Field field;
         int next = pager.getStartIndex();// 此位置即为当前页的第一条记录的ID

-        html.append("
\r\n");
+        html.append("
\r\n");

         String finded, word;
         Row row;
@@ -349,7 +341,7 @@ public Object query(String query) throws ApplicationException {
         html.append("
"
                 + pager.getPageControlBar(actionURL) + "
\r\n");
         html.append("");
+                + (System.currentTimeMillis() - startTime) + " -->");

         int start = page - 1 == 0 ? 1 : (page - 1) * pageSize + 1, end = page
                 * pageSize;
@@ -381,7 +373,7 @@ public Object query(String query) throws ApplicationException {
     }

     public String feed(String query) throws ApplicationException {
-        StringBuffer xml = new StringBuffer();
+        StringBuilder xml = new StringBuilder();
         String finded = "";
         String[] keywords;
         boolean noResult = true;


@@ -27,7 +27,8 @@
 import org.springframework.expression.Expression;
 import org.springframework.expression.ParserContext;
 import org.springframework.expression.spel.standard.SpelExpressionParser;
-import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.expression.spel.support.DataBindingPropertyAccessor;
+import org.springframework.expression.spel.support.SimpleEvaluationContext;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpMethod;
@@ -119,56 +120,57 @@ protected String getText(InstanceEvent event, Instance instance) {
 		root.put("event", event);
 		root.put("instance", instance);
 		root.put("lastStatus", getLastStatus(event.getInstance()));
-		StandardEvaluationContext context = new StandardEvaluationContext(root);
-		context.addPropertyAccessor(new MapAccessor());
+		SimpleEvaluationContext context = SimpleEvaluationContext
+				.forPropertyAccessors(DataBindingPropertyAccessor.forReadOnlyAccess(), new MapAccessor())
+				.withRootObject(root).build();
 		return message.getValue(context, String.class);
 	}

 	public void setRestTemplate(RestTemplate restTemplate) {
 		this.restTemplate = restTemplate;
 	}

-	public void setUrl(@Nullable URI url) {
-		this.url = url;
-	}
-
 	@Nullable
 	public URI getUrl() {
 		return url;
 	}

-	public void setUsername(String username) {
-		this.username = username;
+	public void setUrl(@Nullable URI url) {
+		this.url = url;
 	}

 	public String getUsername() {
 		return username;
 	}

-	public void setRoom(@Nullable String room) {
-		this.room = room;
+	public void setUsername(String username) {
+		this.username = username;
 	}

 	@Nullable
 	public String getRoom() {
 		return room;
 	}

-	public void setToken(@Nullable String token) {
-		this.token = token;
+	public void setRoom(@Nullable String room) {
+		this.room = room;
 	}

 	@Nullable
 	public String getToken() {
 		return token;
 	}

-	public void setMessage(String message) {
-		this.message = parser.parseExpression(message, ParserContext.TEMPLATE_EXPRESSION);
+	public void setToken(@Nullable String token) {
+		this.token = token;
 	}

 	public String getMessage() {
 		return message.getExpressionString();
 	}

+	public void setMessage(String message) {
+		this.message = parser.parseExpression(message, ParserContext.TEMPLATE_EXPRESSION);
+	}
+
 }


Merge pull request #66 from netroms/final_sec_merge_2.36.12.1

fix: merge latest security advisories 2.36.12.1

@@ -69,6 +69,18 @@ void copyFileResourceContent( FileResource fileResource, OutputStream outputStre
         throws IOException,
         NoSuchElementException;

+    /**
+     * Copy fileResource content to a byte array
+     *
+     * @param fileResource
+     * @return a byte array of the content
+     * @throws IOException
+     * @throws NoSuchElementException
+     */
+    byte[] copyFileResourceContent( FileResource fileResource )
+        throws IOException,
+        NoSuchElementException;
+
     boolean fileResourceExists( String uid );

     void updateFileResource( FileResource fileResource );


@@ -38,6 +38,8 @@
 import org.hisp.dhis.dxf2.webmessage.WebMessageException;
 import org.hisp.dhis.dxf2.webmessage.WebMessageUtils;
 import org.hisp.dhis.dxf2.webmessage.responses.FileResourceWebMessageResponse;
+import org.hisp.dhis.external.conf.ConfigurationKey;
+import org.hisp.dhis.external.conf.DhisConfigurationProvider;
 import org.hisp.dhis.feedback.Status;
 import org.hisp.dhis.fileresource.FileResource;
 import org.hisp.dhis.fileresource.FileResourceDomain;
@@ -48,6 +50,7 @@
 import org.hisp.dhis.user.User;
 import org.hisp.dhis.webapi.mvc.annotation.ApiVersion;
 import org.hisp.dhis.webapi.utils.FileResourceUtils;
+import org.hisp.dhis.webapi.utils.HeaderUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
@@ -79,6 +82,9 @@ public class FileResourceController
     @Autowired
     private FileResourceUtils fileResourceUtils;

+    @Autowired
+    private DhisConfigurationProvider dhisConfig;
+
     // -------------------------------------------------------------------------
     // Controller methods
     // -------------------------------------------------------------------------
@@ -127,6 +133,7 @@ public void getFileResourceData( @PathVariable String uid, HttpServletResponse r
         response.setHeader( HttpHeaders.CONTENT_LENGTH,
             String.valueOf( fileResourceService.getFileResourceContentLength( fileResource ) ) );
         response.setHeader( HttpHeaders.CONTENT_DISPOSITION, "filename=" + fileResource.getName() );
+        HeaderUtils.setSecurityHeaders( response, dhisConfig.getProperty( ConfigurationKey.CSP_HEADER_VALUE ) );

         try
         {


@@ -38,11 +38,13 @@
 import org.hisp.dhis.security.MappedRedirectStrategy;
 import org.hisp.dhis.security.ldap.authentication.CustomLdapAuthenticationProvider;
 import org.hisp.dhis.security.oidc.DhisOidcLogoutSuccessHandler;
+import org.hisp.dhis.security.oidc.DhisOidcProviderRepository;
 import org.hisp.dhis.security.spring2fa.TwoFactorAuthenticationProvider;
 import org.hisp.dhis.security.spring2fa.TwoFactorWebAuthenticationDetailsSource;
 import org.hisp.dhis.security.vote.ActionAccessVoter;
 import org.hisp.dhis.security.vote.ModuleAccessVoter;
 import org.hisp.dhis.webapi.filter.CorsFilter;
+import org.hisp.dhis.webapi.filter.CspFilter;
 import org.hisp.dhis.webapi.filter.CustomAuthenticationFilter;
 import org.hisp.dhis.webapi.handler.CustomExceptionMappingAuthenticationFailureHandler;
 import org.hisp.dhis.webapi.handler.DefaultAuthenticationSuccessHandler;
@@ -72,7 +74,9 @@
 import org.springframework.security.web.access.expression.WebExpressionVoter;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.security.web.header.HeaderWriterFilter;
 import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.session.web.http.DefaultCookieSerializer;

 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -139,7 +143,7 @@ public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityCon
         private I18nManager i18nManager;

         @Autowired
-        private DhisConfigurationProvider configurationProvider;
+        private DhisConfigurationProvider dhisConfig;

         @Autowired
         private ExternalAccessVoter externalAccessVoter;
@@ -157,6 +161,9 @@ public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityCon
         @Autowired
         private DefaultAuthenticationEventPublisher authenticationEventPublisher;

+        @Autowired
+        private DhisOidcProviderRepository dhisOidcProviderRepository;
+
         public void configure( AuthenticationManagerBuilder auth )
             throws Exception
         {
@@ -251,10 +258,13 @@ protected void configure( HttpSecurity http )
                 .csrf()
                 .disable()

+                .addFilterBefore( new CspFilter( dhisConfig, dhisOidcProviderRepository ),
+                    HeaderWriterFilter.class )
+
                 .addFilterBefore( CorsFilter.get(), BasicAuthenticationFilter.class )
                 .addFilterBefore( CustomAuthenticationFilter.get(), UsernamePasswordAuthenticationFilter.class );

-            setHttpHeaders( http );
+            setHttpHeaders( http, dhisConfig );
         }

         @Bean
@@ -270,10 +280,10 @@ public DefaultAuthenticationSuccessHandler authenticationSuccessHandler()
         {
             DefaultAuthenticationSuccessHandler successHandler = new DefaultAuthenticationSuccessHandler();
             successHandler.setRedirectStrategy( mappedRedirectStrategy() );
-            if ( configurationProvider.getProperty( ConfigurationKey.SYSTEM_SESSION_TIMEOUT ) != null )
+            if ( dhisConfig.getProperty( ConfigurationKey.SYSTEM_SESSION_TIMEOUT ) != null )
             {
                 successHandler.setSessionTimeout(
-                    Integer.parseInt( configurationProvider.getProperty( ConfigurationKey.SYSTEM_SESSION_TIMEOUT ) ) );
+                    Integer.parseInt( dhisConfig.getProperty( ConfigurationKey.SYSTEM_SESSION_TIMEOUT ) ) );
             }

             return successHandler;


@@ -19,8 +19,9 @@

 package org.apache.iotdb.admin.controller;

-import com.auth0.jwt.JWT;
-import com.auth0.jwt.algorithms.Algorithm;
+import org.apache.iotdb.admin.model.entity.User;
+import org.apache.iotdb.admin.tool.JJwtTool;
+
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -31,9 +32,6 @@
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 import org.springframework.web.context.WebApplicationContext;

-import java.net.InetAddress;
-import java.util.Calendar;
-
 @SpringBootTest
 class UserControllerTest {

@@ -65,16 +63,11 @@ void getUser() throws Exception {
   }

   private String getToken() {
-    Calendar instance = Calendar.getInstance();
     try {
-      instance.add(Calendar.HOUR, 24);
-      String token =
-          JWT.create()
-              .withClaim("userId", 1)
-              .withClaim("name", "root")
-              .withExpiresAt(instance.getTime())
-              .sign(Algorithm.HMAC256("IOTDB:" + InetAddress.getLocalHost().getHostAddress()));
-      return token;
+      User user = new User();
+      user.setId(1);
+      user.setName("root");
+      return JJwtTool.generateToken(user);
     } catch (Exception e) {
       e.printStackTrace();
       return null;


Merge pull request from GHSA-xr8x-pxm6-prjg

* Refactor + Failing test

* Rename tests, add fix

* Test WebSourceProvider

* Remove ZipFetcher

* Add failing test

* Filter illegal path

Co-authored-by: dotasek <david.otasek@smilecdr.com>

@@ -0,0 +1,76 @@
+package org.hl7.fhir.igtools.publisher;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+
+public class IGPack2NpmConvertorTest {
+    public static final String ZIP_NORMAL_ZIP = "zip-normal.zip";
+    public static final String ZIP_SLIP_ZIP = "zip-slip.zip";
+
+    public static final String ZIP_SLIP_2_ZIP = "zip-slip-2.zip";
+
+    public static final String ZIP_SLIP_WIN_ZIP = "zip-slip-win.zip";
+    Path tempDir;
+
+    @Test
+    public void testNormalZip() throws IOException {
+        InputStream normalInputStream = getResourceAsInputStream(this.getClass(),  "zip", ZIP_NORMAL_ZIP);
+
+        IGPack2NpmConvertor convertor = new IGPack2NpmConvertor();
+        Map<String, byte[]> map = convertor.loadZip( normalInputStream);
+
+
+        String actualContent = new String(map.get("zip-normal/depth1/test.txt"), StandardCharsets.UTF_8);
+        assertEquals("dummy file content", actualContent);
+    }
+
+    static final String PATH_DELIMITER = "/";
+
+    public static InputStream getResourceAsInputStream(Class<?> clazz, String... resourcePath) {
+        return clazz.getClassLoader().getResourceAsStream(String.join(PATH_DELIMITER, resourcePath));
+    }
+
+    private static Stream<Arguments> getParams() {
+        return Stream.of(
+                Arguments.of(
+                        getResourceAsInputStream(PublisherTests.class,  "zip", ZIP_SLIP_ZIP),
+                        "Entry with an illegal name: ../evil.txt"),
+                Arguments.of(
+                        getResourceAsInputStream(PublisherTests.class,  "zip", ZIP_SLIP_2_ZIP),
+                        "Entry with an illegal name: child/../../evil.txt"),
+                Arguments.of(
+                        getResourceAsInputStream(PublisherTests.class,  "zip", ZIP_SLIP_WIN_ZIP),
+                        "Entry with an illegal name: ../evil.txt")
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("getParams")
+    public void test(InputStream inputStream, String expectedError){
+        IOException thrown = Assertions.assertThrows(IOException.class, () -> {
+
+            IGPack2NpmConvertor convertor = new IGPack2NpmConvertor();
+            convertor.loadZip( inputStream );
+            //Code under test
+        });
+        assertNotNull(thrown);
+        assertEquals(expectedError, thrown.getMessage());
+    }
+}


@@ -171,9 +171,15 @@ public FormValidation doCheckDependencyJobsNames(@AncestorInPath Item project, @
     /**
      * Fill the server dropdown with the list of servers configured globally.
      *
+     * @param project the current project.
      * @return list of servers.
      */
-    public ListBoxModel doFillServerNameItems() {
+    public ListBoxModel doFillServerNameItems(@AncestorInPath Item project) {
+        if (project == null) {
+            Jenkins.get().checkPermission(Jenkins.ADMINISTER);
+        } else {
+            project.checkPermission(Item.CONFIGURE);
+        }
         ListBoxModel items = new ListBoxModel();
         items.add(Messages.AnyServer(), ANY_SERVER);
         List<String> serverNames = PluginImpl.getServerNames_();


Escape username and domain values to avoid SQL injection in string
concatenation (b/13399766, 2 of 2).

Code review: https://codereview.appspot.com/75770044

@@ -0,0 +1,81 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.enterprise.connector.dctm;
+
+import junit.framework.TestCase;
+
+public class DqlUtilsTest extends TestCase {
+  public void testEscapeString_null() {
+    try {
+      DqlUtils.escapeString(null);
+      fail("Expected a NullPointerException");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  public void testEscapeString_empty() {
+    assertEquals("", DqlUtils.escapeString(""));
+  }
+
+  public void testEscapeString_unescapedString() {
+    String value = "hello world";
+    assertEquals(value, DqlUtils.escapeString(value));
+  }
+
+  public void testEscapeString_unescapedPattern() {
+    String value = "\\ % _ \\\\ \\% \\_";
+    assertEquals(value, DqlUtils.escapeString(value));
+  }
+
+  public void testEscapeString_quoted() {
+    String value = "' \\ % _ \\' \\\\ \\% \\_";
+    assertEquals(value.replace("'", "''"), DqlUtils.escapeString(value));
+  }
+
+  public void testEscapePattern_null() {
+    try {
+      DqlUtils.escapePattern(null, '\\');
+      fail("Expected a NullPointerException");
+    } catch (NullPointerException expected) {
+    }
+  }
+
+  public void testEscapePattern_empty() {
+    assertEquals("", DqlUtils.escapePattern("", '\\'));
+  }
+
+  public void testEscapePattern_unescapedString() {
+    String value = "hello world";
+    assertEquals(value, DqlUtils.escapePattern(value, '\\'));
+  }
+
+  public void testEscapePattern_escapedPattern() {
+    String value = "\\ % _ \\\\ \\% \\_";
+    String escaped = "\\\\ \\% \\_ \\\\\\\\ \\\\\\% \\\\\\_";
+    assertEquals(escaped, DqlUtils.escapePattern(value, '\\'));
+  }
+
+  public void testEscapePattern_quoted() {
+    String value = "' \\ % _ \\' \\\\ \\% \\_";
+    String escaped = "'' \\\\ \\% \\_ \\\\'' \\\\\\\\ \\\\\\% \\\\\\_";
+    assertEquals(escaped, DqlUtils.escapePattern(value, '\\'));
+  }
+
+  public void testEscapePattern_quotedSlash() {
+    String value = "' \\ % _ \\' \\\\ \\% \\_";
+    String escaped = "'' /\\ /% /_ /\\'' /\\/\\ /\\/% /\\/_";
+    assertEquals(escaped, DqlUtils.escapePattern(value, '/'));
+  }
+}


Fix #18 SQLInjection vulnerability cleared

Pattern and Option DAOs re-written to a common key-value base class.  Using composite primary key in place of surrogate key.

@@ -0,0 +1,26 @@
+/*
+ *
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ *  * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *  *
+ *  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ *  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ *  * specific language governing permissions and limitations under the License.
+ *
+ */
+
+package uk.q3c.krail.jpa.i18n;
+
+import uk.q3c.krail.core.data.KrailEntity;
+
+/**
+ * Created by David Sowerby on 18 Jan 2016
+ */
+public interface KeyValueEntity<ID, VER> extends KrailEntity<ID, VER> {
+
+    String getValue();
+
+    void setValue(String value);
+}


@@ -0,0 +1,89 @@
+/*
+ *
+ *  * Copyright (c) 2016. David Sowerby
+ *  *
+ *  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ *  * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *  *
+ *  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ *  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ *  * specific language governing permissions and limitations under the License.
+ *
+ */
+
+package uk.q3c.krail.jpa.user.opt;
+
+import uk.q3c.krail.core.user.opt.Option;
+import uk.q3c.krail.core.user.opt.cache.OptionCacheKey;
+import uk.q3c.krail.jpa.i18n.KeyValueEntity;
+
+import javax.annotation.Nonnull;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Version;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * An entity to hold data for an {@link Option}.  The value is held as a String to enable the use of a single column for it.  Context is part of the overall
+ * {#optionKey} to ensure uniqueness, but is also held separately as it is a useful way to group the options for manual setting up, and there may also be an
+ * opportunity to pre-load options by Context.
+ * <p>
+ * Created by David Sowerby on 13/04/15.
+ */
+@Entity
+public class JpaOptionEntity implements KeyValueEntity<OptionId, Integer> {
+
+    @EmbeddedId
+    private OptionId id;
+    private String value;
+    @Version
+    private Integer version;
+
+    protected JpaOptionEntity() {
+    }
+
+    public JpaOptionEntity(@Nonnull OptionCacheKey optionCacheKey, @Nonnull String value) {
+        this.id = new OptionId(optionCacheKey);
+        this.value = value;
+
+    }
+
+    @Override
+    public OptionId getId() {
+        return id;
+    }
+
+    @Override
+    public Integer getVersion() {
+        return version;
+    }
+
+    public String getContext() {
+        return id.getContext();
+    }
+
+    public String getUserHierarchyName() {
+        return id.getUserHierarchyName();
+    }
+
+    public String getRankName() {
+        return id.getRankName();
+    }
+
+    public String getOptionKey() {
+        return id.getOptionKey();
+    }
+
+    @Nonnull
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(@Nonnull String value) {
+        checkNotNull(value);
+        this.value = value;
+    }
+
+
+}


@@ -1,6 +1,7 @@
 package com.datformers.database;

 import java.sql.*;
+import java.util.ArrayList;
 import java.util.Enumeration;

@@ -46,6 +47,26 @@ public ResultSet executeQuery(String query) {

 			return rs;
 	}
+	public ResultSet executeValidateQuery(String query,ArrayList<String> params) {
+		PreparedStatement  preparedStatement;
+
+		 ResultSet rs = null;
+
+		try {
+			preparedStatement=conn.prepareStatement(query) ;
+			for(int i=0;i<params.size();i++) {
+				preparedStatement.setString(i+1,params.get(i));
+			}
+
+			rs = preparedStatement.executeQuery();
+
+		} catch (Exception e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+
+			return rs;
+	}
 	public void closeConnection() {
 		try {
 			conn.close();


4.4 export security fix (#3370)

* [vFQk1rX0] Fix security issue

* [vFQk1rX0] Add more test cases from PR review

@@ -227,7 +227,7 @@ private static Path getPath(String url) {
         Path urlPath;
         URL toURL = null;
         try {
-            final URI uri = URI.create(url.trim());
+            final URI uri = URI.create(url.trim()).normalize();
             toURL = uri.toURL();
             urlPath = Paths.get(uri);
         } catch (Exception e) {
@@ -242,10 +242,10 @@ private static Path getPath(String url) {

     private static boolean pathStartsWithOther(Path resolvedPath, Path basePath) throws IOException {
         try {
-            return resolvedPath.toRealPath().startsWith(basePath.toRealPath());
+            return resolvedPath.toFile().getCanonicalFile().toPath().startsWith(basePath.toRealPath());
         } catch (Exception e) {
-            if (e instanceof NoSuchFileException) { // If we're about to creating a file this exception has been thrown
-                return resolvedPath.normalize().startsWith(basePath);
+            if (e instanceof NoSuchFileException) { // If we're about to create a file this exception has been thrown
+                return resolvedPath.toFile().getCanonicalFile().toPath().startsWith(basePath);
             }
             return false;
         }


Added validation for contact adding and changed use of prepared statements to avoid SQL injection.

@@ -43,7 +43,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
             } catch (SQLException e) {
                 RequestDispatcher dispatcher = request.getRequestDispatcher("Error"); //New Request Dispatcher
                 request.setAttribute("error", e.getMessage());
-                request.setAttribute("previous", "searchcontact");
+                request.setAttribute("previous", "contact.jsp");
                 dispatcher.forward(request, response);
             }
         }


Protect against CSWSH: (Cross-Site WebSocket Hijacking)

@@ -167,7 +167,9 @@ public io.netty.handler.codec.http.Cookie toNettyCookie() {
         nettyCookie.setDiscard(discardAtEndOfBrowserSession);
         nettyCookie.setHttpOnly(true);  // TODO
         if (GribbitProperties.SSL) {
-            nettyCookie.setSecure(true);  // TODO
+            // If SSL is enabled, force cookies to only be delivered over SSL, to prevent cookie hijacking
+            // on public wifi networks
+            nettyCookie.setSecure(true);
         }
         return nettyCookie;
     }


TMDM-15182 Improper Restriction of XML External Entity Reference (#1420)

* TMDM-15182 Improper Restriction of XML External Entity Reference

@@ -25,7 +25,7 @@
 import org.w3c.dom.Element;
 import org.xml.sax.SAXException;

-import com.amalto.core.util.SAXErrorHandler;
+import com.amalto.commons.core.utils.SAXErrorHandler;
 import com.amalto.core.util.ValidateException;

 public class XmlSchemaValidator implements Validator {


@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2006-2019 Talend Inc. - www.talend.com
- *
- * This source code is available under agreement available at
- * %InstallDIR%\features\org.talend.rcp.branding.%PRODUCTNAME%\%PRODUCTNAME%license.txt
- *
- * You should have received a copy of the agreement along with this program; if not, write to Talend SA 9 rue Pages
- * 92150 Suresnes, France
- */
-package org.talend.mdm.webapp.base.client.util;
-
-import junit.framework.TestCase;
-
-/**
- * DOC talend2 class global comment. Detailled comment
- */
-public class XmlUtilTest extends TestCase {
-
-    public void testTransformXmlAndString() {
-        String xml = "<MultipleCriteria><appearance>true</appearance><SimpleCriterion><key>Product/Id</key><operator>EQUALS</operator><value>*</value><info>*</info></SimpleCriterion><operator>AND</operator><SimpleCriterion><key>Product/Name</key><operator>EQUALS</operator><value>*</value><info>*</info></SimpleCriterion><operator>AND</operator><MultipleCriteria><appearance>true</appearance><SimpleCriterion><key>Product/Description</key><operator>EQUALS</operator><value>*</value><info>*</info></SimpleCriterion><operator>AND</operator><SimpleCriterion><key>Product/Features</key><operator>FULLTEXTSEARCH</operator><value>*</value><info>*</info></SimpleCriterion><operator>AND</operator><MultipleCriteria><appearance>true</appearance><SimpleCriterion><key>Product/Id</key><operator>EQUALS</operator><value>1</value><info>1</info></SimpleCriterion><operator>AND</operator><SimpleCriterion><key>Product/Name</key><operator>EQUALS</operator><value>2</value><info>2</info></SimpleCriterion></MultipleCriteria></MultipleCriteria></MultipleCriteria>"; //$NON-NLS-1$
-        String value = "<MultipleCriteria><appearance>true</appearance><SimpleCriterion><key>Product/Id</key><operator>EQUALS</operator><value>*</value><info>*</info></SimpleCriterion><operator>AND</operator><SimpleCriterion><key>Product/Name</key><operator>EQUALS</operator><value>*</value><info>*</info></SimpleCriterion><operator>AND</operator><MultipleCriteria><appearance>true</appearance><SimpleCriterion><key>Product/Description</key><operator>EQUALS</operator><value>*</value><info>*</info></SimpleCriterion><operator>AND</operator><SimpleCriterion><key>Product/Features</key><operator>FULLTEXTSEARCH</operator><value>*</value><info>*</info></SimpleCriterion><operator>AND</operator><MultipleCriteria><appearance>true</appearance><SimpleCriterion><key>Product/Id</key><operator>EQUALS</operator><value>1</value><info>1</info></SimpleCriterion><operator>AND</operator><SimpleCriterion><key>Product/Name</key><operator>EQUALS</operator><value>2</value><info>2</info></SimpleCriterion></MultipleCriteria></MultipleCriteria></MultipleCriteria>"; //$NON-NLS-1$
-        assertEquals(xml, XmlUtil.transformStringToXml(value));
-        assertEquals(value, XmlUtil.transformXmlToString(xml));
-    }
-}


@@ -12,9 +12,6 @@
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
 import java.io.StringReader;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collection;
@@ -30,8 +27,8 @@
 import javax.xml.parsers.DocumentBuilderFactory;

 import org.apache.commons.lang.ArrayUtils;
-import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 import org.talend.mdm.commmon.util.core.MDMXMLUtils;
 import org.talend.mdm.commmon.util.datamodel.management.BusinessConcept;
 import org.w3c.dom.DOMImplementation;
@@ -41,7 +38,6 @@
 import org.w3c.dom.NodeList;
 import org.xml.sax.InputSource;

-import com.amalto.commons.core.utils.XMLUtils;
 import com.amalto.core.delegator.BeanDelegatorContainer;
 import com.amalto.core.delegator.ILocalUser;
 import com.amalto.core.objects.transformers.TransformerV2POJOPK;
@@ -808,7 +804,7 @@ public static String getDefaultLanguage() throws Exception {

         WSItemPK itemPK = new WSItemPK(new WSDataClusterPK(DATACLUSTER_PK), PROVISIONING_CONCEPT, new String[] { identity });
         if (identity != null && identity.length() > 0) {
-            Document doc = XMLUtils.parse(Util.getPort().getItem(new WSGetItem(itemPK)).getContent());
+            Document doc = MDMXMLUtils.parseXml(Util.getPort().getItem(new WSGetItem(itemPK)).getContent());
             if (doc.getElementsByTagName("language") != null) { //$NON-NLS-1$
                 Node language = doc.getElementsByTagName("language").item(0); //$NON-NLS-1$
                 if (language != null) {


SQL Injection

Changed all of the SQL Queries to be safe from SQL Injeection

@@ -4,6 +4,7 @@
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;

+import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Timestamp;
@@ -116,7 +117,11 @@ public static DailySubmission submissionFromRow(ResultSet resultSet) throws SQLE

         String sales = "";

-        ResultSet salesSet = IndexServlet.execQuery("select * from sales where date='" + date + "'");
+        PreparedStatement statement = IndexServlet.connection.prepareStatement("select * from sales where date=?");
+        statement.setTimestamp(1, date);
+
+        ResultSet salesSet = statement.executeQuery();
+
         while(salesSet.next()){
             sales += salesSet.getString("sale") + ",";
         }


@@ -47,6 +47,7 @@
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;

 import be.e_contract.dssp.client.exception.ClientRuntimeException;
 import be.e_contract.dssp.client.exception.SubjectNotAuthorizedException;
@@ -115,25 +116,32 @@ public static SignResponseVerificationResult checkSignResponse(String signRespon
 		} catch (Base64DecodingException e) {
 			throw new SecurityException("no Base64");
 		}
+
+		// DOM parsing
+		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+		documentBuilderFactory.setNamespaceAware(true);
+		documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+		DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+		InputStream signResponseInputStream = new ByteArrayInputStream(decodedSignResponseMessage);
+		Document signResponseDocument;
+		try {
+			signResponseDocument = documentBuilder.parse(signResponseInputStream);
+		} catch (SAXParseException e) {
+			throw new SecurityException("no valid SignResponse XML");
+		}
+
 		// JAXB parsing
 		JAXBContext jaxbContext = JAXBContext.newInstance(ObjectFactory.class,
 				be.e_contract.dssp.ws.jaxb.dss.async.ObjectFactory.class,
 				be.e_contract.dssp.ws.jaxb.wsa.ObjectFactory.class, be.e_contract.dssp.ws.jaxb.wsu.ObjectFactory.class);
 		Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
 		SignResponse signResponse;
 		try {
-			signResponse = (SignResponse) unmarshaller.unmarshal(new ByteArrayInputStream(decodedSignResponseMessage));
+			signResponse = (SignResponse) unmarshaller.unmarshal(signResponseDocument);
 		} catch (UnmarshalException e) {
 			throw new SecurityException("no valid SignResponse XML");
 		}

-		// DOM parsing
-		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
-		documentBuilderFactory.setNamespaceAware(true);
-		DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
-		InputStream signResponseInputStream = new ByteArrayInputStream(decodedSignResponseMessage);
-		Document signResponseDocument = documentBuilder.parse(signResponseInputStream);
-
 		// signature verification
 		NodeList signatureNodeList = signResponseDocument.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#",
 				"Signature");


Merge pull request from GHSA-7g5f-wrx8-5ccf

* [GEOS-10839] Add JDBC Configuration parameter to disable SQL comments and pretty-printing

* [GEOS-10842] Escape user inputs in SQL queries

* [GEOS-10842] Applied PR feedback

@@ -128,7 +128,7 @@ public QueryBuilder<T> filter(Filter filter) {
         return this;
     }

-    private void querySortBy(StringBuilder query, StringBuilder whereClause, SortBy[] orders) {
+    private void querySortBy(StringBuilder query, String whereClause, SortBy[] orders) {

         /*
          * Start with the oid and id from the object table selecting for type and the filter.
@@ -144,15 +144,15 @@ private void querySortBy(StringBuilder query, StringBuilder whereClause, SortBy[

         int i = 0;

-        query.append("SELECT id FROM ");
-
-        query.append("\n    (SELECT oid, id FROM object WHERE ");
-        if (queryType != null) {
-            query.append("type_id IN (:types) /* ")
-                    .append(queryType.getCanonicalName())
-                    .append(" */\n      AND ");
+        query.append("SELECT id FROM");
+        dialect.appendIfDebug(query, "\n    ", " ");
+        query.append("(SELECT oid, id FROM object WHERE type_id IN (:types)");
+        dialect.appendComment(query, queryType.getName());
+        if (whereClause != null) {
+            dialect.appendIfDebug(query, "      ", " ");
+            query.append("AND ").append(whereClause);
         }
-        query.append(whereClause).append(") object");
+        query.append(") object");

         for (SortBy order : orders) {
             final String sortProperty = order.getPropertyName().getPropertyName();
@@ -167,35 +167,36 @@ private void querySortBy(StringBuilder query, StringBuilder whereClause, SortBy[
             Map<String, Object> namedParameters = getNamedParameters();
             namedParameters.put(propertyParamName, sortPropertyTypeIds);

-            query.append("\n  LEFT JOIN");
-            query.append("\n    (SELECT oid, value ")
-                    .append(attributeName)
-                    .append(" FROM \n      object_property WHERE property_type IN (:")
+            dialect.appendIfDebug(query, "\n  ", " ");
+            query.append("LEFT JOIN");
+            dialect.appendIfDebug(query, "\n    ", " ");
+            query.append("(SELECT oid, value ").append(attributeName).append(" FROM");
+            dialect.appendIfDebug(query, "\n      ", " ");
+            query.append("object_property WHERE property_type IN (:")
                     .append(propertyParamName)
                     .append(")) ")
                     .append(subSelectName);
-
-            query.append(" /* ")
-                    .append(order.getPropertyName().getPropertyName())
-                    .append(" ")
-                    .append(ascDesc(order))
-                    .append(" */");
-
-            query.append("\n  ON object.oid = ").append(subSelectName).append(".oid");
+            dialect.appendComment(
+                    query, order.getPropertyName().getPropertyName(), " ", ascDesc(order));
+            dialect.appendIfDebug(query, "  ", " ");
+            query.append("ON object.oid = ").append(subSelectName).append(".oid");
             // Update the ORDER BY clause to be added later
             if (i > 0) orderBy.append(", ");
             orderBy.append(attributeName).append(" ").append(ascDesc(order));

             i++;
         }
-
-        query.append("\n  ").append(orderBy);
+        dialect.appendIfDebug(query, "\n  ", " ");
+        query.append(orderBy);
     }

-    private StringBuilder buildWhereClause() {
+    private String buildWhereClause() {
+        this.predicateBuilder =
+                new FilterToCatalogSQL(this.dialect, this.queryType, this.dbMappings);
+        if (Filter.INCLUDE.equals(this.originalFilter)) {
+            return null;
+        }
         final SimplifyingFilterVisitor filterSimplifier = new SimplifyingFilterVisitor();
-
-        this.predicateBuilder = new FilterToCatalogSQL(this.queryType, this.dbMappings);
         Capabilities fcs = new Capabilities(FilterToCatalogSQL.CAPABILITIES);
         FeatureType parent = null;
         // use this to instruct the filter splitter which filters can be encoded depending on
@@ -238,36 +239,42 @@ public Filter getDeleteFilter() {
                 (Filter) supported.accept(new LiteralDemultiplyingFilterVisitor(), null);
         this.supportedFilter = (Filter) demultipliedFilter.accept(filterSimplifier, null);
         this.unsupportedFilter = (Filter) unsupported.accept(filterSimplifier, null);
-
+        if (Filter.INCLUDE.equals(this.supportedFilter)) {
+            return null;
+        }
         StringBuilder whereClause = new StringBuilder();
-        return (StringBuilder) this.supportedFilter.accept(predicateBuilder, whereClause);
+        return this.supportedFilter.accept(predicateBuilder, whereClause).toString();
     }

-    public StringBuilder build() {
+    public String build() {

-        StringBuilder whereClause = buildWhereClause();
+        String whereClause = buildWhereClause();

         StringBuilder query = new StringBuilder();
         if (isCountQuery) {
-            if (Filter.INCLUDE.equals(this.originalFilter)) {
-                query.append("SELECT COUNT(oid) FROM object WHERE type_id IN (:types)");
-            } else {
-                query.append("SELECT COUNT(oid) FROM object WHERE type_id IN (:types) AND (\n");
-                query.append(whereClause).append(")");
+            query.append("SELECT COUNT(oid) FROM object WHERE type_id IN (:types)");
+            dialect.appendComment(query, queryType.getName());
+            if (whereClause != null) {
+                dialect.appendIfDebug(query, "", " ");
+                query.append("AND ").append(whereClause);
             }
         } else {
-            SortBy[] orders = this.sortOrder;
-            if (orders == null) {
-                query.append("SELECT id FROM object WHERE type_id IN (:types) AND (\n");
-                query.append(whereClause).append(")");
-                query.append(" ORDER BY oid");
+            if (sortOrder != null) {
+                querySortBy(query, whereClause, sortOrder);
             } else {
-                querySortBy(query, whereClause, orders);
+                query.append("SELECT id FROM object WHERE type_id IN (:types)");
+                dialect.appendComment(query, queryType.getName());
+                dialect.appendIfDebug(query, "", " ");
+                if (whereClause != null) {
+                    query.append("AND ").append(whereClause);
+                    dialect.appendIfDebug(query, whereClause.endsWith("\n") ? "" : " ", " ");
+                }
+                query.append("ORDER BY oid");
             }
             applyOffsetLimit(query);
         }

-        return query;
+        return query.toString().trim();
     }

     /** When the query was built, were the offset and limit included. */


Merge pull request from GHSA-99c3-qc2q-p94m

@@ -246,7 +246,12 @@ public void applyLimitOffset(StringBuffer sql, int limit, int offset) {

     @Override
     public FilterToSQL createFilterToSQL() {
-        return new MySQLFilterToSQL(delegate.getUsePreciseSpatialOps());
+        MySQLFilterToSQL fts = new MySQLFilterToSQL(delegate.getUsePreciseSpatialOps());
+        // see https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sqlmode_no_backslash_escapes
+        // NOTE: for future enhancement, do not escape backslashes when the NO_BACKSLASH_ESCAPES
+        // mode is enabled since that would create an incorrect string in the SQL
+        fts.setEscapeBackslash(true);
+        return fts;
     }

     @Override


@@ -0,0 +1,163 @@
+/*
+ *    GeoTools - The Open Source Java GIS Toolkit
+ *    http://geotools.org
+ *
+ *    (C) 2023, Open Source Geospatial Foundation (OSGeo)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation;
+ *    version 2.1 of the License.
+ *
+ *    This library is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *    Lesser General Public License for more details.
+ */
+package org.geotools.data.postgis;
+
+import static org.geotools.data.postgis.PostgisNGDataStoreFactory.PREPARED_STATEMENTS;
+import static org.geotools.jdbc.JDBCDataStoreFactory.DATASOURCE;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Map;
+import javax.sql.DataSource;
+import org.geotools.jdbc.JDBCDataStore;
+import org.geotools.jdbc.SQLDialect;
+import org.geotools.util.Version;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.postgresql.jdbc.PgConnection;
+
+public class PostgisNGDataStoreFactoryTest {
+
+    @Mock private DataSource ds = null;
+
+    @Mock private Connection conn = null;
+
+    @Mock private PgConnection pgConn = null;
+
+    @Mock private DatabaseMetaData md = null;
+
+    @Mock private Statement st1 = null;
+
+    @Mock private Statement st2 = null;
+
+    @Mock private ResultSet rs1 = null;
+
+    @Mock private ResultSet rs2 = null;
+
+    private JDBCDataStore store = null;
+
+    private AutoCloseable mocks = null;
+
+    @Before
+    public void setUp() throws Exception {
+        this.mocks = MockitoAnnotations.openMocks(this);
+        when(this.ds.getConnection()).thenReturn(this.conn);
+        when(this.conn.getMetaData()).thenReturn(this.md);
+        when(this.md.getDatabaseMajorVersion()).thenReturn(15);
+        when(this.md.getDatabaseMinorVersion()).thenReturn(1);
+        when(this.conn.createStatement()).thenReturn(this.st1, this.st2);
+        when(this.st1.executeQuery("select PostGIS_Lib_Version()")).thenReturn(this.rs1);
+        when(this.rs1.next()).thenReturn(true);
+        when(this.rs1.getString(1)).thenReturn("3.3.2");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (this.mocks != null) {
+            this.mocks.close();
+            this.mocks = null;
+        }
+        if (this.store != null) {
+            this.store.dispose();
+        }
+    }
+
+    @Test
+    public void testStandardConformingStringsOnFromConnection() throws Exception {
+        verifyFilterToSqlSettings(false, false, false);
+    }
+
+    @Test
+    public void testStandardConformingStringsOffFromConnection() throws Exception {
+        verifyFilterToSqlSettings(true, false, false);
+    }
+
+    @Test
+    public void testStandardConformingStringsOnFromQuery() throws Exception {
+        verifyFilterToSqlSettings(false, false, true);
+    }
+
+    @Test
+    public void testStandardConformingStringsOffFromQuery() throws Exception {
+        verifyFilterToSqlSettings(true, false, true);
+    }
+
+    @Test
+    public void testStandardConformingStringsOnWithPSFromConnection() throws Exception {
+        verifyFilterToSqlSettings(false, true, false);
+    }
+
+    @Test
+    public void testStandardConformingStringsOffWithPSFromConnection() throws Exception {
+        verifyFilterToSqlSettings(true, true, false);
+    }
+
+    @Test
+    public void testStandardConformingStringsOnWithPSFromQuery() throws Exception {
+        verifyFilterToSqlSettings(false, true, true);
+    }
+
+    @Test
+    public void testStandardConformingStringsOffWithPSFromQuery() throws Exception {
+        verifyFilterToSqlSettings(true, true, true);
+    }
+
+    private void verifyFilterToSqlSettings(
+            boolean escapeBackslash, boolean withPS, boolean withQuery) throws Exception {
+        if (withQuery) {
+            when(this.st2.executeQuery("SHOW standard_conforming_strings")).thenReturn(this.rs2);
+            when(this.rs2.next()).thenReturn(true);
+            when(this.rs2.getString(1)).thenReturn(escapeBackslash ? "off" : "on");
+        } else {
+            when(this.conn.isWrapperFor(PgConnection.class)).thenReturn(true);
+            when(this.conn.unwrap(PgConnection.class)).thenReturn(this.pgConn);
+            when(this.pgConn.getStandardConformingStrings()).thenReturn(!escapeBackslash);
+        }
+        Map<String, Object> params = new HashMap<>();
+        params.put(DATASOURCE.key, this.ds);
+        params.put(PREPARED_STATEMENTS.key, withPS);
+        this.store = new PostgisNGDataStoreFactory().createDataStore(params);
+        assertNotNull(this.store);
+        SQLDialect dialect = this.store.getSQLDialect();
+        assertThat(dialect, instanceOf(withPS ? PostGISPSDialect.class : PostGISDialect.class));
+        PostGISDialect pgDialect =
+                withPS ? ((PostGISPSDialect) dialect).getDelegate() : (PostGISDialect) dialect;
+        assertEquals(new Version("15.1"), pgDialect.getPostgreSQLVersion(this.conn));
+        assertEquals(new Version("3.3.2"), pgDialect.getVersion(this.conn));
+        assertEquals(escapeBackslash, pgDialect.isEscapeBackslash());
+        verify(this.conn, withQuery ? times(2) : times(1)).createStatement();
+        verify(this.conn).close();
+        verify(this.st1).close();
+        verify(this.rs1).close();
+        verify(this.st2, withQuery ? times(1) : never()).close();
+        verify(this.rs2, withQuery ? times(1) : never()).close();
+    }
+}