Merge remote-tracking branch 'origin/unstable' into testing
authorMathieu <mbaudier@argeo.org>
Thu, 12 Jan 2023 07:29:01 +0000 (08:29 +0100)
committerMathieu <mbaudier@argeo.org>
Thu, 12 Jan 2023 07:29:01 +0000 (08:29 +0100)
30 files changed:
Makefile
org.argeo.cms.lib.dbus/.classpath [new file with mode: 0644]
org.argeo.cms.lib.dbus/.project [new file with mode: 0644]
org.argeo.cms.lib.dbus/OSGI-INF/cmsDBus.xml [new file with mode: 0644]
org.argeo.cms.lib.dbus/bnd.bnd [new file with mode: 0644]
org.argeo.cms.lib.dbus/build.properties [new file with mode: 0644]
org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBus.java [new file with mode: 0644]
org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBusImpl.java [new file with mode: 0644]
org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/FreeDesktopApplication.java [new file with mode: 0644]
org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/introspect-1.0.dtd [new file with mode: 0644]
org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/org.freedesktop.Application.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsEventBus.xml
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java
org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsSynchronousEventBusImpl.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java
swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDBusLauncher.xml [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd
swt/rcp/org.argeo.cms.swt.rcp/build.properties
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpDBusLauncher.java [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpFreeDesktopApplication.java [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/TestApplication.java [new file with mode: 0644]

index 330bc4fca94bd79b5de123e25dd24079678ad9c7..8c15c13072eab1e3b7760e395d3e2335a28fae0e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -17,6 +17,7 @@ org.argeo.cms \
 org.argeo.cms.ux \
 org.argeo.cms.ee \
 org.argeo.cms.lib.jetty \
+org.argeo.cms.lib.dbus \
 org.argeo.cms.lib.sshd \
 org.argeo.cms.cli \
 osgi/equinox/org.argeo.cms.lib.equinox \
diff --git a/org.argeo.cms.lib.dbus/.classpath b/org.argeo.cms.lib.dbus/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.cms.lib.dbus/.project b/org.argeo.cms.lib.dbus/.project
new file mode 100644 (file)
index 0000000..bf0398c
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.lib.dbus</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.cms.lib.dbus/OSGI-INF/cmsDBus.xml b/org.argeo.cms.lib.dbus/OSGI-INF/cmsDBus.xml
new file mode 100644 (file)
index 0000000..6797c3d
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="cmsDBus">
+   <implementation class="org.argeo.cms.dbus.CmsDBusImpl"/>
+   <service>
+      <provide interface="org.argeo.cms.dbus.CmsDBus"/>
+   </service>
+   <reference bind="setCmsEventBus" cardinality="1..1" interface="org.argeo.api.cms.CmsEventBus" name="CmsEventBus" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms.lib.dbus/bnd.bnd b/org.argeo.cms.lib.dbus/bnd.bnd
new file mode 100644 (file)
index 0000000..407c785
--- /dev/null
@@ -0,0 +1,5 @@
+Import-Package: \
+org.freedesktop.dbus.connections.transports,\
+*
+
+Service-Component: OSGI-INF/cmsDBus.xml
diff --git a/org.argeo.cms.lib.dbus/build.properties b/org.argeo.cms.lib.dbus/build.properties
new file mode 100644 (file)
index 0000000..76d7c98
--- /dev/null
@@ -0,0 +1,5 @@
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/cmsDBus.xml
+source.. = src/
+output.. = bin/
diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBus.java b/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBus.java
new file mode 100644 (file)
index 0000000..df76822
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.cms.dbus;
+
+import org.freedesktop.dbus.connections.impl.DBusConnection;
+
+public interface CmsDBus {
+       final static String DBUS_SESSION_BUS_ADDRESS = "DBUS_SESSION_BUS_ADDRESS";
+       
+       DBusConnection openSessionConnection();
+}
diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBusImpl.java b/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBusImpl.java
new file mode 100644 (file)
index 0000000..cf93964
--- /dev/null
@@ -0,0 +1,97 @@
+package org.argeo.cms.dbus;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsLog;
+import org.freedesktop.dbus.bin.EmbeddedDBusDaemon;
+import org.freedesktop.dbus.connections.BusAddress;
+import org.freedesktop.dbus.connections.impl.DBusConnection;
+import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder;
+import org.freedesktop.dbus.exceptions.DBusException;
+
+public class CmsDBusImpl implements CmsDBus {
+       private final static CmsLog log = CmsLog.getLog(CmsDBusImpl.class);
+
+       private BusAddress sessionBusAddress;
+
+       private EmbeddedDBusDaemon dBusDaemon;
+       private Path dBusDaemonSocket;
+
+       private CmsEventBus cmsEventBus;
+
+       public void start() {
+               try {
+                       final String envSessionBusAddress = System.getenv(DBUS_SESSION_BUS_ADDRESS);
+                       if (envSessionBusAddress != null) {
+                               sessionBusAddress = BusAddress.of(envSessionBusAddress);
+
+                               // !! We must first initialise a connection, otherwise there are classloader
+                               // issues later on
+                               try (DBusConnection dBusConnection = DBusConnectionBuilder.forAddress(sessionBusAddress)
+                                               .withShared(false).build()) {
+
+                               }
+                               log.debug(() -> "Found session DBus with address " + sessionBusAddress);
+                       } else {
+                               Path socketLocation = Paths.get(System.getProperty("user.home"), ".cache", "argeo", "bus");
+                               if (Files.exists(socketLocation))
+                                       Files.delete(socketLocation);
+                               else
+                                       Files.createDirectories(socketLocation.getParent());
+
+                               String embeddedSessionBusAddress = "unix:path=" + socketLocation.toString();
+                               dBusDaemon = new EmbeddedDBusDaemon(embeddedSessionBusAddress + ",listen=true");
+                               dBusDaemon.startInBackgroundAndWait(30 * 1000);
+                               dBusDaemonSocket = socketLocation;
+
+                               sessionBusAddress = BusAddress.of(embeddedSessionBusAddress);
+                               try (DBusConnection dBusConnection = DBusConnectionBuilder.forAddress(sessionBusAddress)
+                                               .withShared(false).build()) {
+
+                               }
+                               log.debug(() -> "Started embedded session DBus with address " + sessionBusAddress);
+
+                               // TODO set environment variable?
+                       }
+               } catch (DBusException | IOException e) {
+                       throw new IllegalStateException("Cannot find a session bus", e);
+               }
+       }
+
+       public void stop() {
+               if (dBusDaemon != null) {
+                       try {
+                               dBusDaemon.close();
+                       } catch (IOException e) {
+                               log.error("Cannot close embedded DBus daemon", e);
+                       }
+                       try {
+                               Files.delete(dBusDaemonSocket);
+                       } catch (IOException e) {
+                               log.error("Cannot delete DBus daemon socket " + dBusDaemonSocket, e);
+                       }
+               }
+       }
+
+       @Override
+       public DBusConnection openSessionConnection() {
+               try {
+                       DBusConnection dBusConnection = DBusConnectionBuilder.forAddress(sessionBusAddress).withShared(false)
+                                       .build();
+                       // TODO track all connections?
+                       return dBusConnection;
+               } catch (DBusException e) {
+                       e.printStackTrace();
+                       throw new IllegalStateException("Cannot open connection to session DBus", e);
+               }
+       }
+
+       public void setCmsEventBus(CmsEventBus cmsEventBus) {
+               this.cmsEventBus = cmsEventBus;
+       }
+
+}
diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/FreeDesktopApplication.java b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/FreeDesktopApplication.java
new file mode 100644 (file)
index 0000000..708ee61
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.cms.freedesktop;
+
+import java.util.List;
+import java.util.Map;
+
+import org.freedesktop.dbus.annotations.DBusInterfaceName;
+import org.freedesktop.dbus.annotations.DBusMemberName;
+import org.freedesktop.dbus.interfaces.DBusInterface;
+import org.freedesktop.dbus.types.Variant;
+
+/**
+ * The org.freedesktop.Application interface.
+ */
+@DBusInterfaceName("org.freedesktop.Application")
+public interface FreeDesktopApplication extends DBusInterface {
+
+       @DBusMemberName(value = "Activate")
+       void activate(Map<String, Variant<?>> platformData);
+
+       @DBusMemberName(value = "Open")
+       void open(List<String> uris, Map<String, Variant<?>> platformData);
+
+       @DBusMemberName(value = "ActivateAction")
+       void activateAction(String actionName, List<Variant<?>> parameter, Map<String, Variant<?>> platformData);
+
+}
\ No newline at end of file
diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/introspect-1.0.dtd b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/introspect-1.0.dtd
new file mode 100644 (file)
index 0000000..ba263d3
--- /dev/null
@@ -0,0 +1,37 @@
+<!-- DTD for D-Bus Introspection data -->
+<!-- (C) 2005-02-02 David A. Wheeler; released under the D-Bus licenses,
+         GNU GPL version 2 (or greater) and AFL 1.1 (or greater) -->
+
+<!-- see D-Bus specification for documentation -->
+
+<!ELEMENT node (node|interface)*>
+<!ATTLIST node name CDATA #IMPLIED>
+
+<!ELEMENT interface (method|signal|property|annotation)*>
+<!ATTLIST interface name CDATA #REQUIRED>
+
+<!ELEMENT method (arg|annotation)*>
+<!ATTLIST method name CDATA #REQUIRED>
+
+<!ELEMENT signal (arg|annotation)*>
+<!ATTLIST signal name CDATA #REQUIRED>
+
+<!ELEMENT arg EMPTY>
+<!ATTLIST arg name CDATA #IMPLIED>
+<!ATTLIST arg type CDATA #REQUIRED>
+<!-- Method arguments SHOULD include "direction",
+     while signal and error arguments SHOULD not (since there's no point).
+     The DTD format can't express that subtlety. -->
+<!ATTLIST arg direction (in|out) "in">
+
+<!-- AKA "attribute" -->
+<!ELEMENT property (annotation)*>
+<!ATTLIST property name CDATA #REQUIRED>
+<!ATTLIST property type CDATA #REQUIRED> 
+<!ATTLIST property access (read|write|readwrite) #REQUIRED>
+
+<!ELEMENT annotation EMPTY>  <!-- Generic metadata -->
+<!ATTLIST annotation name CDATA #REQUIRED>
+<!ATTLIST annotation value CDATA #REQUIRED>
+
+
diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/org.freedesktop.Application.xml b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/org.freedesktop.Application.xml
new file mode 100644 (file)
index 0000000..5af11f7
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE node SYSTEM "introspect-1.0.dtd">
+<!-- See https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#dbus -->
+<node>
+       <interface name="org.freedesktop.Application">
+               <method name="Activate">
+                       <arg type="a{sv}" name="platform-data" direction="in" />
+               </method>
+               <method name="Open">
+                       <arg type="as" name="uris" direction="in" />
+                       <arg type="a{sv}" name="platform-data" direction="in" />
+               </method>
+               <method name="ActivateAction">
+                       <arg type="s" name="action-name" direction="in" />
+                       <arg type="av" name="parameter" direction="in" />
+                       <arg type="a{sv}" name="platform-data" direction="in" />
+               </method>
+       </interface>
+</node>
\ No newline at end of file
index 6bb67ceedc3603b6274cb0b02b63a42f19d23589..2c4a76698937a2892f7a2a504379dff6bd17a23d 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="CMS Event Bus">
-   <implementation class="org.argeo.cms.internal.runtime.CmsEventBusImpl"/>
+   <implementation class="org.argeo.cms.internal.runtime.CmsSynchronousEventBusImpl"/>
    <service>
       <provide interface="org.argeo.api.cms.CmsEventBus"/>
    </service>
index 43cae85721bf6b96c3f577f157f64b56845c11b2..5920c420370c78a4051f9000820af7d16c1c34da 100644 (file)
@@ -90,7 +90,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                } else {
 
                        // TODO should we support prefixed name for known types?
-                       QName providerName = NamespaceUtils.parsePrefixedName(provider, path.getFileName().toString());
+                       QName providerName = provider.fromFsPrefixedName(path.getFileName().toString());
 //                     QName providerName = new QName(path.getFileName().toString());
                        // TODO remove extension if mounted?
                        this.name = new ContentName(providerName, session);
@@ -126,7 +126,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                        } else {
                                UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path,
                                                UserDefinedFileAttributeView.class);
-                               String prefixedName = NamespaceUtils.toPrefixedName(provider, key);
+                               String prefixedName = provider.toFsPrefixedName(key);
                                if (!udfav.list().contains(prefixedName))
                                        return Optional.empty();
                                ByteBuffer buf = ByteBuffer.allocate(udfav.size(prefixedName));
@@ -198,7 +198,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                if (udfav != null) {
                        try {
                                for (String name : udfav.list()) {
-                                       QName providerName = NamespaceUtils.parsePrefixedName(provider, name);
+                                       QName providerName = provider.fromFsPrefixedName(name);
                                        if (providerName.getNamespaceURI().equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI))
                                                continue; // skip prefix mapping
                                        QName sessionName = new ContentName(providerName, getSession());
@@ -215,7 +215,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
        protected void removeAttr(QName key) {
                UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
                try {
-                       udfav.delete(NamespaceUtils.toPrefixedName(provider, key));
+                       udfav.delete(provider.toFsPrefixedName(key));
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
                }
@@ -239,7 +239,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
                ByteBuffer bb = ByteBuffer.wrap(toWrite.getBytes(StandardCharsets.UTF_8));
                try {
-                       udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb);
+                       udfav.write(provider.toFsPrefixedName(key), bb);
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
                }
@@ -250,7 +250,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                if (POSIX_KEYS.containsKey(key))
                        return POSIX_KEYS.get(key);
                else
-                       return USER_ + NamespaceUtils.toPrefixedName(provider, key);
+                       return USER_ + provider.toFsPrefixedName(key);
        }
 
        /*
@@ -285,7 +285,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
        public Content add(QName name, QName... classes) {
                FsContent fsContent;
                try {
-                       Path newPath = path.resolve(NamespaceUtils.toPrefixedName(provider, name));
+                       Path newPath = path.resolve(provider.toFsPrefixedName(name));
                        if (ContentName.contains(classes, DName.collection.qName()))
                                Files.createDirectory(newPath);
                        else
index 9b1b9668303f066e2106de3aa4a296edcabbf55d..47cd64af343dc2aec976cb554b82be229c6547ad 100644 (file)
@@ -13,6 +13,8 @@ import java.util.Objects;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
 
+import javax.xml.namespace.QName;
+
 import org.argeo.api.acr.ArgeoNamespace;
 import org.argeo.api.acr.ContentResourceException;
 import org.argeo.api.acr.NamespaceUtils;
@@ -20,28 +22,36 @@ import org.argeo.api.acr.RuntimeNamespaceContext;
 import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.util.OS;
 
 /** Access a file system as a {@link ContentProvider}. */
 public class FsContentProvider implements ContentProvider {
-       final static String XMLNS_ = "xmlns:";
 
        protected String mountPath;
        protected Path rootPath;
 
        private NavigableMap<String, String> prefixes = new TreeMap<>();
 
+       private final boolean isNtfs;
+       private final String XMLNS_;
+
        public FsContentProvider(String mountPath, Path rootPath) {
                Objects.requireNonNull(mountPath);
                Objects.requireNonNull(rootPath);
 
                this.mountPath = mountPath;
                this.rootPath = rootPath;
+
+               this.isNtfs = OS.LOCAL.isMSWindows();
+               this.XMLNS_ = isNtfs ? "xmlns%3A" : "xmlns:";
+
                // FIXME make it more robust
                initNamespaces();
        }
 
        protected FsContentProvider() {
-
+               this.isNtfs = OS.LOCAL.isMSWindows();
+               this.XMLNS_ = isNtfs ? "xmlns%3A" : "xmlns:";
        }
 
        protected void initNamespaces() {
@@ -117,6 +127,27 @@ public class FsContentProvider implements ContentProvider {
                return Files.exists(rootPath.resolve(relativePath));
        }
 
+       /*
+        * ATTRIBUTE NAMES
+        */
+       /**
+        * Make sure that the prefixed name is compatible with the underlying file
+        * system for file names/attributes (NTFS does not accept :)
+        */
+       String toFsPrefixedName(QName key) {
+               return isNtfs ? NamespaceUtils.toPrefixedName(this, key).replace(":", "%3A")
+                               : NamespaceUtils.toPrefixedName(this, key);
+       }
+
+       /**
+        * PArse a prefixed name which is compatible with the underlying file system for
+        * file names/attributes (NTFS does not accept :)
+        */
+       QName fromFsPrefixedName(String name) {
+               return isNtfs ? NamespaceUtils.parsePrefixedName(this, name.replace("%3A", ":"))
+                               : NamespaceUtils.parsePrefixedName(this, name);
+       }
+
        /*
         * NAMESPACE CONTEXT
         */
index 289f8dcc65eabf2e101c0af6208bbb2ead05ff90..37992072482e0cf36b1a08ed899de0afcf2f8601 100644 (file)
@@ -45,6 +45,7 @@ class CmsAuthUtils {
        final static String SHARED_STATE_CERTIFICATE_CHAIN = "org.argeo.cms.auth.certificateChain";
        final static String SHARED_STATE_REMOTE_ADDR = "org.argeo.cms.auth.remote.addr";
        final static String SHARED_STATE_REMOTE_PORT = "org.argeo.cms.auth.remote.port";
+       final static String SHARED_STATE_OS_USERNAME = "org.argeo.cms.os.username";
 
        final static String SINGLE_USER_LOCAL_ID = "single-user";
 
index 4b36f28abb90f927df087b743f81c09852241319..10e091ead0a4953e2236f2ad4860c5d04ba277c2 100644 (file)
@@ -7,6 +7,7 @@ import javax.naming.ldap.LdapName;
 import javax.security.auth.Subject;
 import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.login.CredentialException;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 import javax.security.auth.x500.X500Principal;
@@ -35,8 +36,12 @@ public class SingleUserLoginModule implements LoginModule {
        @Override
        public boolean login() throws LoginException {
                String username = System.getProperty("user.name");
-               if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME))
-                       sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, username);
+               if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_OS_USERNAME)
+                               && !username.equals(sharedState.get(CmsAuthUtils.SHARED_STATE_OS_USERNAME)))
+                       throw new CredentialException(
+                                       "OS username already set with " + sharedState.get(CmsAuthUtils.SHARED_STATE_OS_USERNAME));
+               if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_OS_USERNAME))
+                       sharedState.put(CmsAuthUtils.SHARED_STATE_OS_USERNAME, username);
                return true;
        }
 
@@ -49,7 +54,7 @@ public class SingleUserLoginModule implements LoginModule {
                        X500Principal principal = new X500Principal(userDn.toString());
                        authorizationName = principal.getName();
                } else {
-                       Object username = sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
+                       Object username = sharedState.get(CmsAuthUtils.SHARED_STATE_OS_USERNAME);
                        if (username == null)
                                throw new LoginException("No username available");
                        String hostname = CmsContextImpl.getCmsContext().getCmsState().getHostname();
index 2b5c41ddf8333395ddccf823d14124b5000ddcbf..47b36f446f003744d61d4c56e3ccc31ba6a122f6 100644 (file)
@@ -103,6 +103,11 @@ public class UserAdminLoginModule implements LoginModule {
                        username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
                        password = null;
                        preauth = true;
+               } else if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_OS_USERNAME)) {
+                       // single user, we assume Kerberos or other mean for commit
+                       username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_OS_USERNAME);
+                       password = null;
+                       preauth = true;
                } else {
 
                        // ask for username and password
@@ -205,7 +210,7 @@ public class UserAdminLoginModule implements LoginModule {
 //             }
                UserAdmin userAdmin = CmsContextImpl.getCmsContext().getUserAdmin();
                Authorization authorization;
-               if (callbackHandler == null) {// anonymous
+               if (callbackHandler == null && !sharedState.containsKey(CmsAuthUtils.SHARED_STATE_OS_USERNAME)) {// anonymous
                        authorization = userAdmin.getAuthorization(null);
                } else if (bindAuthorization != null) {// bind
                        authorization = bindAuthorization;
@@ -277,7 +282,7 @@ public class UserAdminLoginModule implements LoginModule {
 
                if (log.isDebugEnabled()) {
                        StringBuilder msg = new StringBuilder();
-                       msg.append("Logged in to CMS: " + authorization.getName() + "(" + authorization + ")\n");
+                       msg.append("Logged in to CMS: '" + authorization + "' (" + authorization.getName() + ")\n");
                        for (Principal principal : subject.getPrincipals()) {
                                msg.append("  Principal: " + principal.getName()).append(" (")
                                                .append(principal.getClass().getSimpleName()).append(")\n");
index 9d9e9bcfa58c6e662db3bc4065b67a813c3d2412..07af1b8494f52b41c9e5c705a8e1c3cef90e5324 100644 (file)
@@ -10,7 +10,7 @@ import org.argeo.api.cms.CmsEventBus;
 import org.argeo.api.cms.CmsEventSubscriber;
 import org.argeo.api.cms.CmsLog;
 
-/** {@link CmsEventBus} implementation based on {@link Flow}. */
+/** An asynchronous {@link CmsEventBus} implementation based on {@link Flow}. */
 public class CmsEventBusImpl implements CmsEventBus {
        private final static CmsLog log = CmsLog.getLog(CmsEventBus.class);
 
index d364620f56f09118a99cd36564146c1f61919a8f..c1f92deb40b3ea7743098bcb0c8e4bbdacc028af 100644 (file)
@@ -39,6 +39,7 @@ import org.argeo.api.uuid.UuidFactory;
 import org.argeo.cms.CmsDeployProperty;
 import org.argeo.cms.auth.ident.IdentClient;
 import org.argeo.cms.util.FsUtils;
+import org.argeo.cms.util.OS;
 
 /**
  * Implementation of a {@link CmsState}, initialising the required services.
@@ -168,7 +169,8 @@ public class CmsStateImpl implements CmsState {
                        try {
                                if (!Files.exists(privateDir))
                                        Files.createDirectories(privateDir);
-                               Files.setPosixFilePermissions(privateDir, posixPermissions);
+                               if (!OS.LOCAL.isMSWindows())
+                                       Files.setPosixFilePermissions(privateDir, posixPermissions);
                        } catch (IOException e) {
                                log.error("Cannot set permissions on " + privateDir, e);
                        }
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsSynchronousEventBusImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsSynchronousEventBusImpl.java
new file mode 100644 (file)
index 0000000..f7db374
--- /dev/null
@@ -0,0 +1,76 @@
+package org.argeo.cms.internal.runtime;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsEventSubscriber;
+import org.argeo.api.cms.CmsLog;
+
+/** A simple synchronous {@link CmsEventBus} implementation. */
+public class CmsSynchronousEventBusImpl implements CmsEventBus {
+       private final static CmsLog log = CmsLog.getLog(CmsSynchronousEventBusImpl.class);
+
+       private final Map<String, List<CmsEventSubscriber>> subscribers;
+
+       public CmsSynchronousEventBusImpl() {
+               subscribers = Collections.synchronizedMap(new HashMap<>());
+       }
+
+       @Override
+       public void sendEvent(String topic, Map<String, Object> event) {
+               List<CmsEventSubscriber> subscribersOfTopic = subscribers.get(topic);
+               if (subscribersOfTopic == null) // no one cares
+                       return;
+               synchronized (subscribersOfTopic) {
+                       for (Iterator<CmsEventSubscriber> it = subscribersOfTopic.iterator(); it.hasNext();) {
+                               CmsEventSubscriber subscriber = it.next();
+                               try {
+                                       subscriber.onEvent(topic, event);
+                               } catch (Throwable e) {
+                                       log.error("Cannot process in topic " + topic + " the event " + event + " for subscriber "
+                                                       + subscriber, e);
+                               }
+                       }
+               }
+               log.trace(() -> "Dispatched event in topic " + topic + ": " + event);
+       }
+
+       @Override
+       public synchronized void addEventSubscriber(String topic, CmsEventSubscriber eventSubscriber) {
+               if (!subscribers.containsKey(topic)) {
+                       subscribers.put(topic, new ArrayList<>());
+               }
+               subscribers.get(topic).add(eventSubscriber);
+               log.debug(() -> "Added subscriber " + eventSubscriber + " to topic " + topic);
+       }
+
+       @Override
+       public synchronized void removeEventSubscriber(String topic, CmsEventSubscriber eventSubscriber) {
+               List<CmsEventSubscriber> subscribersOfTopic = subscribers.get(topic);
+               assert subscribersOfTopic != null;
+               if (subscribersOfTopic != null) {
+                       CmsEventSubscriber removedSubscriber = null;
+                       synchronized (subscribersOfTopic) {
+                               subscribersOfTopic: for (Iterator<CmsEventSubscriber> it = subscribersOfTopic.iterator(); it
+                                               .hasNext();) {
+                                       CmsEventSubscriber subscriber = it.next();
+                                       if (subscriber == eventSubscriber) {
+                                               it.remove();
+                                               removedSubscriber = subscriber;
+                                               log.debug(() -> "Removed subscriber " + eventSubscriber + " from topic " + topic);
+                                               break subscribersOfTopic;
+                                       }
+                               }
+                       }
+                       if (removedSubscriber == null)
+                               log.warn(() -> "Subscriber " + eventSubscriber + " not found (and therefore not removed) in topic "
+                                               + topic);
+               }
+       }
+
+}
index 0fd0a63edb4cb47acce19bbb38f4fc07b4edd672..bb1f6112a927616caec811bb98d61321fc7319ea 100644 (file)
@@ -9,9 +9,10 @@ import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.cms.acr.CmsContentRepository;
 import org.argeo.cms.acr.directory.DirectoryContentProvider;
 import org.argeo.cms.acr.fs.FsContentProvider;
+import org.argeo.cms.util.OS;
 
 public class DeployedContentRepository extends CmsContentRepository {
-       private final static String ROOT_XML = "cr:root.xml";
+       private final static String ROOT_XML = OS.LOCAL.isMSWindows() ? "cr%3Aroot.xml" : "cr:root.xml";
 
        private final static CmsLog log = CmsLog.getLog(DeployedContentRepository.class);
 
@@ -22,7 +23,8 @@ public class DeployedContentRepository extends CmsContentRepository {
                long begin = System.currentTimeMillis();
                try {
                        super.start();
-                       Path rootXml = KernelUtils.getOsgiInstancePath(ROOT_XML);
+                       // FIXME does not work on Windows
+                       //Path rootXml = KernelUtils.getOsgiInstancePath(ROOT_XML);
                        initRootContentProvider(null);
 
 //             Path srvPath = KernelUtils.getOsgiInstancePath(CmsConstants.SRV_WORKSPACE);
index 51db582c69c091bdd7aa79036ff21005b0a9c7e8..0ef142f4aed07132db1d29a7145a1df5b5ede851 100644 (file)
@@ -32,7 +32,8 @@ SINGLE_USER {
     com.sun.security.auth.module.Krb5LoginModule optional
      storeKey=true
      useTicketCache=true;
-    org.argeo.cms.auth.SingleUserLoginModule requisite;
+    org.argeo.cms.auth.SingleUserLoginModule required;
+    org.argeo.cms.auth.UserAdminLoginModule optional;
 };
 
 Jackrabbit {
index f71878060e3d0bf399594f5e065c5e8af7cfc545..6cf4ebf5e00dbf17229d5348c965f4f5e444f12a 100644 (file)
@@ -22,7 +22,7 @@ public class OsUserUtils {
        public static LoginContext loginAsSystemUser(Subject subject) {
                try {
                        URL jaasConfigurationUrl = OsUserUtils.class.getClassLoader()
-                                       .getResource("org/argeo/osgi/useradmin/jaas-os.cfg");
+                                       .getResource("org/argeo/cms/osgi/useradmin/jaas-os.cfg");
                        URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
                        Configuration jaasConfiguration = Configuration.getInstance("JavaLoginConfig", uriParameter);
                        LoginContext lc = new LoginContext(isWindows() ? LOGIN_CONTEXT_USER_NT : LOGIN_CONTEXT_USER_NIX, subject,
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDBusLauncher.xml b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDBusLauncher.xml
new file mode 100644 (file)
index 0000000..e8bfefb
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="cmsRcpDBusLauncher">
+   <implementation class="org.argeo.cms.ui.rcp.dbus.CmsRcpDBusLauncher"/>
+   <reference bind="setCmsDBus" cardinality="1..1" interface="org.argeo.cms.dbus.CmsDBus" name="CmsDBus" policy="static"/>
+   <reference bind="addCmsApp" cardinality="0..n" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic" unbind="removeCmsApp"/>
+</scr:component>
index 5cf5164e1b69f8c09156c2ee14ec4e29367405df..e1735f03cd5e850df581fa66fbcdb345d858c205 100644 (file)
@@ -5,9 +5,11 @@ org.argeo.cms.auth,\
 org.eclipse.swt,\
 org.eclipse.swt.graphics,\
 org.w3c.css.sac,\
+org.freedesktop.dbus.connections,\
 *
 
 Service-Component:\
 OSGI-INF/cmsRcpDisplayFactory.xml,\
-OSGI-INF/cmsRcpHttpLauncher.xml
+OSGI-INF/cmsRcpDBusLauncher.xml
+#OSGI-INF/cmsRcpHttpLauncher.xml
 
index 4ed1d4748afc3e7dc121f8f0fab5a1c1a56d24e3..f85c0d8ca5f1d4168fd5a62a4f046447dce18088 100644 (file)
@@ -1,6 +1,8 @@
-output.. = bin/
 bin.includes = META-INF/,\
                .,\
                OSGI-INF/,\
-               OSGI-INF/cmsRcpHttpLauncher.xml
+               OSGI-INF/cmsRcpHttpLauncher.xml,\
+               OSGI-INF/cmsRcpDBusLauncher.xml
+additional.bundles = org.slf4j.api
 source.. = src/
+output.. = bin/
index a83a54db3926cfa2503079c22d3224dff28a8591..63a1fd84ebb76ce6dc7f9da3d9d924e807871143 100644 (file)
@@ -67,7 +67,8 @@ public class CmsRcpDisplayFactory {
                                display = null;
                        } catch (UnsatisfiedLinkError e) {
                                logger.log(Level.ERROR,
-                                               "Cannot load SWT, probably because the OSGi framework has been refresh. Restart the application.",
+                                               "Cannot load SWT, either because the SWT DLLs are no in the java.library.path,"
+                                                               + " or because the OSGi framework has been refreshed." + " Restart the application.",
                                                e);
                        }
                }
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpDBusLauncher.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpDBusLauncher.java
new file mode 100644 (file)
index 0000000..ea69057
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.cms.ui.rcp.dbus;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.cms.dbus.CmsDBus;
+
+public class CmsRcpDBusLauncher {
+       private CompletableFuture<CmsDBus> cmsDBus = new CompletableFuture<>();
+
+       private Map<String, CmsRcpFreeDesktopApplication> apps = new HashMap<>();
+
+       public void start() {
+
+       }
+
+       public void stop() {
+
+       }
+
+       public void addCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               final String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               cmsDBus.thenAcceptAsync((cmsDBus) -> {
+                       CmsRcpFreeDesktopApplication application = new CmsRcpFreeDesktopApplication(cmsDBus, contextName, cmsApp);
+                       apps.put(contextName, application);
+               });
+       }
+
+       public void removeCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               final String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               CmsRcpFreeDesktopApplication application = apps.remove(contextName);
+               if (application != null) {
+                       try {
+                               application.close();
+                       } catch (IOException e) {
+                               throw new IllegalStateException("Cannot remove CMS RCP app " + contextName, e);
+                       }
+               }
+       }
+
+       public void setCmsDBus(CmsDBus cmsDBus) {
+               this.cmsDBus.complete(cmsDBus);
+       }
+
+}
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpFreeDesktopApplication.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpFreeDesktopApplication.java
new file mode 100644 (file)
index 0000000..98c84fa
--- /dev/null
@@ -0,0 +1,68 @@
+package org.argeo.cms.ui.rcp.dbus;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.cms.dbus.CmsDBus;
+import org.argeo.cms.freedesktop.FreeDesktopApplication;
+import org.argeo.cms.ui.rcp.CmsRcpDisplayFactory;
+import org.freedesktop.dbus.connections.impl.DBusConnection;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.types.Variant;
+
+public class CmsRcpFreeDesktopApplication implements FreeDesktopApplication, Closeable {
+       private String path;
+
+       private CmsApp cmsApp;
+
+       private DBusConnection dBusConnection;
+
+       public CmsRcpFreeDesktopApplication(CmsDBus cmsDBus, String contextName, CmsApp cmsApp) {
+               // TODO find a better prefix and/or make it customisable
+               this.path = "/org/argeo/cms/" + contextName;
+               this.cmsApp = cmsApp;
+               try {
+                       String appName = path.replace('/', '.').substring(1);
+                       dBusConnection = cmsDBus.openSessionConnection();
+                       dBusConnection.requestBusName(appName);
+                       dBusConnection.exportObject(getObjectPath(), this);
+               } catch (DBusException e) {
+                       throw new IllegalStateException("Cannot add CMS app " + path, e);
+               }
+       }
+
+       @Override
+       public String getObjectPath() {
+               return path;
+       }
+
+       @Override
+       public void close() throws IOException {
+               if (dBusConnection != null)
+                       dBusConnection.close();
+       }
+
+       @Override
+       public void activate(Map<String, Variant<?>> platformData) {
+               // String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) :
+               // "";
+               String uiName = "app";
+               CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
+       }
+
+       @Override
+       public void open(List<String> uris, Map<String, Variant<?>> platformData) {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public void activateAction(String actionName, List<Variant<?>> parameter, Map<String, Variant<?>> platformData) {
+               // TODO Auto-generated method stub
+
+       }
+
+}
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/TestApplication.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/TestApplication.java
new file mode 100644 (file)
index 0000000..8f2da98
--- /dev/null
@@ -0,0 +1,96 @@
+package org.argeo.cms.ui.rcp.dbus;
+
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.argeo.cms.freedesktop.FreeDesktopApplication;
+import org.freedesktop.dbus.connections.impl.DBusConnection;
+import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.types.Variant;
+
+public class TestApplication implements FreeDesktopApplication {
+       private DBusConnection dBusConnection;
+
+       private final Display display;
+
+       private Shell shell = null;
+
+       private Text text;
+
+       public TestApplication() throws DBusException {
+               display = Display.getCurrent();
+
+               /* Get a connection to the session bus so we can request a bus name */
+               dBusConnection = DBusConnectionBuilder.forSessionBus().build();
+//             m_conn = DBusConnectionBuilder.forAddress("unix:path=/tmp/dbus-80908265778467677465").build();
+//             m_conn = DBusConnectionBuilder.forAddress("tcp:host=localhost,port=55556").build();
+               /* Request a unique bus name */
+               dBusConnection.requestBusName("org.argeo.TestApplication");
+               /* Export this object onto the bus using the path '/' */
+               dBusConnection.exportObject(getObjectPath(), this);
+       }
+
+       @Override
+       public String getObjectPath() {
+               return "/org/argeo/TestApplication";
+       }
+
+       @Override
+       public void activate(Map<String, Variant<?>> platformData) {
+               display.syncExec(() -> {
+                       shellVisible();
+               });
+
+       }
+
+       protected void shellVisible() {
+               if (shell == null || shell.isDisposed()) {
+                       shell = new Shell(display);
+                       shell.setLayout(new GridLayout());
+                       text = new Text(shell, SWT.MULTI | SWT.WRAP);
+                       text.setLayoutData(CmsSwtUtils.fillAll());
+                       text.setText("New shell\n");
+                       shell.open();
+               } else {
+               }
+               shell.forceActive();
+       }
+
+       @Override
+       public void open(List<String> uris, Map<String, Variant<?>> platformData) {
+               display.syncExec(() -> {
+                       shellVisible();
+                       for (String uri : uris) {
+                               text.append(uri);
+                               text.append("\n");
+                       }
+                       shell.forceActive();
+               });
+       }
+
+       @Override
+       public void activateAction(String actionName, List<Variant<?>> parameter, Map<String, Variant<?>> platformData) {
+               display.syncExec(() -> {
+                       shellVisible();
+                       text.append("Execute action '" + actionName + "' with arguments " + parameter);
+                       text.append("\n");
+               });
+       }
+
+       public static void main(String[] args) throws DBusException {
+               Display display = new Display();
+               new TestApplication();
+               while (!display.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+               display.dispose();
+       }
+}