From: Mathieu Baudier Date: Wed, 10 Nov 2021 06:11:38 +0000 (+0100) Subject: Merge remote-tracking branch 'origin/v2.x' X-Git-Tag: argeo-commons-2.3.2~29 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=043d226a5504a212eb5673c3ed4441c1167724d7;hp=cdc0e2725f4ec174309c3fdcd060f80753210c00 Merge remote-tracking branch 'origin/v2.x' --- diff --git a/.gitignore b/.gitignore index b83d22266..bcf25b2ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target/ +*/generated diff --git a/cnf/build.bnd b/cnf/build.bnd new file mode 100644 index 000000000..150e1d46a --- /dev/null +++ b/cnf/build.bnd @@ -0,0 +1,18 @@ +category: org.argeo.commons +version: 2.1.104 +buildId: S +#buildId: r${tstamp} + +Bundle-Version: 2.1.104.S +Bundle-RequiredExecutionEnvironment: JavaSE-11 + +Private-Package: org.argeo.*.internal.* +Export-Package: !org.argeo.*.internal.*; org.argeo.* +SLC-Category: ${category} +#SLC-Build-Timestamp: ${tstamp} +-savemanifest = META-INF/MANIFEST.MF +-removeheaders = Bnd-LastModified,Build-Jdk,Built-By,Tool,Created-By +-groupId = ${category} +Automatic-Module-Name: ${bsn} + + diff --git a/demo/cms-cluster_0.properties b/demo/cms-cluster_0.properties index c0bb9da2b..d0c3fb2f8 100644 --- a/demo/cms-cluster_0.properties +++ b/demo/cms-cluster_0.properties @@ -13,7 +13,7 @@ org.argeo.cms.e4.rap # Local org.osgi.service.http.port=7070 -argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap:10389/dc=example,dc=com +argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap/dc=example,dc=com argeo.node.repo.type=postgresql_cluster_ds argeo.node.repo.clusterId=03233754-16c3-49a1-8a00-58bf89a65182 argeo.node.repo.dburl=jdbc:postgresql://test-pgsql-ldap/argeo_cluster diff --git a/demo/cms-cluster_1.properties b/demo/cms-cluster_1.properties index cf48eab02..b5e60f85b 100644 --- a/demo/cms-cluster_1.properties +++ b/demo/cms-cluster_1.properties @@ -13,7 +13,7 @@ org.argeo.cms.e4.rap # Local org.osgi.service.http.port=7071 -argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap:10389/dc=example,dc=com +argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap/dc=example,dc=com argeo.node.repo.type=postgresql_cluster_ds argeo.node.repo.clusterId=52463fa3-2917-4814-9ff7-685c41cbc7c7 argeo.node.repo.dburl=jdbc:postgresql://test-pgsql-ldap/argeo_cluster diff --git a/demo/pom.xml b/demo/pom.xml index 40855213e..1716fa853 100644 --- a/demo/pom.xml +++ b/demo/pom.xml @@ -41,7 +41,7 @@ org.argeo.commons - org.argeo.dep.cms.sdk + org.argeo.dep.cms.e4.rap 2.3.1-SNAPSHOT diff --git a/dep/org.argeo.dep.cms.ext/.gitignore b/dep/org.argeo.dep.cms.ext/.gitignore new file mode 100644 index 000000000..e26e09f82 --- /dev/null +++ b/dep/org.argeo.dep.cms.ext/.gitignore @@ -0,0 +1,4 @@ +/target/ +/feature.xml +/modularDistribution.csv +/*-maven.target diff --git a/dep/org.argeo.dep.cms.ext/META-INF/.gitignore b/dep/org.argeo.dep.cms.ext/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/dep/org.argeo.dep.cms.ext/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/dep/org.argeo.dep.cms.ext/bnd.bnd b/dep/org.argeo.dep.cms.ext/bnd.bnd new file mode 100644 index 000000000..e69de29bb diff --git a/dep/org.argeo.dep.cms.ext/build.properties b/dep/org.argeo.dep.cms.ext/build.properties new file mode 100644 index 000000000..edef3d9d1 --- /dev/null +++ b/dep/org.argeo.dep.cms.ext/build.properties @@ -0,0 +1,2 @@ +bin.includes = feature.xml,\ + modularDistribution.csv diff --git a/dep/org.argeo.dep.cms.ext/p2.inf b/dep/org.argeo.dep.cms.ext/p2.inf new file mode 100644 index 000000000..0423aa509 --- /dev/null +++ b/dep/org.argeo.dep.cms.ext/p2.inf @@ -0,0 +1,2 @@ +properties.1.name=org.eclipse.equinox.p2.type.category +properties.1.value=true \ No newline at end of file diff --git a/dep/org.argeo.dep.cms.ext/pom.xml b/dep/org.argeo.dep.cms.ext/pom.xml new file mode 100644 index 000000000..19b5b4875 --- /dev/null +++ b/dep/org.argeo.dep.cms.ext/pom.xml @@ -0,0 +1,146 @@ + + + 4.0.0 + + org.argeo.commons + 2.3.1-SNAPSHOT + dep + .. + + org.argeo.dep.cms.ext + CMS Optional Third Parties + Bulky generic third parties which are not required by the CMS, but necessary for upper layers. + + + + org.argeo.tp.javax + javax.xml.bind + + + + + org.argeo.tp.jackson + com.fasterxml.jackson.core.jackson-core + + + org.argeo.tp.jackson + com.fasterxml.jackson.core.jackson-databind + + + org.argeo.tp.jackson + com.fasterxml.jackson.core.jackson-annotations + + + + + org.argeo.tp.javax + javax.activation + + + org.argeo.tp.javax + javax.mail + + + + + org.argeo.tp.apache.commons + org.apache.commons.math3 + + + org.argeo.tp.apache.commons + org.apache.commons.collections4 + + + org.argeo.tp.apache + org.apache.xml.security + + + org.argeo.tp.apache + org.apache.xmlbeans + + + org.argeo.tp.apache + org.apache.xalan + + + org.argeo.tp.apache + org.apache.xalan.serializer + + + org.argeo.tp.apache + org.apache.xml.resolver + + + org.argeo.tp.apache + org.apache.xerces + + + + + + + rpmbuild-tp + + + + maven-assembly-plugin + + + prepare-source-tp + package + + single + + + + a2-source-tp + + + + + + + org.codehaus.mojo + rpm-maven-plugin + + + rpm-tp + package + + rpm + + + argeo-cms-ext-tp + ${version.argeo-tp} + ${maven.build.timestamp} + + + /usr/share/osgi + root + root + 644 + false + + + ${project.build.directory}/${project.artifactId}-${project.version}-a2-source-tp + + **/*.jar + + + + + + + argeo-cms-node-tp + + + + + + + + + + \ No newline at end of file diff --git a/dep/org.argeo.dep.cms.sdk/pom.xml b/dep/org.argeo.dep.cms.sdk/pom.xml index d752f9850..1029790a2 100644 --- a/dep/org.argeo.dep.cms.sdk/pom.xml +++ b/dep/org.argeo.dep.cms.sdk/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.argeo.commons - 2.3.1-SNAPSHOT + 2.1.104-SNAPSHOT dep .. @@ -13,8 +13,8 @@ org.argeo.commons - org.argeo.dep.cms.e4.rap - 2.3.1-SNAPSHOT + org.argeo.dep.cms.ui.rap + 2.1.104-SNAPSHOT pom @@ -23,7 +23,7 @@ org.argeo.commons org.argeo.osgi.boot - 2.3.1-SNAPSHOT + 2.1.104-SNAPSHOT test @@ -117,7 +117,6 @@ - argeo-cms-platform-tp diff --git a/dep/pom.xml b/dep/pom.xml index 0d0c750ae..37e847f05 100644 --- a/dep/pom.xml +++ b/dep/pom.xml @@ -15,7 +15,7 @@ org.argeo.dep.cms.node org.argeo.dep.cms.ui.rap org.argeo.dep.cms.e4.rap - org.argeo.dep.cms.sdk + org.argeo.dep.cms.ext diff --git a/dist/argeo-node/base/etc/argeo.d/jvm.args.debug b/dist/argeo-node/base/etc/argeo.d/jvm.args.debug new file mode 100644 index 000000000..4e6b1dc59 --- /dev/null +++ b/dist/argeo-node/base/etc/argeo.d/jvm.args.debug @@ -0,0 +1 @@ +-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:8000 \ No newline at end of file diff --git a/dist/argeo-node/pom.xml b/dist/argeo-node/pom.xml index b0f9aa268..f37d15d1d 100644 --- a/dist/argeo-node/pom.xml +++ b/dist/argeo-node/pom.xml @@ -28,11 +28,6 @@ org.argeo.dep.cms.e4.rap 2.3.1-SNAPSHOT - - org.argeo.commons - org.argeo.dep.cms.sdk - 2.3.1-SNAPSHOT - org.argeo.commons osgi-boot diff --git a/dist/argeo-node/rpm/usr/lib/systemd/system/argeo@.service b/dist/argeo-node/rpm/usr/lib/systemd/system/argeo@.service index b310048e1..c631825a2 100644 --- a/dist/argeo-node/rpm/usr/lib/systemd/system/argeo@.service +++ b/dist/argeo-node/rpm/usr/lib/systemd/system/argeo@.service @@ -21,10 +21,10 @@ ExecStart=/usr/lib/jvm/jre-11/bin/java \ -Dosgi.noShutdown=true \ -Dorg.eclipse.equinox.http.jetty.autostart=false \ -Dosgi.bundles=org.argeo.osgi.boot@start \ -@/usr/share/osgi/boot/framework.args \ @/usr/share/argeo/jvm.args \ @/etc/argeo.d/jvm.args \ @/etc/argeo.d/%I/jvm.args \ +@/usr/share/osgi/boot/framework.args \ -configuration /var/lib/argeo.d/%I/state \ -data /var/lib/argeo.d/%I/data # Exit codes of the JVM when SIGTERM or SIGINT have been caught: diff --git a/org.argeo.api/.classpath b/org.argeo.api/.classpath index eca7bdba8..e801ebfb4 100644 --- a/org.argeo.api/.classpath +++ b/org.argeo.api/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.api/src/org/argeo/api/PublishNamespace.java b/org.argeo.api/src/org/argeo/api/PublishNamespace.java new file mode 100644 index 000000000..ff9575486 --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/PublishNamespace.java @@ -0,0 +1,16 @@ +package org.argeo.api; + +import org.osgi.resource.Namespace; + +/** Namespace defining which resources can be published. Typically use to expose icon of scripts to the web. */ +public class PublishNamespace extends Namespace { + + public static final String CMS_PUBLISH_NAMESPACE = "cms.publish"; + public static final String PKG = "pkg"; + public static final String FILE = "file"; + + private PublishNamespace() { + // empty + } + +} diff --git a/org.argeo.cms.e4.rap/.classpath b/org.argeo.cms.e4.rap/.classpath index eca7bdba8..e801ebfb4 100644 --- a/org.argeo.cms.e4.rap/.classpath +++ b/org.argeo.cms.e4.rap/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.cms.e4/.classpath b/org.argeo.cms.e4/.classpath index eca7bdba8..e801ebfb4 100644 --- a/org.argeo.cms.e4/.classpath +++ b/org.argeo.cms.e4/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java index 361e8a0d2..c1bd3ad98 100644 --- a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java +++ b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java @@ -3,6 +3,7 @@ package org.argeo.cms.web; import java.util.Dictionary; import java.util.HashMap; import java.util.Map; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -13,12 +14,13 @@ import org.argeo.cms.ui.CmsView; import org.argeo.util.LangUtils; import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.application.Application; +import org.eclipse.rap.rwt.application.Application.OperationMode; import org.eclipse.rap.rwt.application.ApplicationConfiguration; import org.eclipse.rap.rwt.application.ExceptionHandler; -import org.eclipse.rap.rwt.application.Application.OperationMode; import org.eclipse.rap.rwt.client.WebClient; import org.eclipse.swt.widgets.Display; import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; import org.osgi.framework.ServiceRegistration; import org.osgi.service.event.EventAdmin; @@ -28,6 +30,7 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm private BundleContext bundleContext; private CmsApp cmsApp; + private String cmsAppId; private EventAdmin eventAdmin; private ServiceRegistration rwtAppReg; @@ -35,16 +38,22 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm private final static String CONTEXT_NAME = "contextName"; private String contextName; + private final static String FAVICON_PNG = "favicon.png"; + public void init(BundleContext bundleContext, Map properties) { this.bundleContext = bundleContext; contextName = properties.get(CONTEXT_NAME); - if (cmsApp != null) - themingUpdated(); + if (cmsApp != null) { + if (cmsApp.allThemesAvailable()) + publishWebApp(); + } } public void destroy(BundleContext bundleContext, Map properties) { - if (cmsApp != null) + if (cmsApp != null) { cmsApp.removeCmsAppListener(this); + cmsApp = null; + } } @Override @@ -84,6 +93,11 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm if (theme != null) { properties.put(WebClient.THEME_ID, theme.getThemeId()); properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders()); + properties.put(WebClient.BODY_HTML, theme.getBodyHtml()); + Set imagePaths = theme.getImagesPaths(); + if (imagePaths.contains(FAVICON_PNG)) { + properties.put(WebClient.FAVICON, FAVICON_PNG); + } } else { properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID); } @@ -96,8 +110,8 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm if (log.isDebugEnabled()) log.info("Added web entry point " + (contextName != null ? "/" + contextName : "") + entryPointName); } - if (log.isDebugEnabled()) - log.debug("Published CMS web app /" + (contextName != null ? contextName : "")); +// if (log.isDebugEnabled()) +// log.debug("Published CMS web app /" + (contextName != null ? contextName : "")); } CmsApp getCmsApp() { @@ -110,10 +124,17 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm public void setCmsApp(CmsApp cmsApp, Map properties) { this.cmsApp = cmsApp; + this.cmsAppId = properties.get(Constants.SERVICE_PID); this.cmsApp.addCmsAppListener(this); } public void unsetCmsApp(CmsApp cmsApp, Map properties) { + String cmsAppId = properties.get(Constants.SERVICE_PID); + if (!cmsAppId.equals(this.cmsAppId)) + return; + if (this.cmsApp != null) { + this.cmsApp.removeCmsAppListener(this); + } if (rwtAppReg != null) rwtAppReg.unregister(); this.cmsApp = null; @@ -121,6 +142,11 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm @Override public void themingUpdated() { + if (cmsApp != null && cmsApp.allThemesAvailable()) + publishWebApp(); + } + + protected void publishWebApp() { Dictionary regProps = LangUtils.dict(CONTEXT_NAME, contextName); if (rwtAppReg != null) rwtAppReg.unregister(); diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java index 288069bd9..b1691cb05 100644 --- a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java +++ b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java @@ -114,8 +114,8 @@ public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationL Locale rwtLocale = RWT.getUISession().getLocale(); LocaleUtils.setThreadLocale(rwtLocale); } + parent.setData(CmsApp.UI_NAME_PROPERTY, uiName); ui = cmsWebApp.getCmsApp().initUi(parent); - ui.setData(CmsApp.UI_NAME_PROPERTY, uiName); ui.setLayoutData(CmsUiUtils.fillAll()); // we need ui to be set before refresh so that CmsView can store UI context data // in it. diff --git a/org.argeo.cms.ui.theme/.classpath b/org.argeo.cms.ui.theme/.classpath index eca7bdba8..e801ebfb4 100644 --- a/org.argeo.cms.ui.theme/.classpath +++ b/org.argeo.cms.ui.theme/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.cms.ui/.classpath b/org.argeo.cms.ui/.classpath index eca7bdba8..e801ebfb4 100644 --- a/org.argeo.cms.ui/.classpath +++ b/org.argeo.cms.ui/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java index 2ecc65841..77cd98383 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java @@ -25,10 +25,13 @@ public abstract class AbstractCmsApp implements CmsApp { String themeId = getThemeId(uiName); if (themeId == null) return null; + if (!themes.containsKey(themeId)) + throw new IllegalArgumentException("Theme " + themeId + " not found."); return themes.get(themeId); } - protected boolean allThemesAvailable() { + @Override + public boolean allThemesAvailable() { boolean themeMissing = false; uiNames: for (String uiName : getUiNames()) { String themeId = getThemeId(uiName); diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java index 3e445ce7b..bd7b00334 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java @@ -25,6 +25,8 @@ public interface CmsApp { CmsTheme getTheme(String uiName); + boolean allThemesAvailable(); + void addCmsAppListener(CmsAppListener listener); void removeCmsAppListener(CmsAppListener listener); diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsStyles.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsStyles.java index 506e971cc..678a49717 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsStyles.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsStyles.java @@ -1,6 +1,7 @@ package org.argeo.cms.ui; /** Styles references in the CSS. */ +@Deprecated public interface CmsStyles { // General public final static String CMS_SHELL = "cms_shell"; diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java index e5089e35f..c93991b27 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java @@ -37,6 +37,9 @@ public interface CmsTheme { /** Tags to be added to the header section of the HTML page. */ String getHtmlHeaders(); + /** The HTML body to use. */ + String getBodyHtml(); + /** The image registered at this path, or null if not found. */ Image getImage(String path); diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java index 5ec931e71..c3fd7960d 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java @@ -12,7 +12,7 @@ import org.eclipse.swt.widgets.Display; /** Centralises some generic {@link CmsTheme} patterns. */ public abstract class AbstractCmsTheme implements CmsTheme { - private Map imageCache = new HashMap<>(); + private Map imageCache = new HashMap<>(); private Map> iconPaths = new HashMap<>(); @@ -24,13 +24,14 @@ public abstract class AbstractCmsTheme implements CmsTheme { if (in == null) return null; ImageData imageData = new ImageData(in); - Image image = new Image(Display.getDefault(), imageData); - imageCache.put(path, image); + imageCache.put(path, imageData); } catch (IOException e) { throw new IllegalStateException(e); } } - return imageCache.get(path); + ImageData imageData = imageCache.get(path); + Image image = new Image(Display.getCurrent(), imageData); + return image; } @Override diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java index 3b2669e02..6b997e6a3 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; @@ -15,6 +16,7 @@ import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; +import org.apache.commons.io.IOUtils; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -33,6 +35,10 @@ public class BundleCmsTheme extends AbstractCmsTheme { public final static String CMS_THEME_PROPERTY = "argeo.cms.theme"; public final static String CMS_THEME_BUNDLE_PROPERTY = "argeo.cms.theme.bundle"; + private final static String HEADER_CSS = "header.css"; + private final static String FONTS_TXT = "fonts.txt"; + private final static String BODY_HTML = "body.html"; + // private final static Log log = LogFactory.getLog(BundleCmsTheme.class); private String themeId; @@ -45,6 +51,8 @@ public class BundleCmsTheme extends AbstractCmsTheme { private String headerCss; private List fonts = new ArrayList<>(); + private String bodyHtml=""; + private String basePath; private String styleCssPath; // private String webCssPath; @@ -103,21 +111,30 @@ public class BundleCmsTheme extends AbstractCmsTheme { addFonts("*.woff2"); // fonts - URL fontsUrl = themeBundle.getEntry(basePath + "fonts.txt"); + URL fontsUrl = themeBundle.getEntry(basePath + FONTS_TXT); if (fontsUrl != null) { loadFontsUrl(fontsUrl); } // common CSS header (plain CSS) - URL headerCssUrl = themeBundle.getEntry(basePath + "header.css"); + URL headerCssUrl = themeBundle.getEntry(basePath + HEADER_CSS); if (headerCssUrl != null) { + // added to plain Web CSS + webCssPaths.add(basePath + HEADER_CSS); + // and it will also be used by RAP: try (BufferedReader buffer = new BufferedReader(new InputStreamReader(headerCssUrl.openStream(), UTF_8))) { headerCss = buffer.lines().collect(Collectors.joining("\n")); } catch (IOException e) { throw new IllegalArgumentException("Cannot read " + headerCssUrl, e); } } - } + + // body + URL bodyUrl = themeBundle.getEntry(basePath + BODY_HTML); + if (bodyUrl != null) { + loadBodyHtml(bodyUrl); + } +} public String getHtmlHeaders() { StringBuilder sb = new StringBuilder(); @@ -136,6 +153,13 @@ public class BundleCmsTheme extends AbstractCmsTheme { else return sb.toString(); } + + + + @Override + public String getBodyHtml() { + return bodyHtml; + } Set addCss(Bundle themeBundle, String path) { Set paths = new TreeSet<>(); @@ -182,6 +206,14 @@ public class BundleCmsTheme extends AbstractCmsTheme { } } + void loadBodyHtml(URL url) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) { + bodyHtml= IOUtils.toString(url,StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load URL " + url, e); + } + } + void addImages(String pattern) { Enumeration themeResources = themeBundle.findEntries(basePath, pattern, true); if (themeResources == null) diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java index b3c4a8a57..4cad1de60 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java @@ -1,5 +1,6 @@ package org.argeo.cms.ui.util; +import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; @@ -7,14 +8,12 @@ import java.net.URL; import javax.jcr.Node; import javax.jcr.RepositoryException; -import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.api.NodeUtils; -import org.argeo.cms.CmsException; import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.ui.CmsStyles; import org.argeo.cms.ui.CmsUiProvider; +import org.argeo.jcr.JcrException; import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.service.ResourceManager; import org.eclipse.swt.SWT; @@ -32,9 +31,10 @@ public class CmsLink implements CmsUiProvider { private BundleContext bundleContext; private String label; - private String custom; + private String style; private String target; private String image; + private boolean openNew = false; private MouseListener mouseListener; private int horizontalAlignment = SWT.CENTER; @@ -52,14 +52,18 @@ public class CmsLink implements CmsUiProvider { } public CmsLink(String label, String target) { - this(label, target, null); + this(label, target, (String) null); } - public CmsLink(String label, String target, String custom) { + public CmsLink(String label, String target, CmsStyle style) { + this(label, target, style != null ? style.style() : null); + } + + public CmsLink(String label, String target, String style) { super(); this.label = label; this.target = target; - this.custom = custom; + this.style = style; init(); } @@ -89,7 +93,7 @@ public class CmsLink implements CmsUiProvider { comp.setLayout(CmsUiUtils.noSpaceGridLayout()); Label link = new Label(comp, SWT.NONE); - link.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); + CmsUiUtils.markup(link); GridData layoutData = new GridData(horizontalAlignment, verticalAlignment, false, false); if (image != null) { if (imageHeight != null) @@ -100,13 +104,8 @@ public class CmsLink implements CmsUiProvider { } link.setLayoutData(layoutData); - if (custom != null) { - comp.setData(RWT.CUSTOM_VARIANT, custom); - link.setData(RWT.CUSTOM_VARIANT, custom); - } else { - comp.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_LINK); - link.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_LINK); - } + CmsUiUtils.style(comp, style != null ? style : getDefaultStyle()); + CmsUiUtils.style(link, style != null ? style : getDefaultStyle()); // label StringBuilder labelText = new StringBuilder(); @@ -118,16 +117,19 @@ public class CmsLink implements CmsUiProvider { String homePath = homeNode.getPath(); labelText.append("/#" + homePath); } catch (RepositoryException e) { - throw new CmsException("Cannot get home path", e); + throw new JcrException("Cannot get home path", e); } } else { labelText.append(loggedInTarget); } labelText.append("\">"); } else if (target != null) { - labelText.append(""); + labelText.append(""); } if (image != null) { registerImageIfNeeded(); @@ -162,17 +164,12 @@ public class CmsLink implements CmsUiProvider { ResourceManager resourceManager = RWT.getResourceManager(); if (!resourceManager.isRegistered(image)) { URL res = getImageUrl(); - InputStream inputStream = null; - try { - IOUtils.closeQuietly(inputStream); - inputStream = res.openStream(); + try (InputStream inputStream = res.openStream()) { resourceManager.register(image, inputStream); if (log.isTraceEnabled()) log.trace("Registered image " + image); - } catch (Exception e) { - throw new CmsException("Cannot load image " + image, e); - } finally { - IOUtils.closeQuietly(inputStream); + } catch (IOException e) { + throw new RuntimeException("Cannot load image " + image, e); } } } @@ -180,16 +177,12 @@ public class CmsLink implements CmsUiProvider { private ImageData loadImage() { URL url = getImageUrl(); ImageData result = null; - InputStream inputStream = null; - try { - inputStream = url.openStream(); + try (InputStream inputStream = url.openStream()) { result = new ImageData(inputStream); if (log.isTraceEnabled()) log.trace("Loaded image " + image); - } catch (Exception e) { - throw new CmsException("Cannot load image " + image, e); - } finally { - IOUtils.closeQuietly(inputStream); + } catch (IOException e) { + throw new RuntimeException("Cannot load image " + image, e); } return result; } @@ -204,7 +197,7 @@ public class CmsLink implements CmsUiProvider { } if (url == null) - throw new CmsException("No image " + image + " available."); + throw new IllegalStateException("No image " + image + " available."); return url; } @@ -217,8 +210,14 @@ public class CmsLink implements CmsUiProvider { this.label = label; } + public void setStyle(String style) { + this.style = style; + } + + /** @deprecated Use {@link #setStyle(String)} instead. */ + @Deprecated public void setCustom(String custom) { - this.custom = custom; + this.style = custom; } public void setTarget(String target) { @@ -255,7 +254,8 @@ public class CmsLink implements CmsUiProvider { } else if ("center".equals(vAlign)) { verticalAlignment = SWT.CENTER; } else { - throw new CmsException("Unsupported vertical allignment " + vAlign + " (must be: top, bottom or center)"); + throw new IllegalArgumentException( + "Unsupported vertical alignment " + vAlign + " (must be: top, bottom or center)"); } } @@ -271,4 +271,11 @@ public class CmsLink implements CmsUiProvider { this.imageHeight = imageHeight; } + public void setOpenNew(boolean openNew) { + this.openNew = openNew; + } + + protected String getDefaultStyle() { + return SimpleStyle.link.name(); + } } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsStyle.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsStyle.java index ddbe485ea..ed2244b8d 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsStyle.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsStyle.java @@ -4,17 +4,19 @@ package org.argeo.cms.ui.util; public interface CmsStyle { String name(); + /** @deprecated use {@link #style()} instead. */ @Deprecated default String toStyleClass() { - return getClassPrefix() + "-" + name(); + return style(); } default String style() { - return getClassPrefix() + "-" + name(); + String classPrefix = getClassPrefix(); + return "".equals(classPrefix) ? name() : classPrefix + "-" + name(); } default String getClassPrefix() { - return "cms"; + return ""; } } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java index 428c910e5..b90c017d9 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java @@ -4,8 +4,11 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import java.util.StringTokenizer; import javax.jcr.Node; import javax.jcr.RepositoryException; @@ -17,6 +20,7 @@ import org.argeo.cms.ui.CmsConstants; import org.argeo.cms.ui.CmsView; import org.argeo.eclipse.ui.Selected; import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; +import org.argeo.jcr.JcrException; import org.argeo.jcr.JcrUtils; import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.service.ResourceManager; @@ -30,6 +34,7 @@ import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowData; +import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; @@ -113,6 +118,25 @@ public class CmsUiUtils implements CmsConstants { return NodeUtils.getDataPath(cn, node); } + /** Clean reserved URL characters for use in HTTP links. */ + public static String getDataPathForUrl(Node node) throws RepositoryException { + return cleanPathForUrl(getDataPath(node)); + } + + /** Clean reserved URL characters for use in HTTP links. */ + public static String cleanPathForUrl(String path) throws RepositoryException { + StringTokenizer st = new StringTokenizer(path, "/"); + StringBuilder sb = new StringBuilder(); + while (st.hasMoreElements()) { + sb.append('/'); + String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8); + encoded = encoded.replace("+", "%20"); + sb.append(encoded); + + } + return sb.toString(); + } + /** @deprecated Use rowData16px() instead. GridData should not be reused. */ @Deprecated public static RowData ROW_DATA_16px = new RowData(16, 16); @@ -128,6 +152,7 @@ public class CmsUiUtils implements CmsConstants { return noSpaceGridLayout(new GridLayout(columns, false)); } + /** @return the same layout, with spaces removed. */ public static GridLayout noSpaceGridLayout(GridLayout layout) { layout.horizontalSpacing = 0; layout.verticalSpacing = 0; @@ -159,6 +184,19 @@ public class CmsUiUtils implements CmsConstants { /* * ROW LAYOUT */ + /** @return the same layout, with margins removed. */ + public static RowLayout noMarginsRowLayout(RowLayout rowLayout) { + rowLayout.marginTop = 0; + rowLayout.marginBottom = 0; + rowLayout.marginLeft = 0; + rowLayout.marginRight = 0; + return rowLayout; + } + + public static RowLayout noMarginsRowLayout(int type) { + return noMarginsRowLayout(new RowLayout(type)); + } + public static RowData rowData16px() { return new RowData(16, 16); } @@ -186,7 +224,9 @@ public class CmsUiUtils implements CmsConstants { return widget;// does nothing EclipseUiSpecificUtils.setStyleData(widget, style); if (widget instanceof Control) { - CmsView.getCmsView((Control) widget).applyStyles(widget); + CmsView cmsView = CmsView.getCmsView((Control) widget); + if (cmsView != null) + cmsView.applyStyles(widget); } return widget; } @@ -202,6 +242,12 @@ public class CmsUiUtils implements CmsConstants { return widget; } + /** Disable markup validation. */ + public static T disableMarkupValidation(T widget) { + EclipseUiSpecificUtils.setMarkupValidationDisabledData(widget); + return widget; + } + /** * Apply markup and set text on {@link Label}, {@link Button}, {@link Text}. * @@ -245,6 +291,8 @@ public class CmsUiUtils implements CmsConstants { /** Dispose all children of a Composite */ public static void clear(Composite composite) { + if (composite.isDisposed()) + return; for (Control child : composite.getChildren()) child.dispose(); } @@ -285,7 +333,13 @@ public class CmsUiUtils implements CmsConstants { } public static String img(String serverBase, Node fileNode, String width, String height) { - String src = (serverBase != null ? serverBase : "") + NodeUtils.getDataPath(fileNode); +// String src = (serverBase != null ? serverBase : "") + NodeUtils.getDataPath(fileNode); + String src; + try { + src = (serverBase != null ? serverBase : "") + getDataPathForUrl(fileNode); + } catch (RepositoryException e) { + throw new JcrException("Cannot get URL data path for " + fileNode, e); + } return imgBuilder(src, width, height).append("/>").toString(); } @@ -345,4 +399,5 @@ public class CmsUiUtils implements CmsConstants { /** Singleton. */ private CmsUiUtils() { } + } diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/DefaultImageManager.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/DefaultImageManager.java index 698ab1b06..6c4a870c4 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/DefaultImageManager.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/DefaultImageManager.java @@ -9,6 +9,8 @@ import static org.argeo.cms.ui.CmsConstants.NO_IMAGE_SIZE; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; @@ -150,7 +152,7 @@ public class DefaultImageManager implements CmsImageManager { /** @return null if not available */ @Override public String getImageUrl(Node node) throws RepositoryException { - return CmsUiUtils.getDataPath(node); + return CmsUiUtils.getDataPathForUrl(node); } protected String getResourceName(Node node) throws RepositoryException { diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java new file mode 100644 index 000000000..4bbd19cf9 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java @@ -0,0 +1,6 @@ +package org.argeo.cms.ui.util; + +/** Simple styles used by the CMS UI utilities. */ +public enum SimpleStyle implements CmsStyle { + link; +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java index 60db89978..e5ae67a79 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java @@ -164,7 +164,8 @@ public abstract class AbstractPageViewer extends ContentViewer implements Observ mouseListener = null; refresh(getControl()); // layout(getControl()); - layoutPage(); + if (!getControl().isDisposed()) + layoutPage(); } catch (RepositoryException e) { throw new JcrException("Cannot refresh", e); } diff --git a/org.argeo.cms/.classpath b/org.argeo.cms/.classpath index 53c7dca0b..9a0a269a2 100644 --- a/org.argeo.cms/.classpath +++ b/org.argeo.cms/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.cms/OSGI-INF/filesServlet.xml b/org.argeo.cms/OSGI-INF/filesServlet.xml index 498d09749..ec161faa6 100644 --- a/org.argeo.cms/OSGI-INF/filesServlet.xml +++ b/org.argeo.cms/OSGI-INF/filesServlet.xml @@ -1,5 +1,5 @@ - + diff --git a/org.argeo.cms/OSGI-INF/pkgServlet.xml b/org.argeo.cms/OSGI-INF/pkgServlet.xml new file mode 100644 index 000000000..30e52311c --- /dev/null +++ b/org.argeo.cms/OSGI-INF/pkgServlet.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/pkgServletContext.xml b/org.argeo.cms/OSGI-INF/pkgServletContext.xml new file mode 100644 index 000000000..7540a2cdb --- /dev/null +++ b/org.argeo.cms/OSGI-INF/pkgServletContext.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index 9ecfb8f1a..5af544206 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -7,12 +7,15 @@ org.apache.jackrabbit.webdav.server,\ org.apache.jackrabbit.webdav.jcr,\ org.apache.commons.httpclient.cookie;resolution:=optional,\ !com.sun.security.jgss,\ +org.osgi.framework.namespace;version=0.0.0,\ org.osgi.*;version=0.0.0,\ org.osgi.service.http.whiteboard,\ * Service-Component:\ OSGI-INF/cmsUserManager.xml,\ +OSGI-INF/pkgServletContext.xml,\ +OSGI-INF/pkgServlet.xml,\ OSGI-INF/jcrServletContext.xml,\ OSGI-INF/dataServletContext.xml,\ OSGI-INF/filesServletContext.xml,\ diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java index e9462c3ad..4c09650d4 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java @@ -20,7 +20,6 @@ import org.argeo.api.security.NodeSecurityUtils; import org.argeo.cms.internal.auth.CmsSessionImpl; import org.argeo.cms.internal.auth.ImpliedByPrincipal; import org.argeo.cms.internal.http.WebCmsSessionImpl; -import org.argeo.cms.internal.kernel.Activator; import org.argeo.osgi.useradmin.AuthenticatingUser; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; @@ -28,7 +27,7 @@ import org.osgi.framework.ServiceReference; import org.osgi.service.http.HttpContext; import org.osgi.service.useradmin.Authorization; -/** Centrlaises security related registrations. */ +/** Centralises security related registrations. */ class CmsAuthUtils { // Standard final static String SHARED_STATE_NAME = AuthenticatingUser.SHARED_STATE_NAME; @@ -52,9 +51,7 @@ class CmsAuthUtils { // required for display name: subject.getPrivateCredentials().add(authorization); - if (Activator.isSingleUser()) { - subject.getPrincipals().add(new DataAdminPrincipal()); - } + boolean singleUser = authorization instanceof SingleUserAuthorization; Set principals = subject.getPrincipals(); try { @@ -73,8 +70,9 @@ class CmsAuthUtils { userPrincipal = new X500Principal(name.toString()); principals.add(userPrincipal); - if (Activator.isSingleUser()) { + if (singleUser) { principals.add(new ImpliedByPrincipal(NodeSecurityUtils.ROLE_ADMIN_NAME, userPrincipal)); + principals.add(new DataAdminPrincipal()); } } @@ -123,6 +121,7 @@ class CmsAuthUtils { // subject.getPrincipals().removeAll(subject.getPrincipals(AnonymousPrincipal.class)); } + @SuppressWarnings("unused") synchronized static void registerSessionAuthorization(HttpServletRequest request, Subject subject, Authorization authorization, Locale locale) { // synchronized in order to avoid multiple registrations @@ -131,49 +130,59 @@ class CmsAuthUtils { HttpSession httpSession = request.getSession(false); assert httpSession != null; String httpSessId = httpSession.getId(); - String remoteUser = authorization.getName() != null ? authorization.getName() - : NodeConstants.ROLE_ANONYMOUS; + boolean anonymous = authorization.getName() == null; + String remoteUser = !anonymous ? authorization.getName() : NodeConstants.ROLE_ANONYMOUS; request.setAttribute(HttpContext.REMOTE_USER, remoteUser); request.setAttribute(HttpContext.AUTHORIZATION, authorization); - CmsSessionImpl cmsSession = CmsSessionImpl.getByLocalId(httpSessId); - if (cmsSession != null) { - if (authorization.getName() != null) { - if (cmsSession.getAuthorization().getName() == null) { - cmsSession.close(); - cmsSession = null; - } else if (!authorization.getName().equals(cmsSession.getAuthorization().getName())) { + CmsSessionImpl cmsSession; + CmsSessionImpl currentLocalSession = CmsSessionImpl.getByLocalId(httpSessId); + if (currentLocalSession != null) { + boolean currentLocalSessionAnonymous = currentLocalSession.getAuthorization().getName() == null; + if (!anonymous) { + if (currentLocalSessionAnonymous) { + currentLocalSession.close(); + // new CMS session + cmsSession = new WebCmsSessionImpl(subject, authorization, locale, request); + } else if (!authorization.getName().equals(currentLocalSession.getAuthorization().getName())) { throw new IllegalStateException("Inconsistent user " + authorization.getName() - + " for existing CMS session " + cmsSession); - } - // keyring - if (cmsSession != null) + + " for existing CMS session " + currentLocalSession); + } else { + // keep current session + cmsSession = currentLocalSession; + // keyring subject.getPrivateCredentials().addAll(cmsSession.getSecretKeys()); + } } else {// anonymous - if (cmsSession.getAuthorization().getName() != null) { - cmsSession.close(); - // TODO rather throw an exception ? log a warning ? - cmsSession = null; + if (!currentLocalSessionAnonymous) { + currentLocalSession.close(); + throw new IllegalStateException( + "Existing CMS session " + currentLocalSession + " was not logged out properly."); } + // keep current session + cmsSession = currentLocalSession; } - } else if (cmsSession == null) { + } else { + // new CMS session cmsSession = new WebCmsSessionImpl(subject, authorization, locale, request); } - // request.setAttribute(CmsSession.class.getName(), cmsSession); - if (cmsSession != null) { - CmsSessionId nodeSessionId = new CmsSessionId(cmsSession.getUuid()); - if (subject.getPrivateCredentials(CmsSessionId.class).size() == 0) - subject.getPrivateCredentials().add(nodeSessionId); - else { - UUID storedSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next() - .getUuid(); - // if (storedSessionId.equals(httpSessionId.getValue())) - throw new IllegalStateException( - "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")"); - } + + if (cmsSession == null)// should be dead code (cf. SuppressWarning of the method) + throw new IllegalStateException("CMS session cannot be null"); + + CmsSessionId nodeSessionId = new CmsSessionId(cmsSession.getUuid()); + if (subject.getPrivateCredentials(CmsSessionId.class).size() == 0) { + subject.getPrivateCredentials().add(nodeSessionId); + } else { + UUID storedSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid(); + // if (storedSessionId.equals(httpSessionId.getValue())) + throw new IllegalStateException( + "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")"); } } else { - // TODO desktop, CLI + CmsSessionImpl cmsSession = new CmsSessionImpl(subject, authorization, locale, "desktop"); + CmsSessionId nodeSessionId = new CmsSessionId(cmsSession.getUuid()); + subject.getPrivateCredentials().add(nodeSessionId); } } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SingleUserAuthorization.java b/org.argeo.cms/src/org/argeo/cms/auth/SingleUserAuthorization.java index b4144d30f..af4e636d8 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SingleUserAuthorization.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SingleUserAuthorization.java @@ -8,10 +8,15 @@ import org.osgi.service.useradmin.Authorization; * @see SingleUserLoginModule */ public class SingleUserAuthorization implements Authorization { + private String name; + + public SingleUserAuthorization(String name) { + this.name = name; + } @Override public String getName() { - return System.getProperty("user.name"); + return name; } @Override diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java index 0b163bac3..240564f9e 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java @@ -2,10 +2,8 @@ package org.argeo.cms.auth; import java.net.InetAddress; import java.net.UnknownHostException; -import java.security.Principal; import java.util.Locale; import java.util.Map; -import java.util.Set; import javax.naming.ldap.LdapName; import javax.security.auth.Subject; @@ -18,11 +16,9 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.argeo.api.NodeConstants; -import org.argeo.api.security.DataAdminPrincipal; -import org.argeo.cms.internal.auth.ImpliedByPrincipal; import org.argeo.naming.LdapAttrs; import org.argeo.osgi.useradmin.IpaUtils; +import org.argeo.osgi.useradmin.OsUserUtils; import org.osgi.service.useradmin.Authorization; /** Login module for when the system is owned by a single user. */ @@ -50,11 +46,12 @@ public class SingleUserLoginModule implements LoginModule { @Override public boolean commit() throws LoginException { - X500Principal principal; + String authorizationName; KerberosPrincipal kerberosPrincipal = CmsAuthUtils.getSinglePrincipal(subject, KerberosPrincipal.class); if (kerberosPrincipal != null) { LdapName userDn = IpaUtils.kerberosToDn(kerberosPrincipal.getName()); - principal = new X500Principal(userDn.toString()); + X500Principal principal = new X500Principal(userDn.toString()); + authorizationName = principal.getName(); } else { Object username = sharedState.get(CmsAuthUtils.SHARED_STATE_NAME); if (username == null) @@ -67,12 +64,9 @@ public class SingleUserLoginModule implements LoginModule { hostname = "localhost"; } String baseDn = ("." + hostname).replaceAll("\\.", ",dc="); - principal = new X500Principal(LdapAttrs.uid + "=" + username + baseDn); + X500Principal principal = new X500Principal(LdapAttrs.uid + "=" + username + baseDn); + authorizationName = principal.getName(); } - Set principals = subject.getPrincipals(); - principals.add(principal); - principals.add(new ImpliedByPrincipal(NodeConstants.ROLE_ADMIN, principal)); - principals.add(new DataAdminPrincipal()); HttpServletRequest request = (HttpServletRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST); Locale locale = Locale.getDefault(); @@ -80,8 +74,18 @@ public class SingleUserLoginModule implements LoginModule { locale = request.getLocale(); if (locale == null) locale = Locale.getDefault(); - Authorization authorization = new SingleUserAuthorization(); + Authorization authorization = new SingleUserAuthorization(authorizationName); CmsAuthUtils.addAuthorization(subject, authorization); + + // Add standard Java OS login + OsUserUtils.loginAsSystemUser(subject); + + // additional principals (must be after Authorization registration) +// Set principals = subject.getPrincipals(); +// principals.add(principal); +// principals.add(new ImpliedByPrincipal(NodeConstants.ROLE_ADMIN, principal)); +// principals.add(new DataAdminPrincipal()); + CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale); return true; diff --git a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java index 4b72903e2..092a06b77 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java @@ -33,7 +33,6 @@ import org.argeo.cms.internal.kernel.Activator; import org.argeo.naming.LdapAttrs; import org.argeo.osgi.useradmin.AuthenticatingUser; import org.argeo.osgi.useradmin.IpaUtils; -import org.argeo.osgi.useradmin.OsUserUtils; import org.argeo.osgi.useradmin.TokenUtils; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; @@ -64,7 +63,7 @@ public class UserAdminLoginModule implements LoginModule { private Authorization bindAuthorization = null; - private boolean singleUser = Activator.isSingleUser(); +// private boolean singleUser = Activator.isSingleUser(); @SuppressWarnings("unchecked") @Override @@ -113,11 +112,11 @@ public class UserAdminLoginModule implements LoginModule { username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME); password = null; preauth = true; - } else if (singleUser) { - username = OsUserUtils.getOsUsername(); - password = null; - // TODO retrieve from http session - locale = Locale.getDefault(); +// } else if (singleUser) { +// username = OsUserUtils.getOsUsername(); +// password = null; +// // TODO retrieve from http session +// locale = Locale.getDefault(); } else { // ask for username and password @@ -194,8 +193,8 @@ public class UserAdminLoginModule implements LoginModule { // TODO check CRLs/OSCP validity? // NB: authorization in commit() will work only if an LDAP connection password // is provided - } else if (singleUser) { - // TODO verify IP address? +// } else if (singleUser) { +// // TODO verify IP address? } else if (preauth) { // ident } else { @@ -211,9 +210,9 @@ public class UserAdminLoginModule implements LoginModule { if (locale != null) subject.getPublicCredentials().add(locale); - if (singleUser) { - OsUserUtils.loginAsSystemUser(subject); - } +// if (singleUser) { +// OsUserUtils.loginAsSystemUser(subject); +// } UserAdmin userAdmin = Activator.getUserAdmin(); Authorization authorization; if (callbackHandler == null) {// anonymous @@ -308,6 +307,7 @@ public class UserAdminLoginModule implements LoginModule { Set collectedUsers = new HashSet<>(); // try dn User user = null; + user = null; // try all indexes for (String attr : indexedUserProperties) { user = userAdmin.getUser(attr, providedUsername); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java index b6966765d..f40c6fffd 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java @@ -1,5 +1,6 @@ package org.argeo.cms.internal.auth; +import java.io.Serializable; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedAction; @@ -10,7 +11,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; -import java.util.LinkedHashSet; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -40,15 +40,16 @@ import org.osgi.framework.ServiceRegistration; import org.osgi.service.useradmin.Authorization; /** Default CMS session implementation. */ -public class CmsSessionImpl implements CmsSession { +public class CmsSessionImpl implements CmsSession, Serializable { + private static final long serialVersionUID = 1867719354246307225L; private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionImpl.class).getBundleContext(); private final static Log log = LogFactory.getLog(CmsSessionImpl.class); // private final Subject initialSubject; - private final AccessControlContext initialContext; + private transient AccessControlContext accessControlContext; private final UUID uuid; private final String localSessionId; - private final Authorization authorization; + private Authorization authorization; private final LdapName userDn; private final boolean anonymous; @@ -60,14 +61,14 @@ public class CmsSessionImpl implements CmsSession { private Map dataSessions = new HashMap<>(); private Set dataSessionsInUse = new HashSet<>(); - private LinkedHashSet additionalDataSessions = new LinkedHashSet<>(); + private Set additionalDataSessions = new HashSet<>(); private Map views = new HashMap<>(); public CmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale, String localSessionId) { this.creationTime = ZonedDateTime.now(); this.locale = locale; - this.initialContext = Subject.doAs(initialSubject, new PrivilegedAction() { + this.accessControlContext = Subject.doAs(initialSubject, new PrivilegedAction() { @Override public AccessControlContext run() { @@ -120,23 +121,28 @@ public class CmsSessionImpl implements CmsSession { lc.logout(); } catch (LoginException e) { log.warn("Could not logout " + getSubject() + ": " + e); + } finally { + accessControlContext = null; } log.debug("Closed " + this); } private Subject getSubject() { - return Subject.getSubject(initialContext); + return Subject.getSubject(accessControlContext); } public Set getSecretKeys() { + checkValid(); return getSubject().getPrivateCredentials(SecretKey.class); } public Session newDataSession(String cn, String workspace, Repository repository) { + checkValid(); return login(repository, workspace); } public synchronized Session getDataSession(String cn, String workspace, Repository repository) { + checkValid(); // FIXME make it more robust if (workspace == null) workspace = NodeConstants.SYS_WORKSPACE; @@ -207,12 +213,18 @@ public class CmsSessionImpl implements CmsSession { return !isClosed(); } - protected boolean isClosed() { + private void checkValid() { + if (!isValid()) + throw new IllegalStateException("CMS session " + uuid + " is not valid since " + end); + } + + final protected boolean isClosed() { return getEnd() != null; } @Override public Authorization getAuthorization() { + checkValid(); return authorization; } @@ -258,6 +270,7 @@ public class CmsSessionImpl implements CmsSession { @Override public void registerView(String uid, Object view) { + checkValid(); if (views.containsKey(uid)) throw new IllegalArgumentException("View " + uid + " is already registered."); views.put(uid, view); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java new file mode 100644 index 000000000..9cbd21c9f --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java @@ -0,0 +1,134 @@ +package org.argeo.cms.internal.http; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Collection; +import java.util.Enumeration; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.IOUtils; +import org.argeo.api.PublishNamespace; +import org.argeo.osgi.util.FilterRequirement; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.Version; +import org.osgi.framework.VersionRange; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleWiring; +import org.osgi.framework.wiring.FrameworkWiring; +import org.osgi.resource.Requirement; + +public class PkgServlet extends HttpServlet { + private static final long serialVersionUID = 7660824185145214324L; + + private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String pathInfo = req.getPathInfo(); + + String pkg, versionStr, file; + String[] parts = pathInfo.split("/"); + // first is always empty + if (parts.length == 4) { + pkg = parts[1]; + versionStr = parts[2]; + file = parts[3]; + } else if (parts.length == 3) { + pkg = parts[1]; + versionStr = null; + file = parts[2]; + } else { + throw new IllegalArgumentException("Unsupported path length " + pathInfo); + } + + FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class); + String filter; + if (versionStr == null) { + filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"; + } else { + if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range + VersionRange versionRange = new VersionRange(versionStr); + filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")" + + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")"; + + } else { + Version version = new Version(versionStr); + filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")(" + + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))"; + } + } + Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter); + Collection packages = frameworkWiring.findProviders(requirement); + if (packages.isEmpty()) { + resp.sendError(404); + return; + } + + // TODO verify that it works with multiple versions + SortedMap sorted = new TreeMap<>(); + for (BundleCapability capability : packages) { + sorted.put(capability.getRevision().getVersion(), capability); + } + + Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle(); + String entryPath = '/' + pkg.replace('.', '/') + '/' + file; + URL internalURL = bundle.getResource(entryPath); + if (internalURL == null) { + resp.sendError(404); + return; + } + + // Resource found, we now check whether it can be published + boolean publish = false; + BundleWiring bundleWiring = bundle.adapt(BundleWiring.class); + capabilities: for (BundleCapability bundleCapability : bundleWiring + .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) { + Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG); + if (publishedPkg != null) { + if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) { + Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE); + if (publishedFile == null) { + publish = true; + break capabilities; + } else { + String[] publishedFiles = publishedFile.toString().split(","); + for (String pattern : publishedFiles) { + if (pattern.startsWith("*.")) { + String ext = pattern.substring(1); + if (file.endsWith(ext)) { + publish = true; + break capabilities; + } + } else { + if (publishedFile.equals(file)) { + publish = true; + break capabilities; + } + } + } + } + } + } + } + + if (!publish) { + resp.sendError(404); + return; + } + + try (InputStream in = internalURL.openStream()) { + IOUtils.copy(in, resp.getOutputStream()); + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java b/org.argeo.cms/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java index 2e703ced8..c2898577e 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java @@ -2,6 +2,7 @@ package org.argeo.cms.internal.jcr; import java.util.Properties; +import org.apache.jackrabbit.core.config.BeanConfig; import org.apache.jackrabbit.core.config.ConfigurationException; import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig; @@ -14,7 +15,7 @@ import org.w3c.dom.Element; */ @SuppressWarnings("restriction") class CustomRepositoryConfigurationParser extends RepositoryConfigurationParser { - private ClassLoader accessControlProviderClassLoader = null; + private ClassLoader classLoader = null; public CustomRepositoryConfigurationParser(Properties variables) { super(variables); @@ -30,19 +31,28 @@ class CustomRepositoryConfigurationParser extends RepositoryConfigurationParser props.putAll(variables); CustomRepositoryConfigurationParser subParser = new CustomRepositoryConfigurationParser(props, connectionFactory); - subParser.setAccessControlProviderClassLoader(accessControlProviderClassLoader); + subParser.setClassLoader(classLoader); return subParser; } @Override public WorkspaceSecurityConfig parseWorkspaceSecurityConfig(Element parent) throws ConfigurationException { WorkspaceSecurityConfig workspaceSecurityConfig = super.parseWorkspaceSecurityConfig(parent); - workspaceSecurityConfig.getAccessControlProviderConfig().setClassLoader(accessControlProviderClassLoader); + workspaceSecurityConfig.getAccessControlProviderConfig().setClassLoader(classLoader); return workspaceSecurityConfig; } - public void setAccessControlProviderClassLoader(ClassLoader accessControlProviderClassLoader) { - this.accessControlProviderClassLoader = accessControlProviderClassLoader; + @Override + protected BeanConfig parseBeanConfig(Element parent, String name) throws ConfigurationException { + BeanConfig beanConfig = super.parseBeanConfig(parent, name); + if (beanConfig.getClassName().startsWith("org.argeo")) { + beanConfig.setClassLoader(classLoader); + } + return beanConfig; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java b/org.argeo.cms/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java new file mode 100644 index 000000000..dba005cb4 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java @@ -0,0 +1,68 @@ +package org.argeo.cms.internal.jcr; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.apache.jackrabbit.core.data.DataIdentifier; +import org.apache.jackrabbit.core.data.DataRecord; +import org.apache.jackrabbit.core.data.DataStoreException; +import org.apache.jackrabbit.core.data.FileDataStore; + +/** + * experimental Duplicate added entries in another directory (typically a + * remote mount). + */ +@SuppressWarnings("restriction") +public class LocalFsDataStore extends FileDataStore { + String redundantPath; + FileDataStore redundantStore; + + @Override + public void init(String homeDir) { + // init primary first + super.init(homeDir); + + if (redundantPath != null) { + // redundant directory must be created first + // TODO implement some polling? + if (Files.exists(Paths.get(redundantPath))) { + redundantStore = new FileDataStore(); + redundantStore.setPath(redundantPath); + redundantStore.init(homeDir); + } + } + } + + @Override + public DataRecord addRecord(InputStream input) throws DataStoreException { + DataRecord dataRecord = super.addRecord(input); + syncRedundantRecord(dataRecord); + return dataRecord; + } + + @Override + public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException { + DataRecord dataRecord = super.getRecord(identifier); + syncRedundantRecord(dataRecord); + return dataRecord; + } + + protected void syncRedundantRecord(DataRecord dataRecord) throws DataStoreException { + if (redundantStore == null) + return; + if (redundantStore.getRecordIfStored(dataRecord.getIdentifier()) == null) { + try (InputStream redundant = dataRecord.getStream()) { + redundantStore.addRecord(redundant); + } catch (IOException e) { + throw new DataStoreException("Cannot add redundant record.", e); + } + } + } + + public void setRedundantPath(String redundantPath) { + this.redundantPath = redundantPath; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java b/org.argeo.cms/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java index 9480a2913..fbb1e4f7a 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java @@ -61,7 +61,7 @@ public class RepositoryBuilder { // custom configuration parser CustomRepositoryConfigurationParser parser = new CustomRepositoryConfigurationParser(jackrabbitVars); - parser.setAccessControlProviderClassLoader(cl); + parser.setClassLoader(cl); RepositoryConfig repositoryConfig = parser.parseRepositoryConfig(config); repositoryConfig.init(); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml b/org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml index ff181f195..b430674c9 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml +++ b/org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml @@ -14,20 +14,24 @@ - + - + + - + @@ -38,11 +42,14 @@ - - + + - + - + @@ -66,23 +74,28 @@ - + - + - - + - - + + diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java index 8086c8636..6d50f3dab 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java @@ -226,6 +226,7 @@ public class Activator implements BundleActivator { return getNodeUserAdmin().getAcceptorCredentials(); } + @Deprecated public static boolean isSingleUser() { return getNodeUserAdmin().isSingleUser(); } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java index 40e23541b..1a9817450 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java @@ -96,20 +96,25 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor @Override public void updated(String pid, Dictionary properties) throws ConfigurationException { String uri = (String) properties.get(UserAdminConf.uri.name()); + Object realm = properties.get(UserAdminConf.realm.name()); URI u; try { if (uri == null) { String baseDn = (String) properties.get(UserAdminConf.baseDn.name()); u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + baseDn + ".ldif"); - } else + } else if (realm != null) { + u = null; + } else { u = new URI(uri); + } } catch (URISyntaxException e) { throw new IllegalArgumentException("Badly formatted URI " + uri, e); } // Create AbstractUserDirectory userDirectory; - if (UserAdminConf.SCHEME_LDAP.equals(u.getScheme())) { + if (realm != null || UserAdminConf.SCHEME_LDAP.equals(u.getScheme()) + || UserAdminConf.SCHEME_LDAPS.equals(u.getScheme())) { userDirectory = new LdapUserAdmin(properties); } else if (UserAdminConf.SCHEME_FILE.equals(u.getScheme())) { userDirectory = new LdifUserAdmin(u, properties); @@ -119,7 +124,6 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor } else { throw new IllegalArgumentException("Unsupported scheme " + u.getScheme()); } - Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name()); addUserDirectory(userDirectory); // OSGi @@ -135,9 +139,10 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor pidToBaseDn.put(pid, baseDn); // pidToServiceRegs.put(pid, reg); - if (log.isDebugEnabled()) - log.debug("User directory " + userDirectory.getBaseDn() + " [" + u.getScheme() + "] enabled." - + (realm != null ? " " + realm + " realm." : "")); + if (log.isDebugEnabled()) { + log.debug("User directory " + userDirectory.getBaseDn() + (u != null ? " [" + u.getScheme() + "]" : "") + + " enabled." + (realm != null ? " " + realm + " realm." : "")); + } if (isSystemRolesBaseDn(baseDn)) { // publishes only when system roles are available diff --git a/org.argeo.cms/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms/src/org/argeo/cms/servlet/CmsServletContext.java index 9ff8f855f..c88ee7f93 100644 --- a/org.argeo.cms/src/org/argeo/cms/servlet/CmsServletContext.java +++ b/org.argeo.cms/src/org/argeo/cms/servlet/CmsServletContext.java @@ -21,7 +21,7 @@ import org.osgi.framework.FrameworkUtil; import org.osgi.service.http.context.ServletContextHelper; /** - * Default servlet context degrading to anonymous if the the sesison is not + * Default servlet context degrading to anonymous if the the session is not * pre-authenticated. */ public class CmsServletContext extends ServletContextHelper { diff --git a/org.argeo.core/.classpath b/org.argeo.core/.classpath index 53c7dca0b..9a0a269a2 100644 --- a/org.argeo.core/.classpath +++ b/org.argeo.core/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.eclipse.ui.rap/.classpath b/org.argeo.eclipse.ui.rap/.classpath index 457b11571..e03d341b1 100644 --- a/org.argeo.eclipse.ui.rap/.classpath +++ b/org.argeo.eclipse.ui.rap/.classpath @@ -4,6 +4,6 @@ + path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11" /> diff --git a/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java b/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java index f79c7fd88..a89b921cd 100644 --- a/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java +++ b/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java @@ -22,6 +22,10 @@ public class EclipseUiSpecificUtils { widget.setData(RWT.MARKUP_ENABLED, true); } + public static void setMarkupValidationDisabledData(Widget widget) { + widget.setData("org.eclipse.rap.rwt.markupValidationDisabled", Boolean.TRUE); + } + /** * TootlTip support is supported only for {@link AbstractTableViewer} in RAP */ diff --git a/org.argeo.eclipse.ui/.classpath b/org.argeo.eclipse.ui/.classpath index 457b11571..e03d341b1 100644 --- a/org.argeo.eclipse.ui/.classpath +++ b/org.argeo.eclipse.ui/.classpath @@ -4,6 +4,6 @@ + path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11" /> diff --git a/org.argeo.enterprise/.classpath b/org.argeo.enterprise/.classpath index 4e5da1da6..b21df7df8 100644 --- a/org.argeo.enterprise/.classpath +++ b/org.argeo.enterprise/.classpath @@ -5,6 +5,6 @@ + path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11" /> diff --git a/org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java b/org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java index 62667c518..d9358c083 100644 --- a/org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java +++ b/org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java @@ -79,7 +79,7 @@ public class DnsBrowser implements Closeable { } /** Ordered, with preferred first. */ - public List getSrvRecordsAsHosts(String name) throws NamingException { + public List getSrvRecordsAsHosts(String name, boolean withPort) throws NamingException { List raw = getRecords(name, "SRV"); if (raw.size() == 0) return null; @@ -96,7 +96,7 @@ public class DnsBrowser implements Closeable { } List lst = new ArrayList<>(); for (SrvRecord order : res) { - lst.add(order.toHost()); + lst.add(order.toHost(withPort)); } return Collections.unmodifiableList(lst); } diff --git a/org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java b/org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java index d3515889a..8ecc94457 100644 --- a/org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java +++ b/org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java @@ -20,10 +20,10 @@ class SrvRecord implements Comparable { return priority - other.priority; if (weight != other.weight) return other.weight - other.weight; - String host = toHost(); - String otherHost = other.toHost(); + String host = toHost(false); + String otherHost = other.toHost(false); if (host.length() == otherHost.length()) - return toHost().compareTo(other.toHost()); + return host.compareTo(otherHost); else return host.length() - otherHost.length(); } @@ -43,10 +43,10 @@ class SrvRecord implements Comparable { return priority + " " + weight; } - public String toHost() { + public String toHost(boolean withPort) { String hostStr = hostname; if (hostname.charAt(hostname.length() - 1) == '.') hostStr = hostname.substring(0, hostname.length() - 1); - return hostStr + ":" + port; + return hostStr + (withPort ? ":" + port : ""); } } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java index 610f3f640..f2d7c88fc 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java @@ -51,13 +51,15 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory private final boolean readOnly; private final boolean disabled; - private final URI uri; + private final String uri; private UserAdmin externalRoles; // private List indexedUserProperties = Arrays // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(), // LdapAttrs.cn.name() }); + private final boolean scoped; + private String memberAttributeId = "member"; private List credentialAttributeIds = Arrays .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() }); @@ -66,7 +68,8 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory private TransactionManager transactionManager; private WcXaResource xaResource = new WcXaResource(this); - public AbstractUserDirectory(URI uriArg, Dictionary props) { + AbstractUserDirectory(URI uriArg, Dictionary props, boolean scoped) { + this.scoped = scoped; properties = new Hashtable(); for (Enumeration keys = props.keys(); keys.hasMoreElements();) { String key = keys.nextElement(); @@ -74,18 +77,14 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory } if (uriArg != null) { - uri = uriArg; + uri = uriArg.toString(); // uri from properties is ignored } else { String uriStr = UserAdminConf.uri.getValue(properties); if (uriStr == null) uri = null; else - try { - uri = new URI(uriStr); - } catch (URISyntaxException e) { - throw new UserDirectoryException("Badly formatted URI " + uriStr, e); - } + uri = uriStr; } userObjectClass = UserAdminConf.userObjectClass.getValue(properties); @@ -175,14 +174,16 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory Attributes attrs = user.getAttributes(); // TODO centralize attribute name Attribute memberOf = attrs.get(LdapAttrs.memberOf.name()); - if (memberOf != null) { + // if user belongs to this directory, we only check meberOf + if (memberOf != null && user.getDn().startsWith(getBaseDn())) { try { NamingEnumeration values = memberOf.getAll(); while (values.hasMore()) { Object value = values.next(); LdapName groupDn = new LdapName(value.toString()); DirectoryUser group = doGetRole(groupDn); - allRoles.add(group); + if (group != null) + allRoles.add(group); } } catch (Exception e) { throw new UserDirectoryException("Cannot get memberOf groups for " + user, e); @@ -191,8 +192,10 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory for (LdapName groupDn : getDirectGroups(user.getDn())) { // TODO check for loops DirectoryUser group = doGetRole(groupDn); - allRoles.add(group); - collectRoles(group, allRoles); + if (group != null) { + allRoles.add(group); + collectRoles(group, allRoles); + } } } } @@ -398,13 +401,20 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory return credentialAttributeIds; } - protected URI getUri() { + protected String getUri() { return uri; } - private static boolean readOnlyDefault(URI uri) { - if (uri == null) + private static boolean readOnlyDefault(String uriStr) { + if (uriStr == null) return true; + /// TODO make it more generic + URI uri; + try { + uri = new URI(uriStr.split(" ")[0]); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } if (uri.getScheme() == null) return false;// assume relative file to be writable if (uri.getScheme().equals(UserAdminConf.SCHEME_FILE)) { @@ -488,4 +498,8 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory return xaResource; } + public boolean isScoped() { + return scoped; + } + } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java index 66d46d4e9..bee513546 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java @@ -74,9 +74,9 @@ public class AggregatingUserAdmin implements UserAdmin { public User getUser(String key, String value) { List res = new ArrayList(); for (UserAdmin userAdmin : businessRoles.values()) { - User u = userAdmin.getUser(key, value); - if (u != null) - res.add(u); + User u = userAdmin.getUser(key, value); + if (u != null) + res.add(u); } // Note: node roles cannot contain users, so it is not searched return res.size() == 1 ? res.get(0) : null; @@ -87,11 +87,12 @@ public class AggregatingUserAdmin implements UserAdmin { if (user == null) {// anonymous return systemRoles.getAuthorization(null); } - UserAdmin userAdmin = findUserAdmin(user.getName()); - Authorization rawAuthorization = userAdmin.getAuthorization(user); + AbstractUserDirectory userReferentialOfThisUser = findUserAdmin(user.getName()); + Authorization rawAuthorization = userReferentialOfThisUser.getAuthorization(user); String usernameToUse; String displayNameToUse; if (user instanceof Group) { + // TODO check whether this is still working String ownerDn = TokenUtils.userDn((Group) user); if (ownerDn != null) {// tokens UserAdmin ownerUserAdmin = findUserAdmin(ownerDn); @@ -106,21 +107,38 @@ public class AggregatingUserAdmin implements UserAdmin { usernameToUse = rawAuthorization.getName(); displayNameToUse = rawAuthorization.toString(); } - // gather system roles - Set sysRoles = new HashSet(); - for (String role : rawAuthorization.getRoles()) { - Authorization auth = systemRoles.getAuthorization((User) userAdmin.getRole(role)); - systemRoles: for (String systemRole : auth.getRoles()) { - if (role.equals(systemRole)) - continue systemRoles; - sysRoles.add(systemRole); - } + + // gather roles from other referentials + final AbstractUserDirectory userAdminToUse;// possibly scoped when authenticating + if (user instanceof DirectoryUser) { + userAdminToUse = userReferentialOfThisUser; + } else if (user instanceof AuthenticatingUser) { + userAdminToUse = userReferentialOfThisUser.scope(user); + } else { + throw new IllegalArgumentException("Unsupported user type " + user.getClass()); + } + + try { + Set sysRoles = new HashSet(); + for (String role : rawAuthorization.getRoles()) { + User userOrGroup = (User) userAdminToUse.getRole(role); + Authorization auth = systemRoles.getAuthorization(userOrGroup); + systemRoles: for (String systemRole : auth.getRoles()) { + if (role.equals(systemRole)) + continue systemRoles; + sysRoles.add(systemRole); + } // sysRoles.addAll(Arrays.asList(auth.getRoles())); + } + addAbstractSystemRoles(rawAuthorization, sysRoles); + Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles, + rawAuthorization.getRoles()); + return authorization; + } finally { + if (userAdminToUse != null && userAdminToUse.isScoped()) { + userAdminToUse.destroy(); + } } - addAbstractSystemRoles(rawAuthorization, sysRoles); - Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles, - rawAuthorization.getRoles()); - return authorization; } /** @@ -155,16 +173,26 @@ public class AggregatingUserAdmin implements UserAdmin { protected void postAdd(AbstractUserDirectory userDirectory) { } - private UserAdmin findUserAdmin(String name) { +// private UserAdmin findUserAdmin(User user) { +// if (user == null) +// throw new IllegalArgumentException("User should not be null"); +// AbstractUserDirectory userAdmin = findUserAdmin(user.getName()); +// if (user instanceof DirectoryUser) { +// return userAdmin; +// } else { +// return userAdmin.scope(user); +// } +// } + + private AbstractUserDirectory findUserAdmin(String name) { try { - UserAdmin userAdmin = findUserAdmin(new LdapName(name)); - return userAdmin; + return findUserAdmin(new LdapName(name)); } catch (InvalidNameException e) { throw new UserDirectoryException("Badly formatted name " + name, e); } } - private UserAdmin findUserAdmin(LdapName name) { + private AbstractUserDirectory findUserAdmin(LdapName name) { if (name.startsWith(systemRolesBaseDn)) return systemRoles; if (tokensBaseDn != null && name.startsWith(tokensBaseDn)) diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java index 9d0056c55..d56c06ac0 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java @@ -1,8 +1,19 @@ package org.argeo.osgi.useradmin; +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; + import javax.naming.InvalidNameException; +import javax.naming.NamingException; import javax.naming.ldap.LdapName; +import org.argeo.naming.DnsBrowser; import org.argeo.naming.LdapAttrs; /** Free IPA specific conventions. */ @@ -16,10 +27,19 @@ public class IpaUtils { public final static String IPA_USER_DIRECTORY_CONFIG = UserAdminConf.userBase + "=" + IPA_USER_BASE + "&" + UserAdminConf.groupBase + "=" + IPA_GROUP_BASE + "&" + UserAdminConf.readOnly + "=true"; + @Deprecated static String domainToUserDirectoryConfigPath(String realm) { return domainToBaseDn(realm) + "?" + IPA_USER_DIRECTORY_CONFIG + "&" + UserAdminConf.realm.name() + "=" + realm; } + public static void addIpaConfig(String realm, Dictionary properties) { + properties.put(UserAdminConf.baseDn.name(), domainToBaseDn(realm)); + properties.put(UserAdminConf.realm.name(), realm); + properties.put(UserAdminConf.userBase.name(), IPA_USER_BASE); + properties.put(UserAdminConf.groupBase.name(), IPA_GROUP_BASE); + properties.put(UserAdminConf.readOnly.name(), Boolean.TRUE.toString()); + } + public static String domainToBaseDn(String domain) { String[] dcs = domain.split("\\."); StringBuilder sb = new StringBuilder(); @@ -51,4 +71,67 @@ public class IpaUtils { private IpaUtils() { } + + public static String kerberosDomainFromDns() { + String kerberosDomain; + try (DnsBrowser dnsBrowser = new DnsBrowser()) { + InetAddress localhost = InetAddress.getLocalHost(); + String hostname = localhost.getHostName(); + String dnsZone = hostname.substring(hostname.indexOf('.') + 1); + kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT"); + return kerberosDomain; + } catch (Exception e) { + throw new UserDirectoryException("Cannot determine Kerberos domain from DNS", e); + } + + } + + public static Dictionary convertIpaUri(URI uri) { + String path = uri.getPath(); + String kerberosRealm; + if (path == null || path.length() <= 1) { + kerberosRealm = kerberosDomainFromDns(); + } else { + kerberosRealm = path.substring(1); + } + + if (kerberosRealm == null) + throw new UserDirectoryException("No Kerberos domain available for " + uri); + // TODO intergrate CA certificate in truststore + // String schemeToUse = SCHEME_LDAPS; + String schemeToUse = UserAdminConf.SCHEME_LDAP; + List ldapHosts; + String ldapHostsStr = uri.getHost(); + if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) { + try (DnsBrowser dnsBrowser = new DnsBrowser()) { + ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase(), + schemeToUse.equals(UserAdminConf.SCHEME_LDAP) ? true : false); + if (ldapHosts == null || ldapHosts.size() == 0) { + throw new UserDirectoryException("Cannot configure LDAP for IPA " + uri); + } else { + ldapHostsStr = ldapHosts.get(0); + } + } catch (NamingException | IOException e) { + throw new UserDirectoryException("cannot convert IPA uri " + uri, e); + } + } else { + ldapHosts = new ArrayList<>(); + ldapHosts.add(ldapHostsStr); + } + + StringBuilder uriStr = new StringBuilder(); + try { + for (String host : ldapHosts) { + URI convertedUri = new URI(schemeToUse + "://" + host + "/"); + uriStr.append(convertedUri).append(' '); + } + } catch (URISyntaxException e) { + throw new UserDirectoryException("cannot convert IPA uri " + uri, e); + } + + Hashtable res = new Hashtable<>(); + res.put(UserAdminConf.uri.name(), uriStr.toString()); + addIpaConfig(kerberosRealm, res); + return res; + } } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java new file mode 100644 index 000000000..3e869f3b0 --- /dev/null +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java @@ -0,0 +1,147 @@ +package org.argeo.osgi.useradmin; + +import java.util.Dictionary; +import java.util.Hashtable; + +import javax.naming.CommunicationException; +import javax.naming.Context; +import javax.naming.NameNotFoundException; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.InitialLdapContext; +import javax.naming.ldap.LdapName; + +import org.argeo.naming.LdapAttrs; + +/** A synchronized wrapper for a single {@link InitialLdapContext}. */ +// TODO implement multiple contexts and connection pooling. +class LdapConnection { + private InitialLdapContext initialLdapContext = null; + + LdapConnection(String url, Dictionary properties) { + try { + Hashtable connEnv = new Hashtable(); + connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + connEnv.put(Context.PROVIDER_URL, url); + connEnv.put("java.naming.ldap.attributes.binary", LdapAttrs.userPassword.name()); + // use pooling in order to avoid connection timeout +// connEnv.put("com.sun.jndi.ldap.connect.pool", "true"); +// connEnv.put("com.sun.jndi.ldap.connect.pool.timeout", 300000); + + initialLdapContext = new InitialLdapContext(connEnv, null); + // StartTlsResponse tls = (StartTlsResponse) ctx + // .extendedOperation(new StartTlsRequest()); + // tls.negotiate(); + Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION); + if (securityAuthentication != null) + initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication); + else + initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple"); + Object principal = properties.get(Context.SECURITY_PRINCIPAL); + if (principal != null) { + initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString()); + Object creds = properties.get(Context.SECURITY_CREDENTIALS); + if (creds != null) { + initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString()); + } + } + } catch (Exception e) { + throw new UserDirectoryException("Cannot connect to LDAP", e); + } + + } + + public void init() { + + } + + public void destroy() { + try { + // tls.close(); + initialLdapContext.close(); + initialLdapContext = null; + } catch (NamingException e) { + e.printStackTrace(); + } + } + + protected InitialLdapContext getLdapContext() { + return initialLdapContext; + } + + protected void reconnect() throws NamingException { + initialLdapContext.reconnect(initialLdapContext.getConnectControls()); + } + + public synchronized NamingEnumeration search(LdapName searchBase, String searchFilter, + SearchControls searchControls) throws NamingException { + NamingEnumeration results; + try { + results = getLdapContext().search(searchBase, searchFilter, searchControls); + } catch (CommunicationException e) { + reconnect(); + results = getLdapContext().search(searchBase, searchFilter, searchControls); + } + return results; + } + + public synchronized Attributes getAttributes(LdapName name) throws NamingException { + try { + return getLdapContext().getAttributes(name); + } catch (CommunicationException e) { + reconnect(); + return getLdapContext().getAttributes(name); + } + } + + synchronized void prepareChanges(UserDirectoryWorkingCopy wc) throws NamingException { + // make sure connection will work + reconnect(); + + // delete + for (LdapName dn : wc.getDeletedUsers().keySet()) { + if (!entryExists(dn)) + throw new UserDirectoryException("User to delete no found " + dn); + } + // add + for (LdapName dn : wc.getNewUsers().keySet()) { + if (entryExists(dn)) + throw new UserDirectoryException("User to create found " + dn); + } + // modify + for (LdapName dn : wc.getModifiedUsers().keySet()) { + if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn)) + throw new UserDirectoryException("User to modify not found " + dn); + } + + } + + protected boolean entryExists(LdapName dn) throws NamingException { + try { + return getAttributes(dn).size() != 0; + } catch (NameNotFoundException e) { + return false; + } + } + + synchronized void commitChanges(UserDirectoryWorkingCopy wc) throws NamingException { + // delete + for (LdapName dn : wc.getDeletedUsers().keySet()) { + getLdapContext().destroySubcontext(dn); + } + // add + for (LdapName dn : wc.getNewUsers().keySet()) { + DirectoryUser user = wc.getNewUsers().get(dn); + getLdapContext().createSubcontext(dn, user.getAttributes()); + } + // modify + for (LdapName dn : wc.getModifiedUsers().keySet()) { + Attributes modifiedAttrs = wc.getModifiedUsers().get(dn); + getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs); + } + } +} diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java index 22c178ef4..2d9a87469 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java @@ -4,9 +4,9 @@ import static org.argeo.naming.LdapAttrs.objectClass; import java.util.ArrayList; import java.util.Dictionary; -import java.util.Hashtable; import java.util.List; +import javax.naming.AuthenticationNotSupportedException; import javax.naming.Binding; import javax.naming.Context; import javax.naming.InvalidNameException; @@ -15,14 +15,11 @@ import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; -import javax.naming.directory.DirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; -import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapName; import javax.transaction.TransactionManager; -import org.argeo.naming.LdapAttrs; import org.osgi.framework.Filter; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.User; @@ -32,53 +29,19 @@ import org.osgi.service.useradmin.User; * and an open transaction for write access. */ public class LdapUserAdmin extends AbstractUserDirectory { - private InitialLdapContext initialLdapContext = null; - -// private LdapName adminUserDn = null; -// private LdifUser adminUser = null; + private LdapConnection ldapConnection; public LdapUserAdmin(Dictionary properties) { - super(null, properties); - try { - Hashtable connEnv = new Hashtable(); - connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); - connEnv.put(Context.PROVIDER_URL, getUri().toString()); - connEnv.put("java.naming.ldap.attributes.binary", LdapAttrs.userPassword.name()); + this(properties, false); + } - initialLdapContext = new InitialLdapContext(connEnv, null); - // StartTlsResponse tls = (StartTlsResponse) ctx - // .extendedOperation(new StartTlsRequest()); - // tls.negotiate(); - Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION); - if (securityAuthentication != null) - initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication); - else - initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple"); - Object principal = properties.get(Context.SECURITY_PRINCIPAL); - if (principal != null) { - initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString()); -// adminUserDn = new LdapName(principal.toString()); -// BasicAttributes adminUserAttrs = new BasicAttributes(); -// adminUser = new LdifUser(this, adminUserDn, adminUserAttrs); - Object creds = properties.get(Context.SECURITY_CREDENTIALS); - if (creds != null) { - initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString()); -// adminUserAttrs.put(LdapAttrs.userPassword.name(), adminUser.hash(creds.toString().toCharArray())); - } -// adminUserAttrs.put(LdapAttrs.memberOf.name(), "cn=admin,ou=roles,ou=node"); - } - } catch (Exception e) { - throw new UserDirectoryException("Cannot connect to LDAP", e); - } + public LdapUserAdmin(Dictionary properties, boolean scoped) { + super(null, properties, scoped); + ldapConnection = new LdapConnection(getUri().toString(), properties); } public void destroy() { - try { - // tls.close(); - initialLdapContext.close(); - } catch (NamingException e) { - e.printStackTrace(); - } + ldapConnection.destroy(); } @Override @@ -97,12 +60,12 @@ public class LdapUserAdmin extends AbstractUserDirectory { } else { properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI"); } - return new LdapUserAdmin(properties); + return new LdapUserAdmin(properties, true); } - protected InitialLdapContext getLdapContext() { - return initialLdapContext; - } +// protected InitialLdapContext getLdapContext() { +// return initialLdapContext; +// } @Override protected Boolean daoHasRole(LdapName dn) { @@ -116,7 +79,7 @@ public class LdapUserAdmin extends AbstractUserDirectory { @Override protected DirectoryUser daoGetRole(LdapName name) throws NameNotFoundException { try { - Attributes attrs = getLdapContext().getAttributes(name); + Attributes attrs = ldapConnection.getAttributes(name); if (attrs.size() == 0) return null; int roleType = roleType(name); @@ -129,9 +92,6 @@ public class LdapUserAdmin extends AbstractUserDirectory { throw new UserDirectoryException("Unsupported LDAP type for " + name); return res; } catch (NameNotFoundException e) { -// if (adminUserDn != null && adminUserDn.equals(name)) { -// return adminUser; -// } throw e; } catch (NamingException e) { return null; @@ -149,7 +109,7 @@ public class LdapUserAdmin extends AbstractUserDirectory { searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); LdapName searchBase = getBaseDn(); - NamingEnumeration results = getLdapContext().search(searchBase, searchFilter, searchControls); + NamingEnumeration results = ldapConnection.search(searchBase, searchFilter, searchControls); results: while (results.hasMoreElements()) { SearchResult searchResult = results.next(); @@ -170,9 +130,12 @@ public class LdapUserAdmin extends AbstractUserDirectory { res.add(role); } return res; -// } catch (NameNotFoundException e) { -// return res; + } catch (AuthenticationNotSupportedException e) { + // ignore (typically an unsupported anonymous bind) + // TODO better logging + return res; } catch (Exception e) { + e.printStackTrace(); throw new UserDirectoryException("Cannot get roles for filter " + f, e); } } @@ -192,7 +155,7 @@ public class LdapUserAdmin extends AbstractUserDirectory { searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); LdapName searchBase = getBaseDn(); - NamingEnumeration results = getLdapContext().search(searchBase, searchFilter, searchControls); + NamingEnumeration results = ldapConnection.search(searchBase, searchFilter, searchControls); while (results.hasMoreElements()) { SearchResult searchResult = (SearchResult) results.nextElement(); @@ -207,52 +170,16 @@ public class LdapUserAdmin extends AbstractUserDirectory { @Override protected void prepare(UserDirectoryWorkingCopy wc) { try { - getLdapContext().reconnect(getLdapContext().getConnectControls()); - // delete - for (LdapName dn : wc.getDeletedUsers().keySet()) { - if (!entryExists(dn)) - throw new UserDirectoryException("User to delete no found " + dn); - } - // add - for (LdapName dn : wc.getNewUsers().keySet()) { - if (entryExists(dn)) - throw new UserDirectoryException("User to create found " + dn); - } - // modify - for (LdapName dn : wc.getModifiedUsers().keySet()) { - if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn)) - throw new UserDirectoryException("User to modify not found " + dn); - } + ldapConnection.prepareChanges(wc); } catch (NamingException e) { throw new UserDirectoryException("Cannot prepare LDAP", e); } } - private boolean entryExists(LdapName dn) throws NamingException { - try { - return getLdapContext().getAttributes(dn).size() != 0; - } catch (NameNotFoundException e) { - return false; - } - } - @Override protected void commit(UserDirectoryWorkingCopy wc) { try { - // delete - for (LdapName dn : wc.getDeletedUsers().keySet()) { - getLdapContext().destroySubcontext(dn); - } - // add - for (LdapName dn : wc.getNewUsers().keySet()) { - DirectoryUser user = wc.getNewUsers().get(dn); - getLdapContext().createSubcontext(dn, user.getAttributes()); - } - // modify - for (LdapName dn : wc.getModifiedUsers().keySet()) { - Attributes modifiedAttrs = wc.getModifiedUsers().get(dn); - getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs); - } + ldapConnection.commitChanges(wc); } catch (NamingException e) { throw new UserDirectoryException("Cannot commit LDAP", e); } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java index 970ab7162..832e8e578 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.Dictionary; @@ -40,15 +41,19 @@ public class LdifUserAdmin extends AbstractUserDirectory { private SortedMap groups = new TreeMap(); public LdifUserAdmin(String uri, String baseDn) { - this(fromUri(uri, baseDn)); + this(fromUri(uri, baseDn), false); } public LdifUserAdmin(Dictionary properties) { - super(null, properties); + this(properties, false); + } + + protected LdifUserAdmin(Dictionary properties, boolean scoped) { + super(null, properties, scoped); } public LdifUserAdmin(URI uri, Dictionary properties) { - super(uri, properties); + super(uri, properties, false); } @Override @@ -69,7 +74,7 @@ public class LdifUserAdmin extends AbstractUserDirectory { } Dictionary properties = cloneProperties(); properties.put(UserAdminConf.readOnly.name(), "true"); - LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties); + LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties, true); scopedUserAdmin.groups = Collections.unmodifiableSortedMap(groups); scopedUserAdmin.users = Collections.unmodifiableSortedMap(users); return scopedUserAdmin; @@ -83,13 +88,15 @@ public class LdifUserAdmin extends AbstractUserDirectory { } public void init() { + try { - if (getUri().getScheme().equals("file")) { - File file = new File(getUri()); + URI u = new URI(getUri()); + if (u.getScheme().equals("file")) { + File file = new File(u); if (!file.exists()) return; } - load(getUri().toURL().openStream()); + load(u.toURL().openStream()); } catch (Exception e) { throw new UserDirectoryException("Cannot open URL " + getUri(), e); } @@ -100,9 +107,9 @@ public class LdifUserAdmin extends AbstractUserDirectory { throw new UserDirectoryException("Cannot save LDIF user admin: no URI is set"); if (isReadOnly()) throw new UserDirectoryException("Cannot save LDIF user admin: " + getUri() + " is read-only"); - try (FileOutputStream out = new FileOutputStream(new File(getUri()))) { + try (FileOutputStream out = new FileOutputStream(new File(new URI(getUri())))) { save(out); - } catch (IOException e) { + } catch (IOException | URISyntaxException e) { throw new UserDirectoryException("Cannot save user admin to " + getUri(), e); } } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java index fd7826303..fe1ca7643 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java @@ -21,7 +21,7 @@ public class OsUserDirectory extends AbstractUserDirectory { private final LdifUser osUser; public OsUserDirectory(URI uriArg, Dictionary props) { - super(uriArg, props); + super(uriArg, props, false); try { osUserDn = new LdapName(LdapAttrs.uid.name() + "=" + osUsername + "," + getUserBase() + "," + getBaseDn()); Attributes attributes = new BasicAttributes(); @@ -53,7 +53,7 @@ public class OsUserDirectory extends AbstractUserDirectory { @Override protected List doGetRoles(Filter f) { List res = new ArrayList<>(); - if (f==null || f.match(osUser.getProperties())) + if (f == null || f.match(osUser.getProperties())) res.add(osUser); return res; } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java index 001d8e8bd..ec41978dc 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java @@ -1,6 +1,5 @@ package org.argeo.osgi.useradmin; -import java.io.IOException; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; @@ -11,10 +10,8 @@ import java.util.List; import java.util.Map; import javax.naming.Context; -import javax.naming.NamingException; import javax.naming.ldap.LdapName; -import org.argeo.naming.DnsBrowser; import org.argeo.naming.NamingUtils; /** Properties used to configure user admins. */ @@ -49,6 +46,7 @@ public enum UserAdminConf { public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config"; public final static String SCHEME_LDAP = "ldap"; + public final static String SCHEME_LDAPS = "ldaps"; public final static String SCHEME_FILE = "file"; public final static String SCHEME_OS = "os"; public final static String SCHEME_IPA = "ipa"; @@ -147,8 +145,8 @@ public enum UserAdminConf { URI u = new URI(uriStr); String scheme = u.getScheme(); if (scheme != null && scheme.equals(SCHEME_IPA)) { - u = convertIpaConfig(u); - scheme = u.getScheme(); + return IpaUtils.convertIpaUri(u); +// scheme = u.getScheme(); } String path = u.getPath(); // base DN @@ -166,7 +164,7 @@ public enum UserAdminConf { String principal = null; String credentials = null; if (scheme != null) - if (scheme.equals(SCHEME_LDAP) || scheme.equals("ldaps")) { + if (scheme.equals(SCHEME_LDAP) || scheme.equals(SCHEME_LDAPS)) { // TODO additional checks if (u.getUserInfo() != null) { String[] userInfo = u.getUserInfo().split(":"); @@ -210,49 +208,6 @@ public enum UserAdminConf { } } - private static URI convertIpaConfig(URI uri) { - String path = uri.getPath(); - String kerberosRealm; - if (path == null || path.length() <= 1) { - kerberosRealm = kerberosDomainFromDns(); - } else { - kerberosRealm = path.substring(1); - } - - if (kerberosRealm == null) - throw new UserDirectoryException("No Kerberos domain available for " + uri); - try (DnsBrowser dnsBrowser = new DnsBrowser()) { - String ldapHostsStr = uri.getHost(); - if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) { - List ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase()); - if (ldapHosts == null || ldapHosts.size() == 0) { - throw new UserDirectoryException("Cannot configure LDAP for IPA " + uri); - } else { - ldapHostsStr = ldapHosts.get(0); - } - } - URI convertedUri = new URI( - SCHEME_LDAP + "://" + ldapHostsStr + "/" + IpaUtils.domainToUserDirectoryConfigPath(kerberosRealm)); - return convertedUri; - } catch (NamingException | IOException | URISyntaxException e) { - throw new UserDirectoryException("cannot convert IPA uri " + uri, e); - } - } - - private static String kerberosDomainFromDns() { - String kerberosDomain; - try (DnsBrowser dnsBrowser = new DnsBrowser()) { - InetAddress localhost = InetAddress.getLocalHost(); - String hostname = localhost.getHostName(); - String dnsZone = hostname.substring(hostname.indexOf('.') + 1); - kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT"); - return kerberosDomain; - } catch (Exception e) { - throw new UserDirectoryException("Cannot determine Kerberos domain from DNS", e); - } - - } - private static String getBaseDnFromHostname() { String hostname; try { diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java index 525176d96..1630b6bd3 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java @@ -62,6 +62,7 @@ class WcXaResource implements XAResource { try { userDirectory.prepare(wc); } catch (Exception e) { + e.printStackTrace(); throw new XAException(XAException.XAER_RMERR); } return XA_OK; @@ -78,6 +79,7 @@ class WcXaResource implements XAResource { userDirectory.prepare(wc); userDirectory.commit(wc); } catch (Exception e) { + e.printStackTrace(); throw new XAException(XAException.XAER_RMERR); } finally { cleanUp(xid); @@ -90,6 +92,7 @@ class WcXaResource implements XAResource { checkXid(xid); userDirectory.rollback(wc(xid)); } catch (Exception e) { + e.printStackTrace(); throw new XAException(XAException.XAER_RMERR); } finally { cleanUp(xid); diff --git a/org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java b/org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java new file mode 100644 index 000000000..7cb586310 --- /dev/null +++ b/org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java @@ -0,0 +1,43 @@ +package org.argeo.osgi.util; + +import java.util.HashMap; +import java.util.Map; + +import org.osgi.resource.Namespace; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; + +public class FilterRequirement implements Requirement { + private String namespace; + private String filter; + + + + public FilterRequirement(String namespace, String filter) { + this.namespace = namespace; + this.filter = filter; + } + + @Override + public Resource getResource() { + return null; + } + + @Override + public String getNamespace() { + return namespace; + } + + @Override + public Map getDirectives() { + Map directives = new HashMap<>(); + directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter); + return directives; + } + + @Override + public Map getAttributes() { + return new HashMap<>(); + } + +} diff --git a/org.argeo.jcr/.classpath b/org.argeo.jcr/.classpath index 01836c484..d499d3059 100644 --- a/org.argeo.jcr/.classpath +++ b/org.argeo.jcr/.classpath @@ -2,6 +2,6 @@ - + diff --git a/org.argeo.jcr/src/org/argeo/jcr/Jcr.java b/org.argeo.jcr/src/org/argeo/jcr/Jcr.java index 31077737e..72e325d35 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/Jcr.java +++ b/org.argeo.jcr/src/org/argeo/jcr/Jcr.java @@ -902,6 +902,8 @@ public class Jcr { // QUERY /** Creates a JCR-SQL2 query using {@link MessageFormat}. */ public static Query createQuery(QueryManager qm, String sql, Object... args) { + // fix single quotes + sql = sql.replaceAll("'", "''"); String query = MessageFormat.format(sql, args); try { return qm.createQuery(query, Query.JCR_SQL2); diff --git a/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java b/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java index 56d959e08..3be8be184 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java +++ b/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java @@ -38,6 +38,7 @@ import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; @@ -1729,4 +1730,49 @@ public class JcrUtils { return new String(hexChars); } + /** Export a subtree as a compact XML without namespaces. */ + public static void toSimpleXml(Node node, StringBuilder sb) throws RepositoryException { + sb.append('<'); + String nodeName = node.getName(); + int colIndex = nodeName.indexOf(':'); + if (colIndex > 0) { + nodeName = nodeName.substring(colIndex + 1); + } + sb.append(nodeName); + PropertyIterator pit = node.getProperties(); + properties: while (pit.hasNext()) { + Property p = pit.nextProperty(); + // skip multiple properties + if (p.isMultiple()) + continue properties; + String propertyName = p.getName(); + int pcolIndex = propertyName.indexOf(':'); + // skip properties with namespaces + if (pcolIndex > 0) + continue properties; + // skip binaries + if (p.getType() == PropertyType.BINARY) { + continue properties; + // TODO retrieve identifier? + } + sb.append(' '); + sb.append(propertyName); + sb.append('='); + sb.append('\"').append(p.getString()).append('\"'); + } + + if (node.hasNodes()) { + sb.append('>'); + NodeIterator children = node.getNodes(); + while (children.hasNext()) { + toSimpleXml(children.nextNode(), sb); + } + sb.append("'); + } else { + sb.append("/>"); + } + } + } diff --git a/org.argeo.jcr/src/org/argeo/jcr/JcrxApi.java b/org.argeo.jcr/src/org/argeo/jcr/JcrxApi.java index 8f1ee9fa9..666b2593e 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/JcrxApi.java +++ b/org.argeo.jcr/src/org/argeo/jcr/JcrxApi.java @@ -30,22 +30,33 @@ public class JcrxApi { * XML */ /** - * Set as a subnode which will be exported as an XML element. + * Get the XML text of this child node. */ public static String getXmlValue(Node node, String name) { try { if (!node.hasNode(name)) return null; Node child = node.getNode(name); - if (!child.hasNode(Jcr.JCR_XMLTEXT)) + return getXmlValue(child); + } catch (RepositoryException e) { + throw new IllegalStateException("Cannot get " + name + " as XML text", e); + } + } + + /** + * Get the XML text of this node. + */ + public static String getXmlValue(Node node) { + try { + if (!node.hasNode(Jcr.JCR_XMLTEXT)) return null; - Node xmlText = child.getNode(Jcr.JCR_XMLTEXT); + Node xmlText = node.getNode(Jcr.JCR_XMLTEXT); if (!xmlText.hasProperty(Jcr.JCR_XMLCHARACTERS)) throw new IllegalArgumentException( "Node " + xmlText + " has no " + Jcr.JCR_XMLCHARACTERS + " property"); return xmlText.getProperty(Jcr.JCR_XMLCHARACTERS).getString(); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get " + name + " as XML text", e); + throw new IllegalStateException("Cannot get " + node + " as XML text", e); } } diff --git a/org.argeo.maintenance/.classpath b/org.argeo.maintenance/.classpath index eca7bdba8..e801ebfb4 100644 --- a/org.argeo.maintenance/.classpath +++ b/org.argeo.maintenance/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.maintenance/src/org/argeo/maintenance/AbstractMaintenanceService.java b/org.argeo.maintenance/src/org/argeo/maintenance/AbstractMaintenanceService.java index c756bd8e0..6003d638d 100644 --- a/org.argeo.maintenance/src/org/argeo/maintenance/AbstractMaintenanceService.java +++ b/org.argeo.maintenance/src/org/argeo/maintenance/AbstractMaintenanceService.java @@ -159,25 +159,27 @@ public abstract class AbstractMaintenanceService { } /** Add a user or group to a group. */ - protected void addToGroup(String roledDn, String groupDn) { - if (roledDn.contentEquals(groupDn)) { + protected void addToGroup(String groupToAddDn, String groupDn) { + if (groupToAddDn.contentEquals(groupDn)) { if (log.isTraceEnabled()) log.trace("Ignore adding group " + groupDn + " to itself"); return; } if (getUserAdmin() == null) { - log.warn("No user admin service available, cannot add group " + roledDn + " to " + groupDn); + log.warn("No user admin service available, cannot add group " + groupToAddDn + " to " + groupDn); return; } - Group managerGroup = (Group) getUserAdmin().getRole(roledDn); + Group groupToAdd = (Group) getUserAdmin().getRole(groupToAddDn); + if (groupToAdd == null) + throw new IllegalArgumentException("Group " + groupToAddDn + " not found"); Group group = (Group) getUserAdmin().getRole(groupDn); if (group == null) throw new IllegalArgumentException("Group " + groupDn + " not found"); try { getUserTransaction().begin(); - if (group.addMember(managerGroup)) - log.info("Added " + roledDn + " to " + group); + if (group.addMember(groupToAdd)) + log.info("Added " + groupToAddDn + " to " + group); getUserTransaction().commit(); } catch (Exception e) { try { @@ -185,7 +187,7 @@ public abstract class AbstractMaintenanceService { } catch (Exception e1) { // silent } - throw new IllegalStateException("Cannot add " + managerGroup + " to " + group); + throw new IllegalStateException("Cannot add " + groupToAddDn + " to " + groupDn); } } diff --git a/org.argeo.osgi.boot/.classpath b/org.argeo.osgi.boot/.classpath index a8a298a3c..190db038c 100644 --- a/org.argeo.osgi.boot/.classpath +++ b/org.argeo.osgi.boot/.classpath @@ -3,6 +3,6 @@ - +