diff --git a/exist-core/pom.xml b/exist-core/pom.xml index 23c34c992d7..b73fe462ba0 100644 --- a/exist-core/pom.xml +++ b/exist-core/pom.xml @@ -698,12 +698,15 @@ src/main/java/org/exist/xquery/Cardinality.java src/test/java/org/exist/xquery/ImportModuleTest.java src/test/java/org/exist/xquery/XQueryContextAttributesTest.java + src/main/java/org/exist/xquery/functions/AccessUtil.java + src/test/java/org/exist/xquery/functions/fn/FunEnvironmentTest.java src/main/java/org/exist/xquery/functions/map/MapType.java src/test/java/org/exist/xquery/functions/session/AbstractSessionTest.java src/test/java/org/exist/xquery/functions/xmldb/AbstractXMLDBTest.java src/test/java/org/exist/xquery/functions/session/AttributeTest.java src/test/java/org/exist/xquery/functions/xmldb/XMLDBAuthenticateTest.java src/main/java/org/exist/xquery/functions/util/Eval.java + src/test/java/org/exist/xquery/functions/util/SystemPropertyTest.java src/test/java/org/exist/xquery/util/URIUtilsTest.java src/main/java/org/exist/xquery/value/ArrayListValueSequence.java src/test/java/org/exist/xquery/value/BifurcanMapTest.java @@ -846,12 +849,15 @@ The original license statement is also included below.]]> src/main/java/org/exist/xquery/Cardinality.java src/test/java/org/exist/xquery/ImportModuleTest.java src/test/java/org/exist/xquery/XQueryContextAttributesTest.java + src/main/java/org/exist/xquery/functions/AccessUtil.java + src/test/java/org/exist/xquery/functions/fn/FunEnvironmentTest.java src/main/java/org/exist/xquery/functions/map/MapType.java src/test/java/org/exist/xquery/functions/session/AbstractSessionTest.java src/test/java/org/exist/xquery/functions/xmldb/AbstractXMLDBTest.java src/test/java/org/exist/xquery/functions/session/AttributeTest.java src/test/java/org/exist/xquery/functions/xmldb/XMLDBAuthenticateTest.java src/main/java/org/exist/xquery/functions/util/Eval.java + src/test/java/org/exist/xquery/functions/util/SystemPropertyTest.java src/test/java/org/exist/xquery/util/URIUtilsTest.java src/main/java/org/exist/xquery/value/ArrayListValueSequence.java src/test/java/org/exist/xquery/value/BifurcanMapTest.java diff --git a/exist-core/src/main/java/org/exist/SystemProperties.java b/exist-core/src/main/java/org/exist/ExistSystemProperties.java similarity index 52% rename from exist-core/src/main/java/org/exist/SystemProperties.java rename to exist-core/src/main/java/org/exist/ExistSystemProperties.java index ba3214ab18f..1c1c03c4608 100644 --- a/exist-core/src/main/java/org/exist/SystemProperties.java +++ b/exist-core/src/main/java/org/exist/ExistSystemProperties.java @@ -24,32 +24,41 @@ import java.io.IOException; import java.io.InputStream; import java.util.Properties; +import java.util.Set; -import com.evolvedbinary.j8fu.lazy.LazyVal; +import com.evolvedbinary.j8fu.lazy.AtomicLazyVal; +import net.jcip.annotations.ThreadSafe; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * - * @author aretter + * @author Adam Retter */ -public class SystemProperties { +@ThreadSafe +public class ExistSystemProperties { - private static final Logger LOG = LogManager.getLogger(SystemProperties.class); - private static final SystemProperties instance = new SystemProperties(); + public static final String PROP_PRODUCT_NAME = "product-name"; + public static final String PROP_PRODUCT_VERSION = "product-version"; + public static final String PROP_PRODUCT_BUILD = "product-build"; + public static final String PROP_GIT_BRANCH = "git-branch"; + public static final String PROP_GIT_COMMIT = "git-commit"; - private final LazyVal properties = new LazyVal<>(this::load); + private static final Logger LOG = LogManager.getLogger(ExistSystemProperties.class); + private static final ExistSystemProperties instance = new ExistSystemProperties(); - public final static SystemProperties getInstance() { + private final AtomicLazyVal properties = new AtomicLazyVal<>(this::load); + + public final static ExistSystemProperties getInstance() { return instance; } - private SystemProperties() { + private ExistSystemProperties() { } private Properties load() { final Properties properties = new Properties(); - try (final InputStream is = SystemProperties.class.getResourceAsStream("system.properties")) { + try (final InputStream is = ExistSystemProperties.class.getResourceAsStream("system.properties")) { if (is != null) { properties.load(is); } @@ -59,11 +68,20 @@ private Properties load() { return properties; } - public String getSystemProperty(final String propertyName) { + public String getExistSystemProperty(final String propertyName) { return properties.get().getProperty(propertyName); } - public String getSystemProperty(final String propertyName, final String defaultValue) { + public String getExistSystemProperty(final String propertyName, final String defaultValue) { return properties.get().getProperty(propertyName, defaultValue); } + + /** + * Get the available eXist System Properties. + * + * @return the available eXist System Properties. + */ + public Set getAvailableExistSystemProperties() { + return properties.get().stringPropertyNames(); + } } \ No newline at end of file diff --git a/exist-core/src/main/java/org/exist/Version.java b/exist-core/src/main/java/org/exist/Version.java index c86dc623f0c..f22c328d521 100644 --- a/exist-core/src/main/java/org/exist/Version.java +++ b/exist-core/src/main/java/org/exist/Version.java @@ -36,12 +36,12 @@ public final class Version { static { - final SystemProperties systemProperties = SystemProperties.getInstance(); - NAME = systemProperties.getSystemProperty("product-name", "eXist"); - VERSION = systemProperties.getSystemProperty("product-version"); - BUILD = systemProperties.getSystemProperty("product-build"); - GIT_BRANCH = systemProperties.getSystemProperty("git-branch"); - GIT_COMMIT = systemProperties.getSystemProperty("git-commit"); + final ExistSystemProperties existSystemProperties = ExistSystemProperties.getInstance(); + NAME = existSystemProperties.getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_NAME, "eXist"); + VERSION = existSystemProperties.getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_VERSION); + BUILD = existSystemProperties.getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_BUILD); + GIT_BRANCH = existSystemProperties.getExistSystemProperty(ExistSystemProperties.PROP_GIT_BRANCH); + GIT_COMMIT = existSystemProperties.getExistSystemProperty(ExistSystemProperties.PROP_GIT_COMMIT); } public static String getProductName() { diff --git a/exist-core/src/main/java/org/exist/client/ClientFrame.java b/exist-core/src/main/java/org/exist/client/ClientFrame.java index 05fe7834b77..432dc14a806 100644 --- a/exist-core/src/main/java/org/exist/client/ClientFrame.java +++ b/exist-core/src/main/java/org/exist/client/ClientFrame.java @@ -21,7 +21,7 @@ */ package org.exist.client; -import org.exist.SystemProperties; +import org.exist.ExistSystemProperties; import org.exist.backup.Backup; import org.exist.backup.CreateBackupDialog; import org.exist.backup.GuiRestoreServiceTaskListener; @@ -1724,7 +1724,7 @@ protected static Properties getLoginData(final Properties props) { final ConnectionDialog connectionDialog = new ConnectionDialog(null, true, defaultConnectionSettings, Boolean.parseBoolean(props.getProperty(InteractiveClient.LOCAL_MODE, InteractiveClient.LOCAL_MODE_DEFAULT)), Boolean.parseBoolean(props.getProperty(InteractiveClient.NO_EMBED_MODE, InteractiveClient.NO_EMBED_MODE_DEFAULT))); - connectionDialog.setTitle(SystemProperties.getInstance().getSystemProperty("product-name", "eXist-db") + " " + SystemProperties.getInstance().getSystemProperty("product-version", "unknown") + " Database Login"); + connectionDialog.setTitle(ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_NAME, "eXist-db") + " " + ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_VERSION, "unknown") + " Database Login"); connectionDialog.addDialogCompleteWithResponseCallback(connection -> { properties.setProperty(InteractiveClient.USER, connection.getUsername()); diff --git a/exist-core/src/main/java/org/exist/client/InteractiveClient.java b/exist-core/src/main/java/org/exist/client/InteractiveClient.java index 0c7faa6fd03..66e08ca9b1a 100644 --- a/exist-core/src/main/java/org/exist/client/InteractiveClient.java +++ b/exist-core/src/main/java/org/exist/client/InteractiveClient.java @@ -44,9 +44,8 @@ import javax.xml.transform.OutputKeys; import org.apache.tools.ant.DirectoryScanner; -import org.exist.SystemProperties; +import org.exist.ExistSystemProperties; import org.exist.dom.persistent.XMLUtil; -import org.exist.security.ACLPermission; import org.exist.security.Account; import org.exist.security.Permission; import org.exist.security.SecurityManager; @@ -2517,12 +2516,12 @@ public void printNotice() { public String getNotice() { final StringBuilder builder = new StringBuilder(); - builder.append(SystemProperties.getInstance().getSystemProperty("product-name", "eXist-db")); + builder.append(ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_NAME, "eXist-db")); builder.append(" version "); - builder.append(SystemProperties.getInstance().getSystemProperty("product-version", "unknown")); - if (!"".equals(SystemProperties.getInstance().getSystemProperty("git-commit", ""))) { + builder.append(ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_VERSION, "unknown")); + if (!"".equals(ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_GIT_COMMIT, ""))) { builder.append(" ("); - builder.append(SystemProperties.getInstance().getSystemProperty("git-commit", "(unknown Git commit ID)")); + builder.append(ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_GIT_COMMIT, "(unknown Git commit ID)")); builder.append(")"); } builder.append(", Copyright (C) 2001-"); diff --git a/exist-core/src/main/java/org/exist/jetty/JettyStart.java b/exist-core/src/main/java/org/exist/jetty/JettyStart.java index 7ec2737ab68..3d882410c43 100644 --- a/exist-core/src/main/java/org/exist/jetty/JettyStart.java +++ b/exist-core/src/main/java/org/exist/jetty/JettyStart.java @@ -33,7 +33,7 @@ import org.eclipse.jetty.util.MultiException; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.xml.XmlConfiguration; -import org.exist.SystemProperties; +import org.exist.ExistSystemProperties; import org.exist.http.servlets.ExistExtensionServlet; import org.exist.start.CompatibleJavaVersionCheck; import org.exist.start.Main; @@ -190,9 +190,9 @@ public synchronized void run(final String[] args, final Observer observer) { logger.info("Running as user '{}'", System.getProperty("user.name", "(unknown user.name)")); logger.info("[eXist Home : {}]", System.getProperty("exist.home", "unknown")); - logger.info("[eXist Version : {}]", SystemProperties.getInstance().getSystemProperty("product-version", "unknown")); - logger.info("[eXist Build : {}]", SystemProperties.getInstance().getSystemProperty("product-build", "unknown")); - logger.info("[Git commit : {}]", SystemProperties.getInstance().getSystemProperty("git-commit", "unknown")); + logger.info("[eXist Version : {}]", ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_VERSION, "unknown")); + logger.info("[eXist Build : {}]", ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_BUILD, "unknown")); + logger.info("[Git commit : {}]", ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_GIT_COMMIT, "unknown")); logger.info("[Operating System : {} {} {}]", System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch")); logger.info("[log4j.configurationFile : {}]", System.getProperty("log4j.configurationFile")); diff --git a/exist-core/src/main/java/org/exist/launcher/SplashScreen.java b/exist-core/src/main/java/org/exist/launcher/SplashScreen.java index 08689214d79..baf381e2d0b 100644 --- a/exist-core/src/main/java/org/exist/launcher/SplashScreen.java +++ b/exist-core/src/main/java/org/exist/launcher/SplashScreen.java @@ -21,6 +21,7 @@ */ package org.exist.launcher; +import org.exist.ExistSystemProperties; import org.exist.jetty.JettyStart; import org.exist.storage.BrokerPool; @@ -31,8 +32,6 @@ import java.util.Observable; import java.util.Observer; -import org.exist.SystemProperties; - /** * Display a splash screen showing the eXist-db logo and a status line. * @@ -67,11 +66,11 @@ public SplashScreen(Launcher launcher) { getContentPane().add(imageLabel, BorderLayout.NORTH); // version label - final SystemProperties sysProps = SystemProperties.getInstance(); + final ExistSystemProperties sysProps = ExistSystemProperties.getInstance(); final StringBuilder builder = new StringBuilder(); builder.append("Version "); - builder.append(sysProps.getSystemProperty("product-version", "unknown")); - final String gitCommit = sysProps.getSystemProperty("git-commit"); + builder.append(sysProps.getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_VERSION, "unknown")); + final String gitCommit = sysProps.getExistSystemProperty(ExistSystemProperties.PROP_GIT_COMMIT); if (gitCommit != null && !gitCommit.isEmpty()) { builder.append(" ("); builder.append(gitCommit, 0, Math.min(7, gitCommit.length())); diff --git a/exist-core/src/main/java/org/exist/management/impl/SystemInfo.java b/exist-core/src/main/java/org/exist/management/impl/SystemInfo.java index e5b92ca8eca..0fb8cfe79db 100644 --- a/exist-core/src/main/java/org/exist/management/impl/SystemInfo.java +++ b/exist-core/src/main/java/org/exist/management/impl/SystemInfo.java @@ -24,7 +24,7 @@ import java.nio.charset.Charset; import java.util.Locale; -import org.exist.SystemProperties; +import org.exist.ExistSystemProperties; /** * Class SystemInfo @@ -38,22 +38,22 @@ public class SystemInfo implements SystemInfoMXBean { @Override public String getProductName() { - return SystemProperties.getInstance().getSystemProperty("product-name","eXist"); + return ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_NAME,"eXist"); } @Override public String getProductVersion() { - return SystemProperties.getInstance().getSystemProperty("product-version","unknown"); + return ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_VERSION,"unknown"); } @Override public String getProductBuild() { - return SystemProperties.getInstance().getSystemProperty("product-build","unknown"); + return ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_BUILD,"unknown"); } @Override public String getGitCommit() { - return SystemProperties.getInstance().getSystemProperty("git-commit", "unknown Git commit ID"); + return ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_GIT_COMMIT, "unknown Git commit ID"); } @Override diff --git a/exist-core/src/main/java/org/exist/repo/ClasspathHelper.java b/exist-core/src/main/java/org/exist/repo/ClasspathHelper.java index a41b76a6345..9afd3586075 100644 --- a/exist-core/src/main/java/org/exist/repo/ClasspathHelper.java +++ b/exist-core/src/main/java/org/exist/repo/ClasspathHelper.java @@ -23,7 +23,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.exist.SystemProperties; +import org.exist.ExistSystemProperties; import org.exist.start.Classpath; import org.exist.start.EXistClassLoader; import org.exist.storage.BrokerPool; @@ -108,7 +108,7 @@ private static void scanPackages(BrokerPool pool, Classpath classpath) { private static boolean isCompatible(final Package pkg) throws PackageException { // determine the eXist-db version this package is compatible with final Collection processorDeps = pkg.getProcessorDeps(); - final String procVersion = SystemProperties.getInstance().getSystemProperty("product-version", "1.0"); + final String procVersion = ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_VERSION, "1.0"); PackageLoader.Version requiresExistVersion = null; for (final ProcessorDependency dependency: processorDeps) { if (Deployment.PROCESSOR_NAME.equals(dependency.getProcessor())) { diff --git a/exist-core/src/main/java/org/exist/repo/Deployment.java b/exist-core/src/main/java/org/exist/repo/Deployment.java index d4692a75070..869ccbf9b81 100644 --- a/exist-core/src/main/java/org/exist/repo/Deployment.java +++ b/exist-core/src/main/java/org/exist/repo/Deployment.java @@ -25,7 +25,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.EXistException; -import org.exist.SystemProperties; +import org.exist.ExistSystemProperties; import org.exist.collections.Collection; import org.exist.collections.triggers.TriggerException; import org.exist.dom.QName; @@ -286,7 +286,7 @@ public Optional installAndDeploy(final DBBroker broker, final Txn transa } private void checkProcessorVersion(final PackageLoader.Version version) throws PackageException { - final String procVersion = SystemProperties.getInstance().getSystemProperty("product-version", "1.0"); + final String procVersion = ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_VERSION, "1.0"); final DependencyVersion depVersion = version.getDependencyVersion(); if (!depVersion.isCompatible(procVersion)) { diff --git a/exist-core/src/main/java/org/exist/util/Configuration.java b/exist-core/src/main/java/org/exist/util/Configuration.java index e83ebcda02a..cdb4a944e31 100644 --- a/exist-core/src/main/java/org/exist/util/Configuration.java +++ b/exist-core/src/main/java/org/exist/util/Configuration.java @@ -413,9 +413,6 @@ private void configureXQuery( Element xquery ) throws DatabaseConfigurationExcep * @throws DatabaseConfigurationException */ private void loadModuleClasses( Element xquery, Map> modulesClassMap, Map modulesSourceMap, Map>> moduleParameters) throws DatabaseConfigurationException { - // add the standard function module - modulesClassMap.put(Namespaces.XPATH_FUNCTIONS_NS, org.exist.xquery.functions.fn.FnModule.class); - // add other modules specified in configuration final NodeList builtins = xquery.getElementsByTagName(XQUERY_BUILTIN_MODULES_CONFIGURATION_MODULES_ELEMENT_NAME); @@ -477,6 +474,11 @@ private void loadModuleClasses( Element xquery, Map> modulesCla } } } + + // if not specified in the conf.xml, then add the standard function module anyway + if (!modulesClassMap.containsKey(Namespaces.XPATH_FUNCTIONS_NS)) { + modulesClassMap.put(Namespaces.XPATH_FUNCTIONS_NS, org.exist.xquery.functions.fn.FnModule.class); + } } /** diff --git a/exist-core/src/main/java/org/exist/webstart/JnlpWriter.java b/exist-core/src/main/java/org/exist/webstart/JnlpWriter.java index 89e2c83cc3f..d8b5f7d5111 100644 --- a/exist-core/src/main/java/org/exist/webstart/JnlpWriter.java +++ b/exist-core/src/main/java/org/exist/webstart/JnlpWriter.java @@ -35,7 +35,7 @@ import org.apache.commons.io.FilenameUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.exist.SystemProperties; +import org.exist.ExistSystemProperties; import org.exist.util.FileUtils; import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; @@ -101,7 +101,7 @@ void writeJnlpXML(JnlpJarFiles jnlpFiles, HttpServletRequest request, writer.writeAttribute("codebase", codeBase); writer.writeAttribute("href", "exist.jnlp"); - String version = SystemProperties.getInstance().getSystemProperty("product-version", null); + String version = ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_VERSION, null); if(version!=null){ writer.writeAttribute("version", version); } diff --git a/exist-core/src/main/java/org/exist/xquery/AbstractInternalModule.java b/exist-core/src/main/java/org/exist/xquery/AbstractInternalModule.java index 5d33ed848e8..29ad602cc3f 100644 --- a/exist-core/src/main/java/org/exist/xquery/AbstractInternalModule.java +++ b/exist-core/src/main/java/org/exist/xquery/AbstractInternalModule.java @@ -84,6 +84,15 @@ protected List getParameter(final String paramName) { return parameters.get(paramName); } + /** + * Get the module parameters. + * + * @return the module parameters. + */ + protected Map> getParameters() { + return parameters; + } + @Override public void setContextItem(final Sequence contextItem) { // not used for internal modules diff --git a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java index 4ebea126e94..7e4f76ffaa4 100644 --- a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java +++ b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java @@ -51,6 +51,8 @@ import com.evolvedbinary.j8fu.function.QuadFunctionE; import com.evolvedbinary.j8fu.tuple.Tuple2; import com.ibm.icu.text.Collator; +import io.lacuna.bifurcan.IMap; +import io.lacuna.bifurcan.LinearMap; import it.unimi.dsi.fastutil.Hash; import it.unimi.dsi.fastutil.objects.*; import net.jcip.annotations.Immutable; @@ -372,7 +374,10 @@ public class XQueryContext implements BinaryValueManager, Context { protected Profiler profiler; //For holding the environment variables - private Map envs; + private IMap envs; + + //For holding the Java System Properties + private IMap props; private ContextUpdateListener updateListener = null; @@ -2732,13 +2737,33 @@ public void resolveForwardReferences() throws XPathException { * * @return Map of environment variables */ - public Map getEnvironmentVariables() { + public io.lacuna.bifurcan.IMap getEnvironmentVariables() { if (envs == null) { - envs = System.getenv(); + envs = io.lacuna.bifurcan.Map.from(System.getenv()); } return envs; } + /** + * Get Java System properties. The properties shall not change + * during execution of query. + * + * @return Map of Java System Properties + */ + public io.lacuna.bifurcan.IMap getJavaSystemProperties() { + if (props == null) { + final IMap strProps = new LinearMap<>(); + for (final Map.Entry prop : System.getProperties().entrySet()) { + final Object value = prop.getValue(); + if (value instanceof String) { + strProps.put(prop.getKey().toString(), (String) value); + } + } + props = strProps.forked(); + } + return props; + } + /** * Gets the Effective user * i.e. the user that the query is executing as diff --git a/exist-core/src/main/java/org/exist/xquery/functions/AccessUtil.java b/exist-core/src/main/java/org/exist/xquery/functions/AccessUtil.java new file mode 100644 index 00000000000..c4cac76bd23 --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/functions/AccessUtil.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2014, Evolved Binary Ltd + * + * This file was originally ported from FusionDB to eXist-db by + * Evolved Binary, for the benefit of the eXist-db Open Source community. + * Only the ported code as it appears in this file, at the time that + * it was contributed to eXist-db, was re-licensed under The GNU + * Lesser General Public License v2.1 only for use in eXist-db. + * + * This license grant applies only to a snapshot of the code as it + * appeared when ported, it does not offer or infer any rights to either + * updates of this source code or access to the original source code. + * + * The GNU Lesser General Public License v2.1 only license follows. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2014, Evolved Binary Ltd + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.xquery.functions; + +import com.evolvedbinary.j8fu.tuple.Tuple2; +import io.lacuna.bifurcan.IMap; +import io.lacuna.bifurcan.ISet; +import io.lacuna.bifurcan.LinearSet; +import io.lacuna.bifurcan.LinearMap; +import io.lacuna.bifurcan.Map; +import io.lacuna.bifurcan.Set; +import org.exist.security.Subject; +import org.exist.security.internal.SecurityManagerImpl; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple; + +/** + * @author Adam Retter + */ +public class AccessUtil { + + static final String OTHERWISE = "*"; + + /** + * Parse access parameters into Group Access Rules and User Access Rules. + * + * @param accessRulePattern A pattern whose first group matches a "name", + * and whose second pattern matches the String "Group" or "User". + * @param parameters the module parameters. + * + * @return a Tuple where the first entry is the Group Access Rules, and the second entry is the User Access Rules. + */ + public static Tuple2>, IMap>> parseAccessParameters( + final Pattern accessRulePattern, final java.util.Map> parameters) { + IMap> accessGroupRules = null; + IMap> accessUserRules = null; + + if (parameters != null) { + Matcher matcher = null; + for (final java.util.Map.Entry> parameter : parameters.entrySet()) { + final String parameterName = parameter.getKey(); + if (matcher == null) { + matcher = accessRulePattern.matcher(parameterName); + } else { + matcher.reset(parameterName); + } + + if (matcher.matches()) { + final String principalType = matcher.group(2); + if ("Group".equals(principalType)) { + if (accessGroupRules == null) { + accessGroupRules = new LinearMap<>(); + } + final String name = matcher.group(1); + accessGroupRules.put(name, toSet(parameter.getValue())); + } else if ("User".equals(principalType)) { + if (accessUserRules == null) { + accessUserRules = new LinearMap<>(); + } + final String name = matcher.group(1); + accessUserRules.put(name, toSet(parameter.getValue())); + } + } + } + } + + if ((accessGroupRules == null || !accessGroupRules.contains(OTHERWISE)) + && ((accessUserRules == null) || !accessUserRules.contains(OTHERWISE))) { + if (accessGroupRules == null) { + accessGroupRules = new LinearMap<>(1); + ISet otherwiseDba = new LinearSet<>(1); + otherwiseDba.add(SecurityManagerImpl.DBA_GROUP); + otherwiseDba = otherwiseDba.forked(); + accessGroupRules.put(OTHERWISE, otherwiseDba); + } + } + + if (accessGroupRules == null) { + accessGroupRules = Map.empty(); + } else { + accessGroupRules = accessGroupRules.forked(); + } + + if (accessUserRules == null) { + accessUserRules = Map.empty(); + } else { + accessUserRules = accessUserRules.forked(); + } + + return Tuple(accessGroupRules, accessUserRules); + } + + private static ISet toSet(final List values) { + ISet set; + if (values.size() > 0) { + set = new LinearSet<>(); + for (final Object value : values) { + set.add(value.toString()); + } + set = set.forked(); + } else { + set = Set.empty(); + } + return set; + } + + /** + * Returns true of the user is allowed access to the name. + * + * @param user the user to test the access rules against. + * @param accessGroupRules the group access rules. + * @param accessUserRules the user access rules. + * @param name the name of the resource that the user wishes to access. + */ + public static boolean isAllowedAccess(final Subject user, final IMap> accessGroupRules, + final IMap> accessUserRules, final String name) { + return hasGroupAccess(user, accessGroupRules, name) + || hasUserAccess(user, accessUserRules, name); + } + + private static boolean hasGroupAccess(final Subject user, final IMap> accessGroupRules, + final String name) { + ISet accessGroups = accessGroupRules.get(name, null); + + // fallback to "otherwise" + if (accessGroups == null) { + accessGroups = accessGroupRules.get(OTHERWISE, null); + } + + if (accessGroups != null) { + final String[] userGroups = user.getGroups(); + for (final String userGroup : userGroups) { + if (accessGroups.contains(userGroup)) { + return true; + } + } + } + + return false; + } + + private static boolean hasUserAccess(final Subject user, final IMap> accessUserRules, + final String name) { + ISet accessUsers = accessUserRules.get(name, null); + + // fallback to "otherwise" + if (accessUsers == null) { + accessUsers = accessUserRules.get(OTHERWISE, null); + } + + if (accessUsers != null) { + return accessUsers.contains(user.getUsername()); + } + + return false; + } +} \ No newline at end of file diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FnModule.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FnModule.java index aca7ebe1197..ef847ccabb2 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FnModule.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FnModule.java @@ -24,9 +24,15 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; +import com.evolvedbinary.j8fu.tuple.Tuple2; +import io.lacuna.bifurcan.IMap; +import io.lacuna.bifurcan.ISet; import org.exist.dom.QName; +import org.exist.util.PatternFactory; import org.exist.xquery.*; +import org.exist.xquery.functions.AccessUtil; import org.exist.xquery.value.FunctionParameterSequenceType; import org.exist.xquery.value.FunctionReturnSequenceType; @@ -279,7 +285,12 @@ public class FnModule extends AbstractInternalModule { public final static ErrorCodes.ErrorCode SEPM0019 = new ErrorCodes.ErrorCode("SEPM0019", "It is an error if an instance of the data model " + "used to specify the settings of serialization parameters specifies the value of the same parameter more than once."); - public FnModule(Map> parameters) { + private static final Pattern PTN_ENVIRONMENT_VARIABLE_ACCESS = PatternFactory.getInstance().getPattern("environmentVariableAccess\\.([^=\\00])+\\.requires((?:Group)|(?:User))"); + + private IMap> environmentVariableAccessGroups = null; + private IMap> environmentVariableAccessUsers = null; + + public FnModule(final Map> parameters) { super(functions, parameters, true); } @@ -308,6 +319,34 @@ public String getReleaseVersion() { return RELEASED_IN_VERSION; } + /** + * Get the environment variable names and groups that are allowed to access them. + * + * @return a map where the key is the environment variable name, and the value is a set of group names. + */ + IMap> getEnvironmentVariableAccessGroups() { + if (environmentVariableAccessGroups == null) { + final Tuple2>, IMap>> accessRules = AccessUtil.parseAccessParameters(PTN_ENVIRONMENT_VARIABLE_ACCESS, getParameters()); + this.environmentVariableAccessGroups = accessRules._1; + this.environmentVariableAccessUsers = accessRules._2; + } + return environmentVariableAccessGroups; + } + + /** + * Get the environment variable names and users that are allowed to access them. + * + * @return a map where the key is the environment variable name, and the value is a set of usernames. + */ + IMap> getEnvironmentVariableAccessUsers() { + if (environmentVariableAccessUsers == null) { + final Tuple2>, IMap>> accessRules = AccessUtil.parseAccessParameters(PTN_ENVIRONMENT_VARIABLE_ACCESS, getParameters()); + this.environmentVariableAccessGroups = accessRules._1; + this.environmentVariableAccessUsers = accessRules._2; + } + return environmentVariableAccessUsers; + } + static FunctionSignature functionSignature(final String name, final String description, final FunctionReturnSequenceType returnType, final FunctionParameterSequenceType... paramTypes) { return FunctionDSL.functionSignature(new QName(name, Function.BUILTIN_FUNCTION_NS), description, returnType, paramTypes); } diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunEnvironment.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunEnvironment.java index 26c7ccf57df..b2dea59c9ab 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunEnvironment.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunEnvironment.java @@ -21,7 +21,8 @@ */ package org.exist.xquery.functions.fn; -import java.util.Map; +import io.lacuna.bifurcan.IMap; +import io.lacuna.bifurcan.ISet; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.dom.QName; @@ -31,6 +32,7 @@ import org.exist.xquery.FunctionSignature; import org.exist.xquery.XPathException; import org.exist.xquery.XQueryContext; +import org.exist.xquery.functions.AccessUtil; import org.exist.xquery.value.FunctionParameterSequenceType; import org.exist.xquery.value.FunctionReturnSequenceType; import org.exist.xquery.value.Sequence; @@ -41,61 +43,63 @@ public class FunEnvironment extends BasicFunction { - protected static final Logger logger = LogManager.getLogger(FunEnvironment.class); + private static final Logger LOGGER = LogManager.getLogger(FunEnvironment.class); public final static FunctionSignature[] signature = { - new FunctionSignature( - new QName("available-environment-variables", Function.BUILTIN_FUNCTION_NS), - "Returns a list of environment variable names.", - null, - new FunctionReturnSequenceType(Type.STRING, Cardinality.ZERO_OR_MORE, - "Returns a sequence of strings, being the names of the environment variables. User must be DBA.") - ), - new FunctionSignature( - new QName("environment-variable", Function.BUILTIN_FUNCTION_NS), - "Returns the value of a system environment variable, if it exists.", - new SequenceType[] { - new FunctionParameterSequenceType("name", Type.STRING, - Cardinality.EXACTLY_ONE, "Name of environment variable.") - }, - new FunctionReturnSequenceType(Type.STRING, Cardinality.ZERO_OR_ONE, "Corrensponding value of the environment variable, " - + "if there is no environment variable with a matching name, the function returns the empty sequence. User must be DBA.") - ) + new FunctionSignature( + new QName("available-environment-variables", Function.BUILTIN_FUNCTION_NS), + "Returns a list of environment variable names.", + null, + new FunctionReturnSequenceType(Type.STRING, Cardinality.ZERO_OR_MORE, + "Returns a sequence of strings, being the names of the environment variables. " + + "Only the Environment Variables that the calling user has been granted access to are returned (see conf.xml).") + ), + new FunctionSignature( + new QName("environment-variable", Function.BUILTIN_FUNCTION_NS), + "Returns the value of a system environment variable, if it exists.", + new SequenceType[] { + new FunctionParameterSequenceType("name", Type.STRING, + Cardinality.EXACTLY_ONE, "Name of environment variable.") + }, + new FunctionReturnSequenceType(Type.STRING, Cardinality.ZERO_OR_ONE, "Corresponding value of the environment variable, " + + "if there is no environment variable with a matching name, the function returns the empty sequence. " + + "User must have been granted access to the Environment Variable (see conf.xml).") + ) }; - public FunEnvironment(XQueryContext context, FunctionSignature signature) { + public FunEnvironment(final XQueryContext context, final FunctionSignature signature) { super(context, signature); } @Override - public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException { - - if (!context.getSubject().hasDbaRole()) { - final String txt = "Permission denied, calling user '" + context.getSubject().getName() + "' must be a DBA to call this function."; - logger.error(txt); - return Sequence.EMPTY_SEQUENCE; - } + public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException { + final FnModule fnModule = (FnModule) getParentModule(); + final IMap> environmentVariableAccessGroups = fnModule.getEnvironmentVariableAccessGroups(); + final IMap> environmentVariableAccessUsers = fnModule.getEnvironmentVariableAccessUsers(); if (isCalledAs("available-environment-variables")) { final Sequence result = new ValueSequence(); - final Map env = context.getEnvironmentVariables(); - for (final String key : env.keySet()) { - result.add(new StringValue(this, key)); + final IMap environmentVariables = context.getEnvironmentVariables(); + for (final String environmentVariableName : environmentVariables.keys()) { + if (AccessUtil.isAllowedAccess(context.getEffectiveUser(), environmentVariableAccessGroups, environmentVariableAccessUsers, environmentVariableName)) { + result.add(new StringValue(this, environmentVariableName)); + } } return result; } else { - if (args[0].isEmpty()) { + final String environmentVariableName = args[0].itemAt(0).getStringValue(); + if (!AccessUtil.isAllowedAccess(context.getEffectiveUser(), environmentVariableAccessGroups, environmentVariableAccessUsers, environmentVariableName)) { + final String txt = "Permission denied, calling user '" + context.getSubject().getName() + "' must be granted access to the Environment Variable: " + environmentVariableName + "."; + LOGGER.error(txt); return Sequence.EMPTY_SEQUENCE; } - final String parameter = args[0].itemAt(0).getStringValue(); - - final String value = context.getEnvironmentVariables().get(parameter); + final String value = context.getEnvironmentVariables().get(environmentVariableName, null); if (value == null) { return Sequence.EMPTY_SEQUENCE; } diff --git a/exist-core/src/main/java/org/exist/xquery/functions/system/GetBuild.java b/exist-core/src/main/java/org/exist/xquery/functions/system/GetBuild.java index 07e0ae9885c..f4718ddc649 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/system/GetBuild.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/system/GetBuild.java @@ -21,7 +21,7 @@ */ package org.exist.xquery.functions.system; -import org.exist.SystemProperties; +import org.exist.ExistSystemProperties; import org.exist.dom.QName; import org.exist.xquery.BasicFunction; import org.exist.xquery.Cardinality; @@ -51,11 +51,8 @@ public GetBuild(XQueryContext context) { super(context, signature); } - /* (non-Javadoc) - * @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[], org.exist.xquery.value.Sequence) - */ @Override public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException { - return new StringValue(this, SystemProperties.getInstance().getSystemProperty("product-build", "unknown build")); + return new StringValue(this, ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_BUILD, "unknown build")); } } diff --git a/exist-core/src/main/java/org/exist/xquery/functions/system/GetProductName.java b/exist-core/src/main/java/org/exist/xquery/functions/system/GetProductName.java index 83f62eedc84..720f33aeb8b 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/system/GetProductName.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/system/GetProductName.java @@ -21,7 +21,7 @@ */ package org.exist.xquery.functions.system; -import org.exist.SystemProperties; +import org.exist.ExistSystemProperties; import org.exist.dom.QName; import org.exist.xquery.*; import org.exist.xquery.value.FunctionReturnSequenceType; @@ -49,6 +49,6 @@ public GetProductName(final XQueryContext context) { @Override public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException { - return new StringValue(this, SystemProperties.getInstance().getSystemProperty("product-name", "eXist")); + return new StringValue(this, ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_NAME, "eXist")); } } diff --git a/exist-core/src/main/java/org/exist/xquery/functions/system/GetRevision.java b/exist-core/src/main/java/org/exist/xquery/functions/system/GetRevision.java index a585ad7fa9f..5c0359a23d7 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/system/GetRevision.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/system/GetRevision.java @@ -21,7 +21,7 @@ */ package org.exist.xquery.functions.system; -import org.exist.SystemProperties; +import org.exist.ExistSystemProperties; import org.exist.dom.QName; import org.exist.xquery.BasicFunction; import org.exist.xquery.Cardinality; @@ -52,11 +52,8 @@ public GetRevision(XQueryContext context) { super(context, signature); } - /* (non-Javadoc) - * @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[], org.exist.xquery.value.Sequence) - */ @Override public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException { - return new StringValue(this, SystemProperties.getInstance().getSystemProperty("git-commit", "unknown Git commit ID")); + return new StringValue(this, ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_GIT_COMMIT, "unknown Git commit ID")); } } diff --git a/exist-core/src/main/java/org/exist/xquery/functions/system/GetVersion.java b/exist-core/src/main/java/org/exist/xquery/functions/system/GetVersion.java index 32f87700f1d..ad693fb5c09 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/system/GetVersion.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/system/GetVersion.java @@ -21,7 +21,7 @@ */ package org.exist.xquery.functions.system; -import org.exist.SystemProperties; +import org.exist.ExistSystemProperties; import org.exist.dom.QName; import org.exist.xquery.BasicFunction; import org.exist.xquery.Cardinality; @@ -51,11 +51,8 @@ public GetVersion(XQueryContext context) { super(context, signature); } - /* (non-Javadoc) - * @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[], org.exist.xquery.value.Sequence) - */ @Override public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException { - return new StringValue(this, SystemProperties.getInstance().getSystemProperty("product-version", "unknown version")); + return new StringValue(this, ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_VERSION, "unknown version")); } } diff --git a/exist-core/src/main/java/org/exist/xquery/functions/util/SystemProperty.java b/exist-core/src/main/java/org/exist/xquery/functions/util/SystemProperty.java index 87653b1317c..8eebf1ab3c5 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/util/SystemProperty.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/util/SystemProperty.java @@ -21,53 +21,104 @@ */ package org.exist.xquery.functions.util; -import org.exist.SystemProperties; -import org.exist.dom.QName; +import io.lacuna.bifurcan.IMap; +import io.lacuna.bifurcan.ISet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.exist.ExistSystemProperties; import org.exist.xquery.BasicFunction; -import org.exist.xquery.Cardinality; import org.exist.xquery.FunctionSignature; import org.exist.xquery.XPathException; import org.exist.xquery.XQueryContext; -import org.exist.xquery.value.FunctionParameterSequenceType; -import org.exist.xquery.value.FunctionReturnSequenceType; +import org.exist.xquery.functions.AccessUtil; import org.exist.xquery.value.Sequence; -import org.exist.xquery.value.SequenceType; import org.exist.xquery.value.StringValue; import org.exist.xquery.value.Type; +import org.exist.xquery.value.ValueSequence; + +import java.util.HashSet; +import java.util.Set; + +import static org.exist.xquery.FunctionDSL.*; +import static org.exist.xquery.functions.util.UtilModule.functionSignature; /** - * Libary function to retrieve the value of a system property. + * Library function to retrieve the value of a system property. + * * @author Wolfgang Meier * @author Loren Cahlander + * @author > systemPropertyAccessGroups = utilModule.getSystemPropertyAccessGroups(); + final IMap> systemPropertyAccessUsers = utilModule.getSystemPropertyAccessUsers(); + + if (isCalledAs(FS_AVAILABLE_SYSTEM_PROPERTIES_NAME)) { + + final Set availableProperties = new HashSet<>(); + availableProperties.addAll(ExistSystemProperties.getInstance().getAvailableExistSystemProperties()); + + // add any Java System Properties that the user has access to + for (final String systemPropertyName : context.getJavaSystemProperties().keys()) { + if (AccessUtil.isAllowedAccess(context.getEffectiveUser(), systemPropertyAccessGroups, systemPropertyAccessUsers, systemPropertyName)) { + availableProperties.add(systemPropertyName); + } + } + + final ValueSequence result = new ValueSequence(availableProperties.size()); + for (final String availableProperty : availableProperties) { + result.add(new StringValue(this, availableProperty)); + } + + return result; + + } else { + final String systemPropertyName = args[0].getStringValue(); + + // always allow access to all eXist-db System Properties + String value = ExistSystemProperties.getInstance().getExistSystemProperty(systemPropertyName, null); + if (value == null) { + + // check for a Java System Property that the user has access to + if (!AccessUtil.isAllowedAccess(context.getEffectiveUser(), systemPropertyAccessGroups, systemPropertyAccessUsers, systemPropertyName)) { + final String txt = "Permission denied, calling user '" + context.getSubject().getName() + "' must be granted access to the Java System Property: " + systemPropertyName + "."; + LOGGER.error(txt); + return Sequence.EMPTY_SEQUENCE; + } + + value = context.getJavaSystemProperties().get(systemPropertyName, null); + } - final String key = args[0].getStringValue(); - String value = SystemProperties.getInstance().getSystemProperty(key, null); - if(value == null) { - value = System.getProperty(key); + return value == null ? Sequence.EMPTY_SEQUENCE : new StringValue(this, value); } - return value == null ? Sequence.EMPTY_SEQUENCE : new StringValue(this, value); } -} \ No newline at end of file +} diff --git a/exist-core/src/main/java/org/exist/xquery/functions/util/UtilModule.java b/exist-core/src/main/java/org/exist/xquery/functions/util/UtilModule.java index 7400257f01e..67146d6cbfb 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/util/UtilModule.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/util/UtilModule.java @@ -24,9 +24,15 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; +import com.evolvedbinary.j8fu.tuple.Tuple2; +import io.lacuna.bifurcan.IMap; +import io.lacuna.bifurcan.ISet; import org.exist.dom.QName; +import org.exist.util.PatternFactory; import org.exist.xquery.*; +import org.exist.xquery.functions.AccessUtil; import org.exist.xquery.value.FunctionParameterSequenceType; import org.exist.xquery.value.FunctionReturnSequenceType; @@ -37,7 +43,7 @@ * @author Wolfgang Meier * @author ljo * @author Andrzej Taramina - * @author Adam retter + * @author > systemPropertyAccessGroups = null; + private IMap> systemPropertyAccessUsers = null; + public UtilModule(final Map> parameters) throws XPathException { super(functions, parameters, true); @@ -217,6 +229,34 @@ public void reset(final XQueryContext xqueryContext, final boolean keepGlobals) super.reset(xqueryContext, keepGlobals); } + /** + * Get the system property names and groups that are allowed to access them. + * + * @return a map where the key is the system property name, and the value is a set of group names. + */ + IMap> getSystemPropertyAccessGroups() { + if (systemPropertyAccessGroups == null) { + final Tuple2>, IMap>> accessRules = AccessUtil.parseAccessParameters(PTN_SYSTEM_PROPERTY_ACCESS, getParameters()); + this.systemPropertyAccessGroups = accessRules._1; + this.systemPropertyAccessUsers = accessRules._2; + } + return systemPropertyAccessGroups; + } + + /** + * Get the system property names and users that are allowed to access them. + * + * @return a map where the key is the system property name, and the value is a set of usernames. + */ + IMap> getSystemPropertyAccessUsers() { + if (systemPropertyAccessUsers == null) { + final Tuple2>, IMap>> accessRules = AccessUtil.parseAccessParameters(PTN_SYSTEM_PROPERTY_ACCESS, getParameters()); + this.systemPropertyAccessGroups = accessRules._1; + this.systemPropertyAccessUsers = accessRules._2; + } + return systemPropertyAccessUsers; + } + static FunctionSignature functionSignature(final String name, final String description, final FunctionReturnSequenceType returnType, final FunctionParameterSequenceType... paramTypes) { return FunctionDSL.functionSignature(new QName(name, NAMESPACE_URI, PREFIX), description, returnType, paramTypes); } diff --git a/exist-core/src/test/java/org/exist/xquery/functions/fn/FunEnvironmentTest.java b/exist-core/src/test/java/org/exist/xquery/functions/fn/FunEnvironmentTest.java new file mode 100644 index 00000000000..b7ee9e88e87 --- /dev/null +++ b/exist-core/src/test/java/org/exist/xquery/functions/fn/FunEnvironmentTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2014, Evolved Binary Ltd + * + * This file was originally ported from FusionDB to eXist-db by + * Evolved Binary, for the benefit of the eXist-db Open Source community. + * Only the ported code as it appears in this file, at the time that + * it was contributed to eXist-db, was re-licensed under The GNU + * Lesser General Public License v2.1 only for use in eXist-db. + * + * This license grant applies only to a snapshot of the code as it + * appeared when ported, it does not offer or infer any rights to either + * updates of this source code or access to the original source code. + * + * The GNU Lesser General Public License v2.1 only license follows. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2014, Evolved Binary Ltd + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.xquery.functions.fn; + +import org.exist.EXistException; +import org.exist.security.PermissionDeniedException; +import org.exist.storage.BrokerPool; +import org.exist.storage.DBBroker; +import org.exist.test.ExistEmbeddedServer; +import org.exist.util.DatabaseConfigurationException; +import org.exist.xquery.XPathException; +import org.exist.xquery.XQuery; +import org.exist.xquery.value.Sequence; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Optional; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(Parameterized.class) +public class FunEnvironmentTest { + + @Parameterized.Parameters(name = "{0}") + public static java.util.Collection data() { + return Arrays.asList(new Object[][] { + { "non-secure", null, false }, + { "secure", "conf-env-vars-admins-only.xml", true } + }); + } + + @Parameterized.Parameter(value = 0) + public String testTypeName; + + @Parameterized.Parameter(value = 1) + public String confFileName; + + @Parameterized.Parameter(value = 2) + public boolean shouldReturnEmptySequence; + + private ExistEmbeddedServer existEmbeddedServer = null; + + @Before + public void setup() throws URISyntaxException, DatabaseConfigurationException, EXistException, IOException { + if (confFileName == null) { + existEmbeddedServer = new ExistEmbeddedServer(true, true); + } else { + final Path confFile = Paths.get(getClass().getResource(confFileName).toURI()); + existEmbeddedServer = new ExistEmbeddedServer(null, confFile, null, true, true); + } + existEmbeddedServer.startDb(); + } + + @After + public void teardown() { + if (existEmbeddedServer != null) { + existEmbeddedServer.stopDb(); + } + existEmbeddedServer = null; + } + + @Test + public void availableEnvironmentVariables() throws EXistException, XPathException, PermissionDeniedException { + final BrokerPool pool = existEmbeddedServer.getBrokerPool(); + final XQuery xqueryService = pool.getXQueryService(); + try (final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) { + + final String query = "fn:available-environment-variables()"; + final Sequence result = xqueryService.execute(broker, query, null); + + if (shouldReturnEmptySequence) { + assertTrue(result.isEmpty()); + } else { + assertFalse(result.isEmpty()); + } + } + } + + @Test + public void environmentVariable() throws EXistException, XPathException, PermissionDeniedException { + final BrokerPool pool = existEmbeddedServer.getBrokerPool(); + final XQuery xqueryService = pool.getXQueryService(); + try (final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) { + + final String query; + if (isWindows()) { + query = "fn:environment-variable('Path')"; + } else { + query = "fn:environment-variable('PATH')"; + } + + final Sequence result = xqueryService.execute(broker, query, null); + + if (shouldReturnEmptySequence) { + assertTrue(result.isEmpty()); + } else { + assertFalse(result.isEmpty()); + } + } + } + + private static boolean isWindows() { + return System.getProperty("os.name").toLowerCase().startsWith("win"); + } +} diff --git a/exist-core/src/test/java/org/exist/xquery/functions/util/SystemPropertyTest.java b/exist-core/src/test/java/org/exist/xquery/functions/util/SystemPropertyTest.java new file mode 100644 index 00000000000..950f51bef46 --- /dev/null +++ b/exist-core/src/test/java/org/exist/xquery/functions/util/SystemPropertyTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2014, Evolved Binary Ltd + * + * This file was originally ported from FusionDB to eXist-db by + * Evolved Binary, for the benefit of the eXist-db Open Source community. + * Only the ported code as it appears in this file, at the time that + * it was contributed to eXist-db, was re-licensed under The GNU + * Lesser General Public License v2.1 only for use in eXist-db. + * + * This license grant applies only to a snapshot of the code as it + * appeared when ported, it does not offer or infer any rights to either + * updates of this source code or access to the original source code. + * + * The GNU Lesser General Public License v2.1 only license follows. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2014, Evolved Binary Ltd + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.xquery.functions.util; + +import org.exist.EXistException; +import org.exist.ExistSystemProperties; +import org.exist.security.PermissionDeniedException; +import org.exist.storage.BrokerPool; +import org.exist.storage.DBBroker; +import org.exist.test.ExistEmbeddedServer; +import org.exist.util.DatabaseConfigurationException; +import org.exist.xquery.XPathException; +import org.exist.xquery.XQuery; +import org.exist.xquery.value.Sequence; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import static org.junit.Assert.*; + +@RunWith(Parameterized.class) +public class SystemPropertyTest { + + @Parameterized.Parameters(name = "{0}") + public static java.util.Collection data() { + return Arrays.asList(new Object[][] { + { "non-secure", null, false }, + { "secure", "conf-sys-props-admins-only.xml", true } + }); + } + + @Parameterized.Parameter(value = 0) + public String testTypeName; + + @Parameterized.Parameter(value = 1) + public String confFileName; + + @Parameterized.Parameter(value = 2) + public boolean shouldReturnEmptySequence; + + private ExistEmbeddedServer existEmbeddedServer = null; + + @Before + public void setup() throws URISyntaxException, DatabaseConfigurationException, EXistException, IOException { + if (confFileName == null) { + existEmbeddedServer = new ExistEmbeddedServer(true, true); + } else { + final Path confFile = Paths.get(getClass().getResource(confFileName).toURI()); + existEmbeddedServer = new ExistEmbeddedServer(null, confFile, null, true, true); + } + existEmbeddedServer.startDb(); + } + + @After + public void teardown() { + if (existEmbeddedServer != null) { + existEmbeddedServer.stopDb(); + } + existEmbeddedServer = null; + } + + @Test + public void availableSystemProperties() throws EXistException, XPathException, PermissionDeniedException { + final BrokerPool pool = existEmbeddedServer.getBrokerPool(); + final XQuery xqueryService = pool.getXQueryService(); + try (final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) { + + final String query = "util:available-system-properties()"; + final Sequence result = xqueryService.execute(broker, query, null); + assertFalse(result.isEmpty()); + + final Set set = new HashSet<>(result.getItemCount()); + for (int i = 0; i < result.getItemCount(); i++) { + set.add(result.itemAt(i).getStringValue()); + } + + assertTrue(set.contains(ExistSystemProperties.PROP_PRODUCT_VERSION)); + + if (shouldReturnEmptySequence) { + assertFalse(set.contains("os.name")); + } else { + assertTrue(set.contains("os.name")); + } + } + } + + @Test + public void systemProperty() throws EXistException, XPathException, PermissionDeniedException { + final BrokerPool pool = existEmbeddedServer.getBrokerPool(); + final XQuery xqueryService = pool.getXQueryService(); + try (final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) { + + String query = "util:system-property('" + ExistSystemProperties.PROP_PRODUCT_NAME + "')"; + Sequence result = xqueryService.execute(broker, query, null); + assertEquals(1, result.getItemCount()); + + query = "util:system-property('os.name')"; + result = xqueryService.execute(broker, query, null); + + if (shouldReturnEmptySequence) { + assertTrue(result.isEmpty()); + } else { + assertFalse(result.isEmpty()); + } + } + } +} diff --git a/exist-core/src/test/resources/org/exist/xquery/functions/fn/conf-env-vars-admins-only.xml b/exist-core/src/test/resources/org/exist/xquery/functions/fn/conf-env-vars-admins-only.xml new file mode 100644 index 00000000000..dad17886ad0 --- /dev/null +++ b/exist-core/src/test/resources/org/exist/xquery/functions/fn/conf-env-vars-admins-only.xml @@ -0,0 +1,828 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exist-core/src/test/resources/org/exist/xquery/functions/util/conf-sys-props-admins-only.xml b/exist-core/src/test/resources/org/exist/xquery/functions/util/conf-sys-props-admins-only.xml new file mode 100644 index 00000000000..1c4fe7ca700 --- /dev/null +++ b/exist-core/src/test/resources/org/exist/xquery/functions/util/conf-sys-props-admins-only.xml @@ -0,0 +1,835 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exist-distribution/src/main/config/conf.xml b/exist-distribution/src/main/config/conf.xml index 06122caa76f..71c88173289 100644 --- a/exist-distribution/src/main/config/conf.xml +++ b/exist-distribution/src/main/config/conf.xml @@ -935,6 +935,25 @@ + + + + + + @@ -953,6 +972,22 @@ + + + diff --git a/extensions/modules/expathrepo/src/main/java/org/exist/xquery/modules/expathrepo/Deploy.java b/extensions/modules/expathrepo/src/main/java/org/exist/xquery/modules/expathrepo/Deploy.java index 367e6f30741..03f89c95b8b 100644 --- a/extensions/modules/expathrepo/src/main/java/org/exist/xquery/modules/expathrepo/Deploy.java +++ b/extensions/modules/expathrepo/src/main/java/org/exist/xquery/modules/expathrepo/Deploy.java @@ -31,7 +31,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.exist.SystemProperties; +import org.exist.ExistSystemProperties; import org.exist.dom.persistent.BinaryDocument; import org.exist.dom.persistent.DocumentImpl; import org.exist.dom.QName; @@ -280,7 +280,7 @@ public RepoPackageLoader(final String repoURL) { @Override public XarSource load(final String name, final Version version) throws IOException { String pkgURL = repoURL + "?name=" + URLEncoder.encode(name, "UTF-8") + - "&processor=" + SystemProperties.getInstance().getSystemProperty("product-version", "2.2.0"); + "&processor=" + ExistSystemProperties.getInstance().getExistSystemProperty(ExistSystemProperties.PROP_PRODUCT_VERSION, "2.2.0"); if (version != null) { if (version.getMin() != null) { pkgURL += "&semver-min=" + version.getMin();