Refactor CMS life cycle.
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 9 Jan 2022 07:42:55 +0000 (08:42 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 9 Jan 2022 07:42:55 +0000 (08:42 +0100)
62 files changed:
org.argeo.api/src/org/argeo/api/cms/CmsContext.java
org.argeo.api/src/org/argeo/api/cms/CmsDeployment.java
org.argeo.api/src/org/argeo/api/cms/CmsState.java
org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java
org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java
org.argeo.cms/OSGI-INF/cmsContext.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsDeployment.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsState.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/deployConfig.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/nodeUserAdmin.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/simpleTransactionManager.xml [new file with mode: 0644]
org.argeo.cms/bnd.bnd
org.argeo.cms/build.properties
org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java
org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java
org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/.gitignore [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsContextImpl.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeploymentImpl.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsShutdown.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsStateImpl.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/GogoShellKiller.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/SecurityProfile.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/dc=example,dc=com.ldif [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/example-ou=roles,ou=node.ldif [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas-ipa.cfg [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=roles,ou=node.ldif [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=tokens,ou=node.ldif [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsShutdown.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeLogger.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/osgi/SecurityProfile.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/.gitignore [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/GogoShellKiller.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/dc=example,dc=com.ldif [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/example-ou=roles,ou=node.ldif [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=roles,ou=node.ldif [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=tokens,ou=node.ldif [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java

index a9616c8ae6ac6107bd3e1d516c628adc98b5b98e..fa26b253a2121f6f67f103966657460729fcb898 100644 (file)
@@ -1,5 +1,8 @@
 package org.argeo.api.cms;
 
+import java.util.List;
+import java.util.Locale;
+
 /**
  * A logical view on this CMS instance, independently of a particular launch or
  * deployment.
@@ -11,6 +14,13 @@ public interface CmsContext {
         */
        public final static String WORKGROUP = "workgroup";
 
+       Locale getDefaultLocale();
+
+       List<Locale> getLocales();
+
+       Long getAvailableSince();
+
+       
        /** Mark this group as a workgroup */
        void createWorkgroup(String groupDn);
 }
index 9498f96f3d3ccae9f26d8e7720db098bbc28bf23..5893d2ec52553179bc5ebe4ad32d894d0d99e5e0 100644 (file)
@@ -4,9 +4,8 @@ import java.util.Dictionary;
 
 /** A configured node deployment. */
 public interface CmsDeployment {
-       Long getAvailableSince();
-       
+
        void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props);
-       Dictionary<String, Object> getProps(String factoryPid, String cn);
 
+       Dictionary<String, Object> getProps(String factoryPid, String cn);
 }
index 26427003e0b117a8bac4d0d361b8a9933a3ec492..ed8698fcaccdb2aabe25ad6fd1de360c779aecfc 100644 (file)
@@ -1,14 +1,7 @@
 package org.argeo.api.cms;
 
-import java.util.List;
-import java.util.Locale;
-
 /** A running node process. */
 public interface CmsState {
-       Locale getDefaultLocale();
-
-       List<Locale> getLocales();
-
        String getHostname();
 
        Long getAvailableSince();
index 6aaa1692f35c637d14d3c4f05dcfe26b412bcc96..e713f53e1aac9fed254b1f97e0e7a201b4feca28 100644 (file)
@@ -3,9 +3,10 @@ package org.argeo.cms.e4.maintenance;
 import java.util.GregorianCalendar;
 import java.util.TimeZone;
 
-import org.argeo.api.cms.CmsState;
 import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsContext;
 import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsState;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.FillLayout;
@@ -66,14 +67,14 @@ class DeploymentEntryPoint {
                if (nodeStateRef == null)
                        throw new IllegalStateException("No CMS state available");
                CmsState nodeState = bc.getService(nodeStateRef);
-               ServiceReference<CmsDeployment> nodeDeploymentRef = bc.getServiceReference(CmsDeployment.class);
+               ServiceReference<CmsContext> nodeDeploymentRef = bc.getServiceReference(CmsContext.class);
                Label label = new Label(composite, SWT.WRAP);
                CmsSwtUtils.markup(label);
                if (nodeDeploymentRef == null) {
                        label.setText("Not yet deployed on <br>" + nodeState.getHostname() + "</br>, please configure below.");
                } else {
                        Object stateUuid = nodeStateRef.getProperty(CmsConstants.CN);
-                       CmsDeployment nodeDeployment = bc.getService(nodeDeploymentRef);
+                       CmsContext nodeDeployment = bc.getService(nodeDeploymentRef);
                        GregorianCalendar calendar = new GregorianCalendar();
                        calendar.setTimeInMillis(nodeDeployment.getAvailableSince());
                        calendar.setTimeZone(TimeZone.getDefault());
index 42eedf1a3a23417f9f828c75835e9c527cf926ff..9c8680c4d79660260c2b67e3e26ad56a5a7553ba 100644 (file)
@@ -17,10 +17,10 @@ import javax.security.auth.callback.UnsupportedCallbackException;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
 
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsState;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsView;
 import org.argeo.cms.CmsMsg;
 import org.argeo.cms.LocaleUtils;
 import org.argeo.cms.auth.RemoteAuthCallback;
@@ -64,7 +64,8 @@ public class CmsLogin implements CmsStyles, CallbackHandler {
 
        public CmsLogin(CmsView cmsView) {
                this.cmsView = cmsView;
-               CmsState nodeState = null;// = Activator.getNodeState();
+               CmsContext nodeState = null;// = Activator.getNodeState();
+               // FIXME reactivate locales
                if (nodeState != null) {
                        defaultLocale = nodeState.getDefaultLocale();
                        List<Locale> locales = nodeState.getLocales();
diff --git a/org.argeo.cms/OSGI-INF/cmsContext.xml b/org.argeo.cms/OSGI-INF/cmsContext.xml
new file mode 100644 (file)
index 0000000..c46b3a4
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="CMS Context">
+   <implementation class="org.argeo.cms.internal.runtime.CmsContextImpl"/>
+   <reference bind="setCmsDeployment" cardinality="1..1" interface="org.argeo.api.cms.CmsDeployment" name="CmsDeployment" policy="static"/>
+   <service>
+      <provide interface="org.argeo.api.cms.CmsContext"/>
+   </service>
+   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+   <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/cmsDeployment.xml b/org.argeo.cms/OSGI-INF/cmsDeployment.xml
new file mode 100644 (file)
index 0000000..4093f3e
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="CMS Deployment">
+   <reference bind="setDeployConfig" cardinality="1..1" interface="org.argeo.cms.internal.osgi.DeployConfig" name="DeployConfig" policy="static"/>
+   <implementation class="org.argeo.cms.internal.runtime.CmsDeploymentImpl"/>
+   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+   <service>
+      <provide interface="org.argeo.api.cms.CmsDeployment"/>
+   </service>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/cmsState.xml b/org.argeo.cms/OSGI-INF/cmsState.xml
new file mode 100644 (file)
index 0000000..9e9ecc4
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="false" name="CMS State">
+   <implementation class="org.argeo.cms.internal.runtime.CmsStateImpl"/>
+   <service>
+      <provide interface="org.argeo.api.cms.CmsState"/>
+   </service>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/deployConfig.xml b/org.argeo.cms/OSGI-INF/deployConfig.xml
new file mode 100644 (file)
index 0000000..10fcb54
--- /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="init" deactivate="destroy" name="Deploy Config">
+   <implementation class="org.argeo.cms.internal.osgi.DeployConfig"/>
+   <service>
+      <provide interface="org.argeo.cms.internal.osgi.DeployConfig"/>
+   </service>
+   <reference bind="setConfigurationAdmin" cardinality="1..1" interface="org.osgi.service.cm.ConfigurationAdmin" name="ConfigurationAdmin" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml b/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml
new file mode 100644 (file)
index 0000000..7becce7
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Node User Admin">
+   <implementation class="org.argeo.cms.internal.osgi.NodeUserAdmin"/>
+   <property name="service.pid" type="String" value="org.argeo.api.userAdmin"/>
+   <reference bind="setTransactionManager" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkControl" name="WorkControl" policy="static"/>
+   <service>
+      <provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
+   </service>
+   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkTransaction" name="WorkTransaction" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/simpleTransactionManager.xml b/org.argeo.cms/OSGI-INF/simpleTransactionManager.xml
new file mode 100644 (file)
index 0000000..c331aa4
--- /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" name="Simple Transaction Manager">
+   <implementation class="org.argeo.osgi.transaction.SimpleTransactionManager"/>
+   <service>
+      <provide interface="org.argeo.osgi.transaction.WorkControl"/>
+      <provide interface="org.argeo.osgi.transaction.WorkTransaction"/>
+   </service>
+</scr:component>
index 61cb6cbab2914b5025be52a9b6c3f3cb44c61c42..e75adcdc3b49afd28a98c73422944d81afce5736 100644 (file)
@@ -1,10 +1,18 @@
-Bundle-Activator: org.argeo.cms.internal.kernel.Activator
+Bundle-Activator: org.argeo.cms.internal.osgi.CmsActivator
 
-Import-Package: org.apache.commons.httpclient.cookie;resolution:=optional,\
+Import-Package: \
+org.argeo.osgi.transaction, \
+org.apache.commons.httpclient.cookie;resolution:=optional,\
 !com.sun.security.jgss,\
 org.osgi.*;version=0.0.0,\
 *
 
-#Service-Component:\
-#OSGI-INF/cmsUserManager.xml,\
+Service-Component:\
+OSGI-INF/cmsState.xml,\
+OSGI-INF/simpleTransactionManager.xml,\
+OSGI-INF/nodeUserAdmin.xml,\
+OSGI-INF/cmsUserManager.xml,\
+OSGI-INF/deployConfig.xml,\
+OSGI-INF/cmsDeployment.xml,\
+OSGI-INF/cmsContext.xml,\
 
index 67b98f4d0034c2f7752e765fbee687477633e222..db86d95a50099a1a879c9b09b154cee6fd72227b 100644 (file)
@@ -2,5 +2,11 @@ output.. = bin/
 bin.includes = META-INF/,\
                .,\
                bin/,\
-               OSGI-INF/
+               OSGI-INF/,\
+               OSGI-INF/simpleTransactionManager.xml,\
+               OSGI-INF/cmsState.xml,\
+               OSGI-INF/nodeUserAdmin.xml,\
+               OSGI-INF/deployConfig.xml,\
+               OSGI-INF/cmsDeployment.xml,\
+               OSGI-INF/cmsContext.xml
 source.. = src/
index afb6360b476450401ad9fa06f638f01063f51558..85a4824646bec37124c8cefc79627240165a435a 100644 (file)
@@ -13,12 +13,12 @@ import java.util.UUID;
 import javax.security.auth.Subject;
 import javax.security.auth.x500.X500Principal;
 
+import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsSession;
 import org.argeo.api.cms.CmsSessionId;
-import org.argeo.api.cms.CmsConstants;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
 import org.argeo.cms.internal.auth.ImpliedByPrincipal;
-import org.argeo.cms.internal.kernel.Activator;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.osgi.service.useradmin.Authorization;
 
 /**
@@ -107,7 +107,7 @@ public final class CurrentUser {
        public final static Locale locale(Subject subject) {
                Set<Locale> locales = subject.getPublicCredentials(Locale.class);
                if (locales.isEmpty()) {
-                       Locale defaultLocale = Activator.getNodeState().getDefaultLocale();
+                       Locale defaultLocale = CmsContextImpl.getCmsContext().getDefaultLocale();
                        return defaultLocale;
                } else
                        return locales.iterator().next();
@@ -152,7 +152,7 @@ public final class CurrentUser {
                else
                        return false;
                CmsSessionImpl cmsSession = CmsSessionImpl.getByUuid(nodeSessionId.toString());
-               
+
                // FIXME logout all views
                // TODO check why it is sometimes null
                if (cmsSession != null)
index ccf7fc724af1e8bf322c4c4545a84348883f3f9e..097e588e43737e83f09e6633a5309dcc8bdf25c4 100644 (file)
@@ -12,7 +12,7 @@ import javax.security.auth.spi.LoginModule;
 
 import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.auth.ident.IdentClient;
-import org.argeo.cms.internal.kernel.Activator;
+import org.argeo.cms.internal.runtime.CmsStateImpl;
 
 /** Use an ident service to identify. */
 public class IdentLoginModule implements LoginModule {
@@ -44,7 +44,7 @@ public class IdentLoginModule implements LoginModule {
                RemoteAuthRequest request = httpCallback.getRequest();
                if (request == null)
                        return false;
-               IdentClient identClient = Activator.getIdentClient(request.getRemoteAddr());
+               IdentClient identClient = CmsStateImpl.getIdentClient(request.getRemoteAddr());
                if (identClient == null)
                        return false;
                String identUsername;
index 4d3617eefc4150e01fdc07075b87d61ae03ce252..962094d4ace32377f3b9e4ba5da1e2ab1ce81f1a 100644 (file)
@@ -14,9 +14,10 @@ import javax.security.auth.callback.UnsupportedCallbackException;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 
+import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.argeo.cms.internal.kernel.Activator;
+import org.argeo.cms.internal.runtime.KernelUtils;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.service.http.HttpContext;
@@ -211,7 +212,7 @@ public class RemoteSessionLoginModule implements LoginModule {
                        if (log.isDebugEnabled())
                                log.debug("Client certificate " + certDn + " verified by servlet container");
                } // Reverse proxy verified the client certificate
-               String clientDnHttpHeader = Activator.getHttpProxySslHeader();
+               String clientDnHttpHeader = KernelUtils.getFrameworkProp(CmsConstants.HTTP_PROXY_SSL_DN);
                if (clientDnHttpHeader != null) {
                        String certDn = req.getHeader(clientDnHttpHeader);
                        // TODO retrieve more cf. https://httpd.apache.org/docs/current/mod/mod_ssl.html
index c94480cb54de824d0ec2d9f31bdaf3f5cae993f7..2dbad96d28d592bcb007d7e186ea6223c054f62c 100644 (file)
@@ -9,7 +9,7 @@ import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 
 import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.kernel.Activator;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.ietf.jgss.GSSContext;
 import org.ietf.jgss.GSSCredential;
 import org.ietf.jgss.GSSException;
@@ -111,7 +111,7 @@ public class SpnegoLoginModule implements LoginModule {
        private GSSContext checkToken(byte[] authToken) {
                GSSManager manager = GSSManager.getInstance();
                try {
-                       GSSContext gContext = manager.createContext(Activator.getAcceptorCredentials());
+                       GSSContext gContext = manager.createContext(CmsContextImpl.getAcceptorCredentials());
 
                        if (gContext == null) {
                                log.debug("SpnegoUserRealm: failed to establish GSSContext");
@@ -132,7 +132,8 @@ public class SpnegoLoginModule implements LoginModule {
 
        }
 
+       @Deprecated
        public static boolean hasAcceptorCredentials() {
-               return Activator.getAcceptorCredentials() != null;
+               return CmsContextImpl.getAcceptorCredentials() != null;
        }
 }
index b9f8d4a5131df7d0773c63cd532239cfe7049f9f..738b507e79495a5898f29098f5a0f2c4169bf8a9 100644 (file)
@@ -26,7 +26,8 @@ import javax.security.auth.spi.LoginModule;
 
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.kernel.Activator;
+import org.argeo.cms.internal.osgi.NodeUserAdmin;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.argeo.cms.security.CryptoKeyring;
 import org.argeo.osgi.useradmin.AuthenticatingUser;
 import org.argeo.osgi.useradmin.IpaUtils;
@@ -79,7 +80,7 @@ public class UserAdminLoginModule implements LoginModule {
 
        @Override
        public boolean login() throws LoginException {
-               UserAdmin userAdmin = Activator.getUserAdmin();
+               UserAdmin userAdmin = CmsContextImpl.getUserAdmin();
                final String username;
                final char[] password;
                Object certificateChain = null;
@@ -211,7 +212,7 @@ public class UserAdminLoginModule implements LoginModule {
 //             if (singleUser) {
 //                     OsUserUtils.loginAsSystemUser(subject);
 //             }
-               UserAdmin userAdmin = Activator.getUserAdmin();
+               UserAdmin userAdmin = CmsContextImpl.getUserAdmin();
                Authorization authorization;
                if (callbackHandler == null) {// anonymous
                        authorization = userAdmin.getAuthorization(null);
index 334e43c85a1672e9cd737bd6f9096a3ec78329d3..4abdd145830a2b8d5ce19809a6b97cec3ad12d36 100644 (file)
@@ -20,7 +20,7 @@ import org.apache.commons.httpclient.auth.MalformedChallengeException;
 import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.commons.httpclient.params.DefaultHttpParams;
 import org.apache.commons.httpclient.params.HttpParams;
-import org.argeo.cms.internal.kernel.KernelConstants;
+import org.argeo.cms.internal.runtime.KernelConstants;
 import org.ietf.jgss.GSSContext;
 import org.ietf.jgss.GSSException;
 import org.ietf.jgss.GSSManager;
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/.gitignore b/org.argeo.cms/src/org/argeo/cms/internal/kernel/.gitignore
deleted file mode 100644 (file)
index 50e1322..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/*.log
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
deleted file mode 100644 (file)
index f505942..0000000
+++ /dev/null
@@ -1,292 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.net.URL;
-import java.security.AllPermission;
-import java.util.Dictionary;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import javax.security.auth.login.Configuration;
-
-import org.argeo.api.cms.CmsState;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsContext;
-import org.argeo.api.cms.CmsDeployment;
-import org.argeo.cms.ArgeoLogger;
-import org.argeo.cms.auth.ident.IdentClient;
-import org.ietf.jgss.GSSCredential;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.condpermadmin.BundleLocationCondition;
-import org.osgi.service.condpermadmin.ConditionInfo;
-import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
-import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
-import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
-import org.osgi.service.log.LogReaderService;
-import org.osgi.service.permissionadmin.PermissionInfo;
-import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * Activates the kernel. Gives access to kernel information for the rest of the
- * bundle (and only it)
- */
-public class Activator implements BundleActivator {
-       private final static CmsLog log = CmsLog.getLog(Activator.class);
-
-       private static Activator instance;
-
-       // TODO make it configurable
-       private boolean hardened = false;
-
-       private static BundleContext bundleContext;
-
-       private LogReaderService logReaderService;
-
-       private NodeLogger logger;
-       private CmsStateImpl nodeState;
-       private CmsDeploymentImpl nodeDeployment;
-       private CmsContextImpl nodeInstance;
-
-       private ServiceTracker<UserAdmin, NodeUserAdmin> userAdminSt;
-       private ExecutorService internalExecutorService;
-
-       static {
-               Bundle bundle = FrameworkUtil.getBundle(Activator.class);
-               if (bundle != null) {
-                       bundleContext = bundle.getBundleContext();
-               }
-       }
-
-       void init() {
-               Runtime.getRuntime().addShutdownHook(new CmsShutdown());
-               instance = this;
-//             this.bc = bundleContext;
-               if (bundleContext != null)
-                       this.logReaderService = getService(LogReaderService.class);
-               this.internalExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
-
-               try {
-                       initSecurity();
-//                     initArgeoLogger();
-                       initNode();
-
-                       if (log.isTraceEnabled())
-                               log.trace("Kernel bundle started");
-               } catch (Throwable e) {
-                       log.error("## FATAL: CMS activator failed", e);
-               }
-       }
-
-       void destroy() {
-               try {
-                       if (nodeInstance != null)
-                               nodeInstance.shutdown();
-                       if (nodeDeployment != null)
-                               nodeDeployment.shutdown();
-                       if (nodeState != null)
-                               nodeState.shutdown();
-
-                       if (userAdminSt != null)
-                               userAdminSt.close();
-
-                       internalExecutorService.shutdown();
-                       instance = null;
-                       bundleContext = null;
-                       this.logReaderService = null;
-                       // this.configurationAdmin = null;
-               } catch (Exception e) {
-                       log.error("CMS activator shutdown failed", e);
-               }
-       }
-
-       private void initSecurity() {
-               if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
-                       String jaasConfig = KernelConstants.JAAS_CONFIG;
-                       URL url = getClass().getResource(jaasConfig);
-                       // System.setProperty(KernelConstants.JAAS_CONFIG_PROP,
-                       // url.toExternalForm());
-                       KernelUtils.setJaasConfiguration(url);
-               }
-               // explicitly load JAAS configuration
-               Configuration.getConfiguration();
-
-               // code-level permissions
-               String osgiSecurity = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_SECURITY);
-               if (osgiSecurity != null && Constants.FRAMEWORK_SECURITY_OSGI.equals(osgiSecurity)) {
-                       // TODO rather use a tracker?
-                       ConditionalPermissionAdmin permissionAdmin = bundleContext
-                                       .getService(bundleContext.getServiceReference(ConditionalPermissionAdmin.class));
-                       if (!hardened) {
-                               // All permissions to all bundles
-                               ConditionalPermissionUpdate update = permissionAdmin.newConditionalPermissionUpdate();
-                               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] {
-                                                               new ConditionInfo(BundleLocationCondition.class.getName(), new String[] { "*" }) },
-                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
-                                               ConditionalPermissionInfo.ALLOW));
-                               // TODO data admin permission
-//                             PermissionInfo dataAdminPerm = new PermissionInfo(AuthPermission.class.getName(),
-//                                             "createLoginContext." + NodeConstants.LOGIN_CONTEXT_DATA_ADMIN, null);
-//                             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-//                                             new ConditionInfo[] {
-//                                                             new ConditionInfo(BundleLocationCondition.class.getName(), new String[] { "*" }) },
-//                                             new PermissionInfo[] { dataAdminPerm }, ConditionalPermissionInfo.DENY));
-//                             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-//                                             new ConditionInfo[] {
-//                                                             new ConditionInfo(BundleSignerCondition.class.getName(), new String[] { "CN=\"Eclipse.org Foundation, Inc.\", OU=IT, O=\"Eclipse.org Foundation, Inc.\", L=Nepean, ST=Ontario, C=CA" }) },
-//                                             new PermissionInfo[] { dataAdminPerm }, ConditionalPermissionInfo.ALLOW));
-                               update.commit();
-                       } else {
-                               SecurityProfile securityProfile = new SecurityProfile() {
-                               };
-                               securityProfile.applySystemPermissions(permissionAdmin);
-                       }
-               }
-
-       }
-
-       private void initArgeoLogger() {
-               logger = new NodeLogger(logReaderService);
-               if (bundleContext != null)
-                       bundleContext.registerService(ArgeoLogger.class, logger, null);
-       }
-
-       private void initNode() throws IOException {
-               // Node state
-               nodeState = new CmsStateImpl();
-               registerService(CmsState.class, nodeState, null);
-
-               // Node deployment
-               nodeDeployment = new CmsDeploymentImpl();
-//             registerService(NodeDeployment.class, nodeDeployment, null);
-
-               // Node instance
-               nodeInstance = new CmsContextImpl();
-               registerService(CmsContext.class, nodeInstance, null);
-       }
-
-       public static <T> void registerService(Class<T> clss, T service, Dictionary<String, ?> properties) {
-               if (bundleContext != null) {
-                       bundleContext.registerService(clss, service, properties);
-               }
-
-       }
-
-       public static <T> T getService(Class<T> clss) {
-               if (bundleContext != null) {
-                       return bundleContext.getService(bundleContext.getServiceReference(clss));
-               } else {
-                       return null;
-               }
-       }
-
-       /*
-        * OSGi
-        */
-
-       @Override
-       public void start(BundleContext bc) throws Exception {
-               if (!bc.getBundle().equals(bundleContext.getBundle()))
-                       throw new IllegalStateException(
-                                       "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle());
-               init();
-               userAdminSt = new ServiceTracker<>(bundleContext, UserAdmin.class, null);
-               userAdminSt.open();
-       }
-
-       @Override
-       public void stop(BundleContext bc) throws Exception {
-               if (!bc.getBundle().equals(bundleContext.getBundle()))
-                       throw new IllegalStateException(
-                                       "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle());
-               destroy();
-       }
-
-//     private <T> T getService(Class<T> clazz) {
-//             ServiceReference<T> sr = bundleContext.getServiceReference(clazz);
-//             if (sr == null)
-//                     throw new IllegalStateException("No service available for " + clazz);
-//             return bundleContext.getService(sr);
-//     }
-
-       public static CmsState getNodeState() {
-               return instance.nodeState;
-       }
-
-       public static GSSCredential getAcceptorCredentials() {
-               return getNodeUserAdmin().getAcceptorCredentials();
-       }
-
-       @Deprecated
-       public static boolean isSingleUser() {
-               return getNodeUserAdmin().isSingleUser();
-       }
-
-       public static UserAdmin getUserAdmin() {
-               return (UserAdmin) getNodeUserAdmin();
-       }
-
-       public static String getHttpProxySslHeader() {
-               return KernelUtils.getFrameworkProp(CmsConstants.HTTP_PROXY_SSL_DN);
-       }
-
-       public static IdentClient getIdentClient(String remoteAddr) {
-               if (!IdentClient.isDefaultAuthdPassphraseFileAvailable())
-                       return null;
-               // TODO make passphrase more configurable
-               return new IdentClient(remoteAddr);
-       }
-
-       private static NodeUserAdmin getNodeUserAdmin() {
-               NodeUserAdmin res;
-               try {
-                       res = instance.userAdminSt.waitForService(60000);
-               } catch (InterruptedException e) {
-                       throw new IllegalStateException("Cannot retrieve Node user admin", e);
-               }
-               if (res == null)
-                       throw new IllegalStateException("No Node user admin found");
-
-               return res;
-               // ServiceReference<UserAdmin> sr =
-               // instance.bc.getServiceReference(UserAdmin.class);
-               // NodeUserAdmin userAdmin = (NodeUserAdmin) instance.bc.getService(sr);
-               // return userAdmin;
-
-       }
-
-       static ExecutorService getInternalExecutorService() {
-               return instance.internalExecutorService;
-       }
-
-       // static CmsSecurity getCmsSecurity() {
-       // return instance.nodeSecurity;
-       // }
-
-       public String[] getLocales() {
-               // TODO optimize?
-               List<Locale> locales = getNodeState().getLocales();
-               String[] res = new String[locales.size()];
-               for (int i = 0; i < locales.size(); i++)
-                       res[i] = locales.get(i).toString();
-               return res;
-       }
-
-       static BundleContext getBundleContext() {
-               return bundleContext;
-       }
-
-       public static void main(String[] args) {
-               instance = new Activator();
-               instance.init();
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsContextImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsContextImpl.java
deleted file mode 100644 (file)
index 822959b..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import org.argeo.api.cms.CmsContext;
-import org.argeo.api.cms.CmsLog;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
-public class CmsContextImpl implements CmsContext {
-       private final CmsLog log = CmsLog.getLog(getClass());
-       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-//     private EgoRepository egoRepository;
-
-       public CmsContextImpl() {
-               initTrackers();
-       }
-
-       private void initTrackers() {
-               // node repository
-//             new ServiceTracker<Repository, Repository>(bc, Repository.class, null) {
-//                     @Override
-//                     public Repository addingService(ServiceReference<Repository> reference) {
-//                             Object cn = reference.getProperty(NodeConstants.CN);
-//                             if (cn != null && cn.equals(NodeConstants.EGO_REPOSITORY)) {
-////                                   egoRepository = (EgoRepository) bc.getService(reference);
-//                                     if (log.isTraceEnabled())
-//                                             log.trace("Home repository is available");
-//                             }
-//                             return super.addingService(reference);
-//                     }
-//
-//                     @Override
-//                     public void removedService(ServiceReference<Repository> reference, Repository service) {
-//                             super.removedService(reference, service);
-////                           egoRepository = null;
-//                     }
-//
-//             }.open();
-       }
-
-       public void shutdown() {
-
-       }
-
-       @Override
-       public void createWorkgroup(String dn) {
-//             if (egoRepository == null)
-//                     throw new CmsException("Ego repository is not available");
-//             // TODO add check that the group exists
-//             egoRepository.createWorkgroup(dn);
-               throw new UnsupportedOperationException();
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeploymentImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeploymentImpl.java
deleted file mode 100644 (file)
index 4c7cb1d..0000000
+++ /dev/null
@@ -1,243 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.lang.management.ManagementFactory;
-import java.net.URL;
-import java.util.Dictionary;
-
-import org.argeo.api.cms.CmsState;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsDeployment;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.eclipse.equinox.http.jetty.JettyConfigurator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.cm.Configuration;
-import org.osgi.service.cm.ConfigurationAdmin;
-import org.osgi.service.http.HttpService;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.util.tracker.ServiceTracker;
-
-/** Implementation of a CMS deployment. */
-public class CmsDeploymentImpl implements CmsDeployment {
-       private final CmsLog log = CmsLog.getLog(getClass());
-       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       private DeployConfig deployConfig;
-
-       private Long availableSince;
-
-       // Readiness
-       private boolean nodeAvailable = false;
-       private boolean userAdminAvailable = false;
-       private boolean httpExpected = false;
-       private boolean httpAvailable = false;
-
-       public CmsDeploymentImpl() {
-//             ServiceReference<NodeState> nodeStateSr = bc.getServiceReference(NodeState.class);
-//             if (nodeStateSr == null)
-//                     throw new CmsException("No node state available");
-
-//             NodeState nodeState = bc.getService(nodeStateSr);
-//             cleanState = nodeState.isClean();
-
-//             nodeHttp = new NodeHttp();
-               initTrackers();
-       }
-
-       private void initTrackers() {
-               ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
-
-                       @Override
-                       public HttpService addingService(ServiceReference<HttpService> sr) {
-                               httpAvailable = true;
-                               Object httpPort = sr.getProperty("http.port");
-                               Object httpsPort = sr.getProperty("https.port");
-                               log.info(httpPortsMsg(httpPort, httpsPort));
-                               checkReadiness();
-                               return super.addingService(sr);
-                       }
-               };
-               // httpSt.open();
-               KernelUtils.asyncOpen(httpSt);
-
-               ServiceTracker<?, ?> userAdminSt = new ServiceTracker<UserAdmin, UserAdmin>(bc, UserAdmin.class, null) {
-                       @Override
-                       public UserAdmin addingService(ServiceReference<UserAdmin> reference) {
-                               UserAdmin userAdmin = super.addingService(reference);
-                               addStandardSystemRoles(userAdmin);
-                               userAdminAvailable = true;
-                               checkReadiness();
-                               return userAdmin;
-                       }
-               };
-               // userAdminSt.open();
-               KernelUtils.asyncOpen(userAdminSt);
-
-               ServiceTracker<?, ?> confAdminSt = new ServiceTracker<ConfigurationAdmin, ConfigurationAdmin>(bc,
-                               ConfigurationAdmin.class, null) {
-                       @Override
-                       public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> reference) {
-                               ConfigurationAdmin configurationAdmin = bc.getService(reference);
-                               boolean isClean;
-                               try {
-                                       Configuration[] confs = configurationAdmin
-                                                       .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
-                                       isClean = confs == null || confs.length == 0;
-                               } catch (Exception e) {
-                                       throw new IllegalStateException("Cannot analyse clean state", e);
-                               }
-                               deployConfig = new DeployConfig(configurationAdmin, isClean);
-                               Activator.registerService(CmsDeployment.class, CmsDeploymentImpl.this, null);
-//                             JcrInitUtils.addToDeployment(CmsDeployment.this);
-                               httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
-                               try {
-                                       Configuration[] configs = configurationAdmin
-                                                       .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
-
-                                       boolean hasDomain = false;
-                                       for (Configuration config : configs) {
-                                               Object realm = config.getProperties().get(UserAdminConf.realm.name());
-                                               if (realm != null) {
-                                                       log.debug("Found realm: " + realm);
-                                                       hasDomain = true;
-                                               }
-                                       }
-                                       if (hasDomain) {
-                                               loadIpaJaasConfiguration();
-                                       }
-                               } catch (Exception e) {
-                                       throw new IllegalStateException("Cannot initialize config", e);
-                               }
-                               return super.addingService(reference);
-                       }
-               };
-               // confAdminSt.open();
-               KernelUtils.asyncOpen(confAdminSt);
-       }
-
-       public void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
-               deployConfig.putFactoryDeployConfig(factoryPid, props);
-               deployConfig.save();
-               try {
-                       deployConfig.loadConfigs();
-               } catch (IOException e) {
-                       throw new IllegalStateException(e);
-               }
-       }
-
-       public Dictionary<String, Object> getProps(String factoryPid, String cn) {
-               return deployConfig.getProps(factoryPid, cn);
-       }
-
-       private String httpPortsMsg(Object httpPort, Object httpsPort) {
-               return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
-       }
-
-       private void addStandardSystemRoles(UserAdmin userAdmin) {
-               // we assume UserTransaction is already available (TODO make it more robust)
-               WorkTransaction userTransaction = bc.getService(bc.getServiceReference(WorkTransaction.class));
-               try {
-                       userTransaction.begin();
-                       Role adminRole = userAdmin.getRole(CmsConstants.ROLE_ADMIN);
-                       if (adminRole == null) {
-                               adminRole = userAdmin.createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
-                       }
-                       if (userAdmin.getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
-                               Group userAdminRole = (Group) userAdmin.createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
-                               userAdminRole.addMember(adminRole);
-                       }
-                       userTransaction.commit();
-               } catch (Exception e) {
-                       try {
-                               userTransaction.rollback();
-                       } catch (Exception e1) {
-                               // silent
-                       }
-                       throw new IllegalStateException("Cannot add standard system roles", e);
-               }
-       }
-
-       private void loadIpaJaasConfiguration() {
-               if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
-                       String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
-                       URL url = getClass().getClassLoader().getResource(jaasConfig);
-                       KernelUtils.setJaasConfiguration(url);
-                       log.debug("Set IPA JAAS configuration.");
-               }
-       }
-
-       public void shutdown() {
-//             if (nodeHttp != null)
-//                     nodeHttp.destroy();
-
-               try {
-                       JettyConfigurator.stopServer(KernelConstants.DEFAULT_JETTY_SERVER);
-               } catch (Exception e) {
-                       log.error("Cannot stop default Jetty server.", e);
-               }
-
-               if (deployConfig != null) {
-                       new Thread(() -> deployConfig.save(), "Save Argeo Deploy Config").start();
-               }
-       }
-
-       /**
-        * Checks whether the deployment is available according to expectations, and
-        * mark it as available.
-        */
-       private synchronized void checkReadiness() {
-               if (isAvailable())
-                       return;
-               if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) {
-                       String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
-                       String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
-                       availableSince = System.currentTimeMillis();
-                       long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-                       String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
-                       log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
-                       if (log.isDebugEnabled()) {
-                               log.debug("## state: " + state);
-                               if (data != null)
-                                       log.debug("## data: " + data);
-                       }
-                       long begin = bc.getService(bc.getServiceReference(CmsState.class)).getAvailableSince();
-                       long initDuration = System.currentTimeMillis() - begin;
-                       if (log.isTraceEnabled())
-                               log.trace("Kernel initialization took " + initDuration + "ms");
-                       tributeToFreeSoftware(initDuration);
-               }
-       }
-
-       final private void tributeToFreeSoftware(long initDuration) {
-               if (log.isTraceEnabled()) {
-                       long ms = initDuration / 100;
-                       log.trace("Spend " + ms + "ms" + " reflecting on the progress brought to mankind" + " by Free Software...");
-                       long beginNano = System.nanoTime();
-                       try {
-                               Thread.sleep(ms, 0);
-                       } catch (InterruptedException e) {
-                               // silent
-                       }
-                       long durationNano = System.nanoTime() - beginNano;
-                       final double M = 1000d * 1000d;
-                       double sleepAccuracy = ((double) durationNano) / (ms * M);
-                       log.trace("Sleep accuracy: " + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100)) + " %");
-               }
-       }
-
-       @Override
-       public synchronized Long getAvailableSince() {
-               return availableSince;
-       }
-
-       public synchronized boolean isAvailable() {
-               return availableSince != null;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsShutdown.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsShutdown.java
deleted file mode 100644 (file)
index eb7657e..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import org.argeo.api.cms.CmsLog;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkEvent;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.launch.Framework;
-
-/** Shutdowns the OSGi framework */
-class CmsShutdown extends Thread {
-       public final int EXIT_OK = 0;
-       public final int EXIT_ERROR = 1;
-       public final int EXIT_TIMEOUT = 2;
-       public final int EXIT_UNKNOWN = 3;
-
-       private final CmsLog log = CmsLog.getLog(CmsShutdown.class);
-       // private final BundleContext bc =
-       // FrameworkUtil.getBundle(CmsShutdown.class).getBundleContext();
-       private final Framework framework;
-
-       /** Shutdown timeout in ms */
-       private long timeout = 10 * 60 * 1000;
-
-       public CmsShutdown() {
-               super("CMS Shutdown Hook");
-               framework = FrameworkUtil.getBundle(CmsShutdown.class) != null
-                               ? (Framework) FrameworkUtil.getBundle(CmsShutdown.class).getBundleContext().getBundle(0)
-                               : null;
-       }
-
-       @Override
-       public void run() {
-               if (framework != null && framework.getState() != Bundle.ACTIVE) {
-                       return;
-               }
-
-               if (log.isDebugEnabled())
-                       log.debug("Shutting down OSGi framework...");
-               try {
-                       if (framework != null) {
-                               // shutdown framework
-                               framework.stop();
-                               // wait for shutdown
-                               FrameworkEvent shutdownEvent = framework.waitForStop(timeout);
-                               int stoppedType = shutdownEvent.getType();
-                               Runtime runtime = Runtime.getRuntime();
-                               if (stoppedType == FrameworkEvent.STOPPED) {
-                                       // close VM
-                                       // System.exit(EXIT_OK);
-                               } else if (stoppedType == FrameworkEvent.ERROR) {
-                                       log.error("The OSGi framework stopped with an error");
-                                       runtime.halt(EXIT_ERROR);
-                               } else if (stoppedType == FrameworkEvent.WAIT_TIMEDOUT) {
-                                       log.error("The OSGi framework hasn't stopped after " + timeout + "ms."
-                                                       + " Forcibly terminating the JVM...");
-                                       runtime.halt(EXIT_TIMEOUT);
-                               } else {
-                                       log.error("Unknown state of OSGi framework after " + timeout + "ms."
-                                                       + " Forcibly terminating the JVM... (" + shutdownEvent + ")");
-                                       runtime.halt(EXIT_UNKNOWN);
-                               }
-                       }
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       log.error("Unexpected exception " + e + " in shutdown hook. " + " Forcibly terminating the JVM...");
-                       Runtime.getRuntime().halt(EXIT_UNKNOWN);
-               }
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsStateImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsStateImpl.java
deleted file mode 100644 (file)
index 6bc8ac8..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import static java.util.Locale.ENGLISH;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-import org.argeo.api.cms.CmsState;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.osgi.transaction.SimpleTransactionManager;
-import org.argeo.osgi.transaction.WorkControl;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.util.LangUtils;
-import org.osgi.framework.Constants;
-import org.osgi.service.cm.ManagedServiceFactory;
-
-/**
- * Implementation of a {@link CmsState}, initialising the required services.
- */
-public class CmsStateImpl implements CmsState {
-       private final static CmsLog log = CmsLog.getLog(CmsStateImpl.class);
-//     private final BundleContext bc = FrameworkUtil.getBundle(CmsState.class).getBundleContext();
-
-       // REFERENCES
-       private Long availableSince;
-
-       // i18n
-       private Locale defaultLocale;
-       private List<Locale> locales = null;
-
-       private ThreadGroup threadGroup = new ThreadGroup("CMS");
-       private List<Runnable> stopHooks = new ArrayList<>();
-
-       private final String stateUuid;
-//     private final boolean cleanState;
-       private String hostname;
-
-       public CmsStateImpl() {
-//             this.stateUuid = stateUuid;
-               this.stateUuid = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_UUID);
-//             this.cleanState = stateUuid.equals(frameworkUuid);
-               try {
-                       this.hostname = InetAddress.getLocalHost().getHostName();
-               } catch (UnknownHostException e) {
-                       log.error("Cannot set hostname: " + e);
-               }
-
-               availableSince = System.currentTimeMillis();
-               if (log.isDebugEnabled())
-                       // log.debug("## CMS starting... stateUuid=" + this.stateUuid + (cleanState ? "
-                       // (clean state) " : " "));
-                       log.debug("## CMS starting... (" + stateUuid + ")");
-
-               initI18n();
-               initServices();
-
-       }
-
-       private void initI18n() {
-               Object defaultLocaleValue = KernelUtils.getFrameworkProp(CmsConstants.I18N_DEFAULT_LOCALE);
-               defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString())
-                               : new Locale(ENGLISH.getLanguage());
-               locales = LocaleUtils.asLocaleList(KernelUtils.getFrameworkProp(CmsConstants.I18N_LOCALES));
-       }
-
-       private void initServices() {
-               // JTA
-               String tmType = KernelUtils.getFrameworkProp(CmsConstants.TRANSACTION_MANAGER,
-                               CmsConstants.TRANSACTION_MANAGER_SIMPLE);
-               if (CmsConstants.TRANSACTION_MANAGER_SIMPLE.equals(tmType)) {
-                       initSimpleTransactionManager();
-               } else if (CmsConstants.TRANSACTION_MANAGER_BITRONIX.equals(tmType)) {
-//                     initBitronixTransactionManager();
-                       throw new UnsupportedOperationException(
-                                       "Bitronix is not supported anymore, but could be again if there is enough interest.");
-               } else {
-                       throw new IllegalArgumentException("Usupported transaction manager type " + tmType);
-               }
-
-               // POI
-//             POIXMLTypeLoader.setClassLoader(CTConnection.class.getClassLoader());
-
-               // Tika
-//             OpenDocumentParser odfParser = new OpenDocumentParser();
-//             bc.registerService(Parser.class, odfParser, new Hashtable());
-//             PDFParser pdfParser = new PDFParser();
-//             bc.registerService(Parser.class, pdfParser, new Hashtable());
-//             OOXMLParser ooxmlParser = new OOXMLParser();
-//             bc.registerService(Parser.class, ooxmlParser, new Hashtable());
-//             TesseractOCRParser ocrParser = new TesseractOCRParser();
-//             ocrParser.setLanguage("ara");
-//             bc.registerService(Parser.class, ocrParser, new Hashtable());
-
-//             // JCR
-//             RepositoryServiceFactory repositoryServiceFactory = new RepositoryServiceFactory();
-//             stopHooks.add(() -> repositoryServiceFactory.shutdown());
-//             Activator.registerService(ManagedServiceFactory.class, repositoryServiceFactory,
-//                             LangUtils.dict(Constants.SERVICE_PID, NodeConstants.NODE_REPOS_FACTORY_PID));
-//
-//             NodeRepositoryFactory repositoryFactory = new NodeRepositoryFactory();
-//             Activator.registerService(RepositoryFactory.class, repositoryFactory, null);
-
-               // Security
-               NodeUserAdmin userAdmin = new NodeUserAdmin(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
-               stopHooks.add(() -> userAdmin.destroy());
-               Activator.registerService(ManagedServiceFactory.class, userAdmin,
-                               LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_USER_ADMIN_PID));
-
-       }
-
-       private void initSimpleTransactionManager() {
-               SimpleTransactionManager transactionManager = new SimpleTransactionManager();
-               Activator.registerService(WorkControl.class, transactionManager, null);
-               Activator.registerService(WorkTransaction.class, transactionManager, null);
-//             Activator.registerService(TransactionManager.class, transactionManager, null);
-//             Activator.registerService(UserTransaction.class, transactionManager, null);
-               // TODO TransactionSynchronizationRegistry
-       }
-
-//     private void initBitronixTransactionManager() {
-//             // TODO manage it in a managed service, as startup could be long
-//             ServiceReference<TransactionManager> existingTm = bc.getServiceReference(TransactionManager.class);
-//             if (existingTm != null) {
-//                     if (log.isDebugEnabled())
-//                             log.debug("Using provided transaction manager " + existingTm);
-//                     return;
-//             }
-//
-//             if (!TransactionManagerServices.isTransactionManagerRunning()) {
-//                     bitronix.tm.Configuration tmConf = TransactionManagerServices.getConfiguration();
-//                     tmConf.setServerId(UUID.randomUUID().toString());
-//
-//                     Bundle bitronixBundle = FrameworkUtil.getBundle(bitronix.tm.Configuration.class);
-//                     File tmBaseDir = bitronixBundle.getDataFile(KernelConstants.DIR_TRANSACTIONS);
-//                     File tmDir1 = new File(tmBaseDir, "btm1");
-//                     tmDir1.mkdirs();
-//                     tmConf.setLogPart1Filename(new File(tmDir1, tmDir1.getName() + ".tlog").getAbsolutePath());
-//                     File tmDir2 = new File(tmBaseDir, "btm2");
-//                     tmDir2.mkdirs();
-//                     tmConf.setLogPart2Filename(new File(tmDir2, tmDir2.getName() + ".tlog").getAbsolutePath());
-//             }
-//             BitronixTransactionManager transactionManager = getTransactionManager();
-//             stopHooks.add(() -> transactionManager.shutdown());
-//             BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry = getTransactionSynchronizationRegistry();
-//             // register
-//             bc.registerService(TransactionManager.class, transactionManager, null);
-//             bc.registerService(UserTransaction.class, transactionManager, null);
-//             bc.registerService(TransactionSynchronizationRegistry.class, transactionSynchronizationRegistry, null);
-//             if (log.isDebugEnabled())
-//                     log.debug("Initialised default Bitronix transaction manager");
-//     }
-
-       void shutdown() {
-               if (log.isDebugEnabled())
-                       log.debug("CMS stopping...  (" + this.stateUuid + ")");
-
-               // In a different thread in order to avoid interruptions
-               Thread stopHookThread = new Thread(() -> applyStopHooks(), "Apply Argeo Stop Hooks");
-               stopHookThread.start();
-               try {
-                       stopHookThread.join(10 * 60 * 1000);
-               } catch (InterruptedException e) {
-                       // silent
-               }
-
-               long duration = ((System.currentTimeMillis() - availableSince) / 1000) / 60;
-               log.info("## ARGEO CMS STOPPED after " + (duration / 60) + "h " + (duration % 60) + "min uptime ##");
-       }
-
-       /** Apply shutdown hoos in reverse order. */
-       private void applyStopHooks() {
-               for (int i = stopHooks.size() - 1; i >= 0; i--) {
-                       try {
-                               stopHooks.get(i).run();
-                       } catch (Exception e) {
-                               log.error("Could not run shutdown hook #" + i);
-                       }
-               }
-               // Clean hanging Gogo shell thread
-               new GogoShellKiller().start();
-       }
-
-//     @Override
-//     public boolean isClean() {
-//             return cleanState;
-//     }
-
-       @Override
-       public Long getAvailableSince() {
-               return availableSince;
-       }
-
-       /*
-        * ACCESSORS
-        */
-       public Locale getDefaultLocale() {
-               return defaultLocale;
-       }
-
-       public List<Locale> getLocales() {
-               return locales;
-       }
-
-       public String getHostname() {
-               return hostname;
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java
deleted file mode 100644 (file)
index 4a88dd1..0000000
+++ /dev/null
@@ -1,372 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.List;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.InvalidNameException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.util.naming.AttributesDictionary;
-import org.argeo.util.naming.LdifParser;
-import org.argeo.util.naming.LdifWriter;
-import org.eclipse.equinox.http.jetty.JettyConfigurator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.cm.Configuration;
-import org.osgi.service.cm.ConfigurationAdmin;
-import org.osgi.service.cm.ConfigurationEvent;
-import org.osgi.service.cm.ConfigurationListener;
-
-/** Manages the LDIF-based deployment configuration. */
-class DeployConfig implements ConfigurationListener {
-       private final CmsLog log = CmsLog.getLog(getClass());
-       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       private static Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH);
-       private SortedMap<LdapName, Attributes> deployConfigs = new TreeMap<>();
-//     private final DataModels dataModels;
-
-       private boolean isFirstInit = false;
-
-       private final static String ROLES = "roles";
-       
-       private ConfigurationAdmin configurationAdmin;
-
-       public DeployConfig(ConfigurationAdmin configurationAdmin, boolean isClean) {
-//             this.dataModels = dataModels;
-               // ConfigurationAdmin configurationAdmin =
-               // bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
-               try {
-                       if (!isInitialized()) { // first init
-                               isFirstInit = true;
-                               firstInit();
-                       }
-                       this.configurationAdmin = configurationAdmin;
-                       init(configurationAdmin, isClean, isFirstInit);
-               } catch (IOException e) {
-                       throw new RuntimeException("Could not init deploy configs", e);
-               }
-               // FIXME check race conditions during initialization
-               // bc.registerService(ConfigurationListener.class, this, null);
-       }
-
-       private void firstInit() throws IOException {
-               log.info("## FIRST INIT ##");
-               Files.createDirectories(deployConfigPath.getParent());
-
-               // FirstInit firstInit = new FirstInit();
-               InitUtils.prepareFirstInitInstanceArea();
-
-               if (!Files.exists(deployConfigPath))
-                       deployConfigs = new TreeMap<>();
-               else// config file could have juste been copied by preparation
-                       try (InputStream in = Files.newInputStream(deployConfigPath)) {
-                               deployConfigs = new LdifParser().read(in);
-                       }
-               save();
-       }
-
-       private void setFromFrameworkProperties(boolean isFirstInit) {
-
-               // user admin
-               List<Dictionary<String, Object>> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs();
-               if (userDirectoryConfigs.size() != 0) {
-                       List<String> activeCns = new ArrayList<>();
-                       for (int i = 0; i < userDirectoryConfigs.size(); i++) {
-                               Dictionary<String, Object> userDirectoryConfig = userDirectoryConfigs.get(i);
-                               String baseDn = (String) userDirectoryConfig.get(UserAdminConf.baseDn.name());
-                               String cn;
-                               if (CmsConstants.ROLES_BASEDN.equals(baseDn))
-                                       cn = ROLES;
-                               else
-                                       cn = UserAdminConf.baseDnHash(userDirectoryConfig);
-                               activeCns.add(cn);
-                               userDirectoryConfig.put(CmsConstants.CN, cn);
-                               putFactoryDeployConfig(CmsConstants.NODE_USER_ADMIN_PID, userDirectoryConfig);
-                       }
-                       // disable others
-                       LdapName userAdminFactoryName = serviceFactoryDn(CmsConstants.NODE_USER_ADMIN_PID);
-                       for (LdapName name : deployConfigs.keySet()) {
-                               if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) {
-//                                     try {
-                                       Attributes attrs = deployConfigs.get(name);
-                                       String cn = name.getRdn(name.size() - 1).getValue().toString();
-                                       if (!activeCns.contains(cn)) {
-                                               attrs.put(UserAdminConf.disabled.name(), "true");
-                                       }
-//                                     } catch (Exception e) {
-//                                             throw new CmsException("Cannot disable user directory " + name, e);
-//                                     }
-                               }
-                       }
-               }
-
-               // http server
-//             Dictionary<String, Object> webServerConfig = InitUtils
-//                             .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT));
-//             if (!webServerConfig.isEmpty()) {
-//                     // TODO check for other customizers
-//                     webServerConfig.put("customizer.class", "org.argeo.equinox.jetty.CmsJettyCustomizer");
-//                     putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig);
-//             }
-               LdapName defaultHttpServiceDn = serviceDn(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT);
-               if (deployConfigs.containsKey(defaultHttpServiceDn)) {
-                       // remove old default configs since we have now to start Jetty servlet bridge
-                       // indirectly
-                       deployConfigs.remove(defaultHttpServiceDn);
-               }
-
-               // SAVE
-               save();
-               //
-
-               // Explicitly configures Jetty so that the default server is not started by the
-               // activator of the Equinox Jetty bundle.
-               Dictionary<String, Object> webServerConfig = InitUtils
-                               .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT));
-//             if (!webServerConfig.isEmpty()) {
-//                     webServerConfig.put("customizer.class", KernelConstants.CMS_JETTY_CUSTOMIZER_CLASS);
-//
-//                     // TODO centralise with Jetty extender
-//                     Object webSocketEnabled = webServerConfig.get(InternalHttpConstants.WEBSOCKET_ENABLED);
-//                     if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
-//                             bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
-//                             webServerConfig.put(InternalHttpConstants.WEBSOCKET_ENABLED, "true");
-//                     }
-//             }
-
-               int tryCount = 60;
-               try {
-                       tryGettyJetty: while (tryCount > 0) {
-                               try {
-                                       JettyConfigurator.startServer(KernelConstants.DEFAULT_JETTY_SERVER, webServerConfig);
-                                       // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
-                                       // configuration is not cleaned
-                                       FrameworkUtil.getBundle(JettyConfigurator.class).start();
-                                       break tryGettyJetty;
-                               } catch (IllegalStateException e) {
-                                       // Jetty may not be ready
-                                       try {
-                                               Thread.sleep(1000);
-                                       } catch (Exception e1) {
-                                               // silent
-                                       }
-                                       tryCount--;
-                               }
-                       }
-               } catch (Exception e) {
-                       log.error("Cannot start default Jetty server with config " + webServerConfig, e);
-               }
-
-       }
-
-       private void init(ConfigurationAdmin configurationAdmin, boolean isClean, boolean isFirstInit) throws IOException {
-
-               try (InputStream in = Files.newInputStream(deployConfigPath)) {
-                       deployConfigs = new LdifParser().read(in);
-               }
-               if (isClean) {
-                       if (log.isDebugEnabled())
-                               log.debug("Clean state, loading from framework properties...");
-                       setFromFrameworkProperties(isFirstInit);
-                       loadConfigs();
-               }
-               // TODO check consistency if not clean
-       }
-       
-       public void loadConfigs() throws IOException {
-               // FIXME make it more robust
-               Configuration systemRolesConf = null;
-               LdapName systemRolesDn;
-               try {
-                       // FIXME make it more robust
-                       systemRolesDn = new LdapName("cn=roles,ou=org.argeo.api.userAdmin,ou=deploy,ou=node");
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException(e);
-               }
-               deployConfigs: for (LdapName dn : deployConfigs.keySet()) {
-                       Rdn lastRdn = dn.getRdn(dn.size() - 1);
-                       LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1);
-                       if (prefix.toString().equals(CmsConstants.DEPLOY_BASEDN)) {
-                               if (lastRdn.getType().equals(CmsConstants.CN)) {
-                                       // service
-                                       String pid = lastRdn.getValue().toString();
-                                       Configuration conf = configurationAdmin.getConfiguration(pid);
-                                       AttributesDictionary dico = new AttributesDictionary(deployConfigs.get(dn));
-                                       conf.update(dico);
-                               } else {
-                                       // service factory definition
-                               }
-                       } else {
-                               Attributes config = deployConfigs.get(dn);
-                               Attribute disabled = config.get(UserAdminConf.disabled.name());
-                               if (disabled != null)
-                                       continue deployConfigs;
-                               // service factory service
-                               Rdn beforeLastRdn = dn.getRdn(dn.size() - 2);
-                               assert beforeLastRdn.getType().equals(CmsConstants.OU);
-                               String factoryPid = beforeLastRdn.getValue().toString();
-                               Configuration conf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
-                               if (systemRolesDn.equals(dn)) {
-                                       systemRolesConf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
-                               } else {
-                                       AttributesDictionary dico = new AttributesDictionary(config);
-                                       conf.update(dico);
-                               }
-                       }
-               }
-
-               // system roles must be last since it triggers node user admin publication
-               if (systemRolesConf == null)
-                       throw new IllegalStateException("System roles are not configured.");
-               systemRolesConf.update(new AttributesDictionary(deployConfigs.get(systemRolesDn)));
-               
-       }
-
-       @Override
-       public void configurationEvent(ConfigurationEvent event) {
-               try {
-                       if (ConfigurationEvent.CM_UPDATED == event.getType()) {
-                               ConfigurationAdmin configurationAdmin = bc.getService(event.getReference());
-                               Configuration conf = configurationAdmin.getConfiguration(event.getPid(), null);
-                               LdapName serviceDn = null;
-                               String factoryPid = conf.getFactoryPid();
-                               if (factoryPid != null) {
-                                       LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
-                                       if (deployConfigs.containsKey(serviceFactoryDn)) {
-                                               for (LdapName dn : deployConfigs.keySet()) {
-                                                       if (dn.startsWith(serviceFactoryDn)) {
-                                                               Rdn lastRdn = dn.getRdn(dn.size() - 1);
-                                                               assert lastRdn.getType().equals(CmsConstants.CN);
-                                                               Object value = conf.getProperties().get(lastRdn.getType());
-                                                               assert value != null;
-                                                               if (value.equals(lastRdn.getValue())) {
-                                                                       serviceDn = dn;
-                                                                       break;
-                                                               }
-                                                       }
-                                               }
-
-                                               Object cn = conf.getProperties().get(CmsConstants.CN);
-                                               if (cn == null)
-                                                       throw new IllegalArgumentException("Properties must contain cn");
-                                               if (serviceDn == null) {
-                                                       putFactoryDeployConfig(factoryPid, conf.getProperties());
-                                               } else {
-                                                       Attributes attrs = deployConfigs.get(serviceDn);
-                                                       assert attrs != null;
-                                                       AttributesDictionary.copy(conf.getProperties(), attrs);
-                                               }
-                                               save();
-                                               if (log.isDebugEnabled())
-                                                       log.debug("Updated deploy config " + serviceDn(factoryPid, cn.toString()));
-                                       } else {
-                                               // ignore non config-registered service factories
-                                       }
-                               } else {
-                                       serviceDn = serviceDn(event.getPid());
-                                       if (deployConfigs.containsKey(serviceDn)) {
-                                               Attributes attrs = deployConfigs.get(serviceDn);
-                                               assert attrs != null;
-                                               AttributesDictionary.copy(conf.getProperties(), attrs);
-                                               save();
-                                               if (log.isDebugEnabled())
-                                                       log.debug("Updated deploy config " + serviceDn);
-                                       } else {
-                                               // ignore non config-registered services
-                                       }
-                               }
-                       }
-               } catch (Exception e) {
-                       log.error("Could not handle configuration event", e);
-               }
-       }
-
-       void putFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
-               Object cn = props.get(CmsConstants.CN);
-               if (cn == null)
-                       throw new IllegalArgumentException("cn must be set in properties");
-               LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
-               if (!deployConfigs.containsKey(serviceFactoryDn))
-                       deployConfigs.put(serviceFactoryDn, new BasicAttributes(CmsConstants.OU, factoryPid));
-               LdapName serviceDn = serviceDn(factoryPid, cn.toString());
-               Attributes attrs = new BasicAttributes();
-               AttributesDictionary.copy(props, attrs);
-               deployConfigs.put(serviceDn, attrs);
-       }
-
-       void putDeployConfig(String servicePid, Dictionary<String, Object> props) {
-               LdapName serviceDn = serviceDn(servicePid);
-               Attributes attrs = new BasicAttributes(CmsConstants.CN, servicePid);
-               AttributesDictionary.copy(props, attrs);
-               deployConfigs.put(serviceDn, attrs);
-       }
-
-       void save() {
-               try (Writer writer = Files.newBufferedWriter(deployConfigPath)) {
-                       new LdifWriter(writer).write(deployConfigs);
-               } catch (IOException e) {
-                       // throw new CmsException("Cannot save deploy configs", e);
-                       log.error("Cannot save deploy configs", e);
-               }
-       }
-
-       /*
-        * UTILITIES
-        */
-       private LdapName serviceFactoryDn(String factoryPid) {
-               try {
-                       return new LdapName(CmsConstants.OU + "=" + factoryPid + "," + CmsConstants.DEPLOY_BASEDN);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot generate DN from " + factoryPid, e);
-               }
-       }
-
-       private LdapName serviceDn(String servicePid) {
-               try {
-                       return new LdapName(CmsConstants.CN + "=" + servicePid + "," + CmsConstants.DEPLOY_BASEDN);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot generate DN from " + servicePid, e);
-               }
-       }
-
-       private LdapName serviceDn(String factoryPid, String cn) {
-               try {
-                       return (LdapName) serviceFactoryDn(factoryPid).add(new Rdn(CmsConstants.CN, cn));
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot generate DN from " + factoryPid + " and " + cn, e);
-               }
-       }
-
-public Dictionary<String, Object> getProps(String factoryPid, String cn) {
-               Attributes attrs = deployConfigs.get(serviceDn(factoryPid, cn));
-               if (attrs != null)
-                       return new AttributesDictionary(attrs);
-               else
-                       return null;
-       }
-
-       private static boolean isInitialized() {
-               return Files.exists(deployConfigPath);
-       }
-
-       public boolean isFirstInit() {
-               return isFirstInit;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/GogoShellKiller.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/GogoShellKiller.java
deleted file mode 100644 (file)
index 39b11a5..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-/**
- * Workaround for killing Gogo shell by system shutdown.
- * 
- * @see https://issues.apache.org/jira/browse/FELIX-4208
- */
-class GogoShellKiller extends Thread {
-
-       public GogoShellKiller() {
-               super("Gogo Shell Killer");
-               setDaemon(true);
-       }
-
-       @Override
-       public void run() {
-               ThreadGroup rootTg = getRootThreadGroup(null);
-               Thread gogoShellThread = findGogoShellThread(rootTg);
-               if (gogoShellThread == null)
-                       return;
-               while (getNonDaemonCount(rootTg) > 2) {
-                       try {
-                               Thread.sleep(100);
-                       } catch (InterruptedException e) {
-                               // silent
-                       }
-               }
-               gogoShellThread = findGogoShellThread(rootTg);
-               if (gogoShellThread == null)
-                       return;
-               // No non-deamon threads left, forcibly halting the VM
-               Runtime.getRuntime().halt(0);
-       }
-
-       private ThreadGroup getRootThreadGroup(ThreadGroup tg) {
-               if (tg == null)
-                       tg = Thread.currentThread().getThreadGroup();
-               if (tg.getParent() == null)
-                       return tg;
-               else
-                       return getRootThreadGroup(tg.getParent());
-       }
-
-       private int getNonDaemonCount(ThreadGroup rootThreadGroup) {
-               Thread[] threads = new Thread[rootThreadGroup.activeCount()];
-               rootThreadGroup.enumerate(threads);
-               int nonDameonCount = 0;
-               for (Thread t : threads)
-                       if (t != null && !t.isDaemon())
-                               nonDameonCount++;
-               return nonDameonCount;
-       }
-
-       private Thread findGogoShellThread(ThreadGroup rootThreadGroup) {
-               Thread[] threads = new Thread[rootThreadGroup.activeCount()];
-               rootThreadGroup.enumerate(threads, true);
-               for (Thread thread : threads) {
-                       if (thread.getName().equals("pipe-gosh --login --noshutdown"))
-                               return thread;
-               }
-               return null;
-       }
-
-}
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java
deleted file mode 100644 (file)
index a2006a7..0000000
+++ /dev/null
@@ -1,288 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.io.Reader;
-import java.net.InetAddress;
-import java.net.URI;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.KeyStore;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.apache.commons.io.FileUtils;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.http.InternalHttpConstants;
-import org.argeo.osgi.useradmin.UserAdminConf;
-
-/**
- * Interprets framework properties in order to generate the initial deploy
- * configuration.
- */
-class InitUtils {
-       private final static CmsLog log = CmsLog.getLog(InitUtils.class);
-
-
-       /** Override the provided config with the framework properties */
-       static Dictionary<String, Object> getHttpServerConfig(Dictionary<String, Object> provided) {
-               String httpPort = getFrameworkProp("org.osgi.service.http.port");
-               String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
-               /// TODO make it more generic
-               String httpHost = getFrameworkProp(
-                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTP_HOST);
-               String httpsHost = getFrameworkProp(
-                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTPS_HOST);
-               String webSocketEnabled = getFrameworkProp(
-                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.WEBSOCKET_ENABLED);
-
-               final Hashtable<String, Object> props = new Hashtable<String, Object>();
-               // try {
-               if (httpPort != null || httpsPort != null) {
-                       boolean httpEnabled = httpPort != null;
-                       props.put(InternalHttpConstants.HTTP_ENABLED, httpEnabled);
-                       boolean httpsEnabled = httpsPort != null;
-                       props.put(InternalHttpConstants.HTTPS_ENABLED, httpsEnabled);
-
-                       if (httpEnabled) {
-                               props.put(InternalHttpConstants.HTTP_PORT, httpPort);
-                               if (httpHost != null)
-                                       props.put(InternalHttpConstants.HTTP_HOST, httpHost);
-                       }
-
-                       if (httpsEnabled) {
-                               props.put(InternalHttpConstants.HTTPS_PORT, httpsPort);
-                               if (httpsHost != null)
-                                       props.put(InternalHttpConstants.HTTPS_HOST, httpsHost);
-
-                               // server certificate
-                               Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH);
-                               Path pemKeyPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_KEY_PATH);
-                               Path pemCertPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_CERT_PATH);
-                               String keyStorePasswordStr = getFrameworkProp(
-                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD);
-                               char[] keyStorePassword;
-                               if (keyStorePasswordStr == null)
-                                       keyStorePassword = "changeit".toCharArray();
-                               else
-                                       keyStorePassword = keyStorePasswordStr.toCharArray();
-
-                               // if PEM files both exists, update the PKCS12 file
-                               if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) {
-                                       // TODO check certificate update time? monitor changes?
-                                       KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
-                                       try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII);
-                                                       Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) {
-                                               PkiUtils.loadPem(keyStore, key, keyStorePassword, cert);
-                                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
-                                               if (log.isDebugEnabled())
-                                                       log.debug("PEM certificate stored in " + keyStorePath);
-                                       } catch (IOException e) {
-                                               log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e);
-                                       }
-                               }
-
-                               if (!Files.exists(keyStorePath))
-                                       createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
-                               props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12);
-                               props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString());
-                               props.put(InternalHttpConstants.SSL_PASSWORD, new String(keyStorePassword));
-
-//                             props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11");
-//                             props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb");
-//                             props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword);
-
-                               // client certificate authentication
-                               String wantClientAuth = getFrameworkProp(
-                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_WANTCLIENTAUTH);
-                               if (wantClientAuth != null)
-                                       props.put(InternalHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
-                               String needClientAuth = getFrameworkProp(
-                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_NEEDCLIENTAUTH);
-                               if (needClientAuth != null)
-                                       props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
-                       }
-
-                       // web socket
-                       if (webSocketEnabled != null && webSocketEnabled.equals("true"))
-                               props.put(InternalHttpConstants.WEBSOCKET_ENABLED, true);
-
-                       props.put(CmsConstants.CN, CmsConstants.DEFAULT);
-               }
-               return props;
-       }
-
-       static List<Dictionary<String, Object>> getUserDirectoryConfigs() {
-               List<Dictionary<String, Object>> res = new ArrayList<>();
-               File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile();
-               List<String> uris = new ArrayList<>();
-
-               // node roles
-               String nodeRolesUri = getFrameworkProp(CmsConstants.ROLES_URI);
-               String baseNodeRoleDn = CmsConstants.ROLES_BASEDN;
-               if (nodeRolesUri == null) {
-                       nodeRolesUri = baseNodeRoleDn + ".ldif";
-                       File nodeRolesFile = new File(nodeBaseDir, nodeRolesUri);
-                       if (!nodeRolesFile.exists())
-                               try {
-                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeRoleDn + ".ldif"),
-                                                       nodeRolesFile);
-                               } catch (IOException e) {
-                                       throw new RuntimeException("Cannot copy demo resource", e);
-                               }
-                       // nodeRolesUri = nodeRolesFile.toURI().toString();
-               }
-               uris.add(nodeRolesUri);
-
-               // node tokens
-               String nodeTokensUri = getFrameworkProp(CmsConstants.TOKENS_URI);
-               String baseNodeTokensDn = CmsConstants.TOKENS_BASEDN;
-               if (nodeTokensUri == null) {
-                       nodeTokensUri = baseNodeTokensDn + ".ldif";
-                       File nodeTokensFile = new File(nodeBaseDir, nodeTokensUri);
-                       if (!nodeTokensFile.exists())
-                               try {
-                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeTokensDn + ".ldif"),
-                                                       nodeTokensFile);
-                               } catch (IOException e) {
-                                       throw new RuntimeException("Cannot copy demo resource", e);
-                               }
-                       // nodeRolesUri = nodeRolesFile.toURI().toString();
-               }
-               uris.add(nodeTokensUri);
-
-               // Business roles
-               String userAdminUris = getFrameworkProp(CmsConstants.USERADMIN_URIS);
-               if (userAdminUris == null) {
-                       String demoBaseDn = "dc=example,dc=com";
-                       userAdminUris = demoBaseDn + ".ldif";
-                       File businessRolesFile = new File(nodeBaseDir, userAdminUris);
-                       File systemRolesFile = new File(nodeBaseDir, "ou=roles,ou=node.ldif");
-                       if (!businessRolesFile.exists())
-                               try {
-                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(demoBaseDn + ".ldif"),
-                                                       businessRolesFile);
-                                       if (!systemRolesFile.exists())
-                                               FileUtils.copyInputStreamToFile(
-                                                               InitUtils.class.getResourceAsStream("example-ou=roles,ou=node.ldif"), systemRolesFile);
-                               } catch (IOException e) {
-                                       throw new RuntimeException("Cannot copy demo resources", e);
-                               }
-                       // userAdminUris = businessRolesFile.toURI().toString();
-                       log.warn("## DEV Using dummy base DN " + demoBaseDn);
-                       // TODO downgrade security level
-               }
-               for (String userAdminUri : userAdminUris.split(" "))
-                       uris.add(userAdminUri);
-
-               // Interprets URIs
-               for (String uri : uris) {
-                       URI u;
-                       try {
-                               u = new URI(uri);
-                               if (u.getPath() == null)
-                                       throw new IllegalArgumentException(
-                                                       "URI " + uri + " must have a path in order to determine base DN");
-                               if (u.getScheme() == null) {
-                                       if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
-                                               u = new File(uri).getCanonicalFile().toURI();
-                                       else if (!uri.contains("/")) {
-                                               // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri);
-                                               u = new URI(uri);
-                                       } else
-                                               throw new IllegalArgumentException("Cannot interpret " + uri + " as an uri");
-                               } else if (u.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
-                                       u = new File(u).getCanonicalFile().toURI();
-                               }
-                       } catch (Exception e) {
-                               throw new RuntimeException("Cannot interpret " + uri + " as an uri", e);
-                       }
-                       Dictionary<String, Object> properties = UserAdminConf.uriAsProperties(u.toString());
-                       res.add(properties);
-               }
-
-               return res;
-       }
-
-       /**
-        * Called before node initialisation, in order populate OSGi instance are with
-        * some files (typically LDIF, etc).
-        */
-       static void prepareFirstInitInstanceArea() {
-               String nodeInits = getFrameworkProp(CmsConstants.NODE_INIT);
-               if (nodeInits == null)
-                       nodeInits = "../../init";
-
-               for (String nodeInit : nodeInits.split(",")) {
-
-                       if (nodeInit.startsWith("http")) {
-                               // TODO reconnect it
-                               //registerRemoteInit(nodeInit);
-                       } else {
-
-                               // TODO use java.nio.file
-                               File initDir;
-                               if (nodeInit.startsWith("."))
-                                       initDir = KernelUtils.getExecutionDir(nodeInit);
-                               else
-                                       initDir = new File(nodeInit);
-                               // TODO also uncompress archives
-                               if (initDir.exists())
-                                       try {
-                                               FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstanceDir(), new FileFilter() {
-
-                                                       @Override
-                                                       public boolean accept(File pathname) {
-                                                               if (pathname.getName().equals(".svn") || pathname.getName().equals(".git"))
-                                                                       return false;
-                                                               return true;
-                                                       }
-                                               });
-                                               log.info("CMS initialized from " + initDir.getCanonicalPath());
-                                       } catch (IOException e) {
-                                               throw new RuntimeException("Cannot initialize from " + initDir, e);
-                                       }
-                       }
-               }
-       }
-
-       private static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) {
-               // for (Provider provider : Security.getProviders())
-               // System.out.println(provider.getName());
-//             File keyStoreFile = keyStorePath.toFile();
-               char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length);
-               if (!Files.exists(keyStorePath)) {
-                       try {
-                               Files.createDirectories(keyStorePath.getParent());
-                               KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
-                               PkiUtils.generateSelfSignedCertificate(keyStore,
-                                               new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"),
-                                               1024, keyPwd);
-                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
-                               if (log.isDebugEnabled())
-                                       log.debug("Created self-signed unsecure keystore " + keyStorePath);
-                       } catch (Exception e) {
-                               try {
-                                       if (Files.size(keyStorePath) == 0)
-                                               Files.delete(keyStorePath);
-                               } catch (IOException e1) {
-                                       // silent
-                               }
-                               log.error("Cannot create keystore " + keyStorePath, e);
-                       }
-               } else {
-                       throw new IllegalStateException("Keystore " + keyStorePath + " already exists");
-               }
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java
deleted file mode 100644 (file)
index 90f2382..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import org.argeo.api.cms.CmsConstants;
-
-/** Internal CMS constants. */
-public interface KernelConstants {
-       // Directories
-       String DIR_NODE = "node";
-       String DIR_REPOS = "repos";
-       String DIR_INDEXES = "indexes";
-       String DIR_TRANSACTIONS = "transactions";
-
-       // Files
-       String DEPLOY_CONFIG_PATH = DIR_NODE + '/' + CmsConstants.DEPLOY_BASEDN + ".ldif";
-       String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".p12";
-       String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".key";
-       String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".crt";
-       String NODE_KEY_TAB_PATH = DIR_NODE + "/krb5.keytab";
-
-       // Security
-       String JAAS_CONFIG = "/org/argeo/cms/internal/kernel/jaas.cfg";
-       String JAAS_CONFIG_IPA = "/org/argeo/cms/internal/kernel/jaas-ipa.cfg";
-
-       // Java
-       String JAAS_CONFIG_PROP = "java.security.auth.login.config";
-
-       // DEFAULTS JCR PATH
-       String DEFAULT_HOME_BASE_PATH = "/home";
-       String DEFAULT_USERS_BASE_PATH = "/users";
-       String DEFAULT_GROUPS_BASE_PATH = "/groups";
-       
-       // KERBEROS
-       String DEFAULT_KERBEROS_SERVICE = "HTTP";
-
-       // HTTP client
-       String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility";
-
-       // RWT / RAP
-       // String PATH_WORKBENCH = "/ui";
-       // String PATH_WORKBENCH_PUBLIC = PATH_WORKBENCH + "/public";
-
-       String JETTY_FACTORY_PID = "org.eclipse.equinox.http.jetty.config";
-       String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern";
-       // default Jetty server configured via JettyConfigurator
-       String DEFAULT_JETTY_SERVER = "default";
-       String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
-
-       // avoid dependencies
-       String CONTEXT_NAME_PROP = "contextName";
-       String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
-       String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java
deleted file mode 100644 (file)
index f267933..0000000
+++ /dev/null
@@ -1,252 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.URIParameter;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.Properties;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.osgi.DataModelNamespace;
-import org.osgi.framework.BundleContext;
-import org.osgi.util.tracker.ServiceTracker;
-
-/** Package utilities */
-class KernelUtils implements KernelConstants {
-       final static String OSGI_INSTANCE_AREA = "osgi.instance.area";
-       final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
-
-       static void setJaasConfiguration(URL jaasConfigurationUrl) {
-               try {
-                       URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
-                       javax.security.auth.login.Configuration jaasConfiguration = javax.security.auth.login.Configuration
-                                       .getInstance("JavaLoginConfig", uriParameter);
-                       javax.security.auth.login.Configuration.setConfiguration(jaasConfiguration);
-               } catch (Exception e) {
-                       throw new IllegalArgumentException("Cannot set configuration " + jaasConfigurationUrl, e);
-               }
-       }
-
-       static Dictionary<String, ?> asDictionary(Properties props) {
-               Hashtable<String, Object> hashtable = new Hashtable<String, Object>();
-               for (Object key : props.keySet()) {
-                       hashtable.put(key.toString(), props.get(key));
-               }
-               return hashtable;
-       }
-
-       static Dictionary<String, ?> asDictionary(ClassLoader cl, String resource) {
-               Properties props = new Properties();
-               try {
-                       props.load(cl.getResourceAsStream(resource));
-               } catch (IOException e) {
-                       throw new IllegalArgumentException("Cannot load " + resource + " from classpath", e);
-               }
-               return asDictionary(props);
-       }
-
-       static File getExecutionDir(String relativePath) {
-               File executionDir = new File(getFrameworkProp("user.dir"));
-               if (relativePath == null)
-                       return executionDir;
-               try {
-                       return new File(executionDir, relativePath).getCanonicalFile();
-               } catch (IOException e) {
-                       throw new IllegalArgumentException("Cannot get canonical file", e);
-               }
-       }
-
-       static File getOsgiInstanceDir() {
-               return new File(getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length()))
-                               .getAbsoluteFile();
-       }
-
-       static Path getOsgiInstancePath(String relativePath) {
-               return Paths.get(getOsgiInstanceUri(relativePath));
-       }
-
-       static URI getOsgiInstanceUri(String relativePath) {
-               String osgiInstanceBaseUri = getFrameworkProp(OSGI_INSTANCE_AREA);
-               if (osgiInstanceBaseUri != null)
-                       return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : ""));
-               else
-                       return Paths.get(System.getProperty("user.dir")).toUri();
-       }
-
-       static File getOsgiConfigurationFile(String relativePath) {
-               try {
-                       return new File(new URI(getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath))
-                                       .getCanonicalFile();
-               } catch (Exception e) {
-                       throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e);
-               }
-       }
-
-       static String getFrameworkProp(String key, String def) {
-               BundleContext bundleContext = Activator.getBundleContext();
-               String value;
-               if (bundleContext != null)
-                       value = bundleContext.getProperty(key);
-               else
-                       value = System.getProperty(key);
-               if (value == null)
-                       return def;
-               return value;
-       }
-
-       static String getFrameworkProp(String key) {
-               return getFrameworkProp(key, null);
-       }
-
-       // Security
-       // static Subject anonymousLogin() {
-       // Subject subject = new Subject();
-       // LoginContext lc;
-       // try {
-       // lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject);
-       // lc.login();
-       // return subject;
-       // } catch (LoginException e) {
-       // throw new CmsException("Cannot login as anonymous", e);
-       // }
-       // }
-
-       static void logFrameworkProperties(CmsLog log) {
-               BundleContext bc = getBundleContext();
-               for (Object sysProp : new TreeSet<Object>(System.getProperties().keySet())) {
-                       log.debug(sysProp + "=" + bc.getProperty(sysProp.toString()));
-               }
-               // String[] keys = { Constants.FRAMEWORK_STORAGE,
-               // Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_OS_VERSION,
-               // Constants.FRAMEWORK_PROCESSOR, Constants.FRAMEWORK_SECURITY,
-               // Constants.FRAMEWORK_TRUST_REPOSITORIES,
-               // Constants.FRAMEWORK_WINDOWSYSTEM, Constants.FRAMEWORK_VENDOR,
-               // Constants.FRAMEWORK_VERSION, Constants.FRAMEWORK_STORAGE_CLEAN,
-               // Constants.FRAMEWORK_LANGUAGE, Constants.FRAMEWORK_UUID };
-               // for (String key : keys)
-               // log.debug(key + "=" + bc.getProperty(key));
-       }
-
-       static void printSystemProperties(PrintStream out) {
-               TreeMap<String, String> display = new TreeMap<>();
-               for (Object key : System.getProperties().keySet())
-                       display.put(key.toString(), System.getProperty(key.toString()));
-               for (String key : display.keySet())
-                       out.println(key + "=" + display.get(key));
-       }
-
-//     static Session openAdminSession(Repository repository) {
-//             return openAdminSession(repository, null);
-//     }
-//
-//     static Session openAdminSession(final Repository repository, final String workspaceName) {
-//             LoginContext loginContext = loginAsDataAdmin();
-//             return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
-//
-//                     @Override
-//                     public Session run() {
-//                             try {
-//                                     return repository.login(workspaceName);
-//                             } catch (RepositoryException e) {
-//                                     throw new IllegalStateException("Cannot open admin session", e);
-//                             } finally {
-//                                     try {
-//                                             loginContext.logout();
-//                                     } catch (LoginException e) {
-//                                             throw new IllegalStateException(e);
-//                                     }
-//                             }
-//                     }
-//
-//             });
-//     }
-//
-//     static LoginContext loginAsDataAdmin() {
-//             ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
-//             Thread.currentThread().setContextClassLoader(KernelUtils.class.getClassLoader());
-//             LoginContext loginContext;
-//             try {
-//                     loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_DATA_ADMIN);
-//                     loginContext.login();
-//             } catch (LoginException e1) {
-//                     throw new IllegalStateException("Could not login as data admin", e1);
-//             } finally {
-//                     Thread.currentThread().setContextClassLoader(currentCl);
-//             }
-//             return loginContext;
-//     }
-
-//     static void doAsDataAdmin(Runnable action) {
-//             LoginContext loginContext = loginAsDataAdmin();
-//             Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
-//
-//                     @Override
-//                     public Void run() {
-//                             try {
-//                                     action.run();
-//                                     return null;
-//                             } finally {
-//                                     try {
-//                                             loginContext.logout();
-//                                     } catch (LoginException e) {
-//                                             throw new IllegalStateException(e);
-//                                     }
-//                             }
-//                     }
-//
-//             });
-//     }
-
-       static void asyncOpen(ServiceTracker<?, ?> st) {
-               Runnable run = new Runnable() {
-
-                       @Override
-                       public void run() {
-                               st.open();
-                       }
-               };
-               Activator.getInternalExecutorService().execute(run);
-//             new Thread(run, "Open service tracker " + st).start();
-       }
-
-       static BundleContext getBundleContext() {
-               return Activator.getBundleContext();
-       }
-
-       static boolean asBoolean(String value) {
-               if (value == null)
-                       return false;
-               switch (value) {
-               case "true":
-                       return true;
-               case "false":
-                       return false;
-               default:
-                       throw new IllegalArgumentException(
-                                       "Unsupported value for attribute " + DataModelNamespace.ABSTRACT + ": " + value);
-               }
-       }
-
-       private static URI safeUri(String uri) {
-               if (uri == null)
-                       throw new IllegalArgumentException("URI cannot be null");
-               try {
-                       return new URI(uri);
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException("Badly formatted URI " + uri, e);
-               }
-       }
-
-       private KernelUtils() {
-
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java
deleted file mode 100644 (file)
index 0b41037..0000000
+++ /dev/null
@@ -1,539 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.file.FileSystems;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardWatchEventKinds;
-import java.nio.file.WatchEvent;
-import java.nio.file.WatchKey;
-import java.nio.file.WatchService;
-import java.security.SignatureException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ArgeoLogListener;
-import org.argeo.cms.ArgeoLogger;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.cm.ConfigurationAdmin;
-import org.osgi.service.log.LogEntry;
-import org.osgi.service.log.LogLevel;
-import org.osgi.service.log.LogListener;
-import org.osgi.service.log.LogReaderService;
-
-/** Not meant to be used directly in standard log4j config */
-class NodeLogger implements ArgeoLogger, LogListener {
-       /** Internal debug for development purposes. */
-       private static Boolean debug = false;
-
-       private Boolean disabled = false;
-
-       private String level = null;
-
-//     private Level log4jLevel = null;
-
-       private Properties configuration;
-
-       private AppenderImpl appender;
-
-       private final List<ArgeoLogListener> everythingListeners = Collections
-                       .synchronizedList(new ArrayList<ArgeoLogListener>());
-       private final List<ArgeoLogListener> allUsersListeners = Collections
-                       .synchronizedList(new ArrayList<ArgeoLogListener>());
-       private final Map<String, List<ArgeoLogListener>> userListeners = Collections
-                       .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
-
-       private BlockingQueue<LogEvent> events;
-       private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
-
-       private Integer maxLastEventsCount = 10 * 1000;
-
-       /** Marker to prevent stack overflow */
-       private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
-
-               @Override
-               protected Boolean initialValue() {
-                       return false;
-               }
-       };
-
-       public NodeLogger(LogReaderService lrs) {
-               if (lrs != null) {
-                       Enumeration<LogEntry> logEntries = lrs.getLog();
-                       while (logEntries.hasMoreElements())
-                               logged(logEntries.nextElement());
-                       lrs.addLogListener(this);
-
-                       // configure log4j watcher
-                       String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration");
-                       if (log4jConfiguration != null && log4jConfiguration.startsWith("file:")) {
-                               if (log4jConfiguration.contains("..")) {
-                                       if (log4jConfiguration.startsWith("file://"))
-                                               log4jConfiguration = log4jConfiguration.substring("file://".length());
-                                       else if (log4jConfiguration.startsWith("file:"))
-                                               log4jConfiguration = log4jConfiguration.substring("file:".length());
-                               }
-                               try {
-                                       Path log4jconfigPath;
-                                       if (log4jConfiguration.startsWith("file:"))
-                                               log4jconfigPath = Paths.get(new URI(log4jConfiguration));
-                                       else
-                                               log4jconfigPath = Paths.get(log4jConfiguration);
-                                       Thread log4jConfWatcher = new Log4jConfWatcherThread(log4jconfigPath);
-                                       log4jConfWatcher.start();
-                               } catch (Exception e) {
-                                       stdErr("Badly formatted log4j configuration URI " + log4jConfiguration + ": " + e.getMessage());
-                               }
-                       }
-               }
-       }
-
-       public void init() {
-               try {
-                       events = new LinkedBlockingQueue<LogEvent>();
-
-                       // if (layout != null)
-                       // setLayout(layout);
-                       // else
-                       // setLayout(new PatternLayout(pattern));
-                       appender = new AppenderImpl();
-                       reloadConfiguration();
-//                     Logger.getRootLogger().addAppender(appender);
-
-                       logDispatcherThread = new LogDispatcherThread();
-                       logDispatcherThread.start();
-               } catch (Exception e) {
-                       throw new CmsException("Cannot initialize log4j");
-               }
-       }
-
-       public void destroy() throws Exception {
-//             Logger.getRootLogger().removeAppender(appender);
-               allUsersListeners.clear();
-               for (List<ArgeoLogListener> lst : userListeners.values())
-                       lst.clear();
-               userListeners.clear();
-
-               events.clear();
-               events = null;
-               logDispatcherThread.interrupt();
-       }
-
-       // public void setLayout(Layout layout) {
-       // this.layout = layout;
-       // }
-
-       public String toString() {
-               return "Node Logger";
-       }
-
-       //
-       // OSGi LOGGER
-       //
-       @Override
-       public void logged(LogEntry status) {
-               CmsLog pluginLog = CmsLog.getLog(status.getBundle().getSymbolicName());
-               LogLevel severity = status.getLogLevel();
-               if (severity.equals(LogLevel.ERROR) && pluginLog.isErrorEnabled()) {
-                       // FIXME Fix Argeo TP
-                       if (status.getException() instanceof SignatureException)
-                               return;
-                       pluginLog.error(msg(status), status.getException());
-               } else if (severity.equals(LogLevel.WARN) && pluginLog.isWarnEnabled()) {
-                       if (pluginLog.isTraceEnabled())
-                               pluginLog.warn(msg(status), status.getException());
-                       else
-                               pluginLog.warn(msg(status));
-               } else if (severity.equals(LogLevel.INFO) && pluginLog.isDebugEnabled())
-                       pluginLog.debug(msg(status), status.getException());
-               else if (severity.equals(LogLevel.DEBUG) && pluginLog.isTraceEnabled())
-                       pluginLog.trace(msg(status), status.getException());
-               else if (severity.equals(LogLevel.TRACE) && pluginLog.isTraceEnabled())
-                       pluginLog.trace(msg(status), status.getException());
-       }
-
-       private String msg(LogEntry status) {
-               StringBuilder sb = new StringBuilder();
-               sb.append(status.getMessage());
-               Bundle bundle = status.getBundle();
-               if (bundle != null) {
-                       sb.append(" '" + bundle.getSymbolicName() + "'");
-               }
-               ServiceReference<?> sr = status.getServiceReference();
-               if (sr != null) {
-                       sb.append(' ');
-                       String[] objectClasses = (String[]) sr.getProperty(Constants.OBJECTCLASS);
-                       if (isSpringApplicationContext(objectClasses)) {
-                               sb.append("{org.springframework.context.ApplicationContext}");
-                               Object symbolicName = sr.getProperty(Constants.BUNDLE_SYMBOLICNAME);
-                               if (symbolicName != null)
-                                       sb.append(" " + Constants.BUNDLE_SYMBOLICNAME + ": " + symbolicName);
-                       } else {
-                               sb.append(arrayToString(objectClasses));
-                       }
-                       Object cn = sr.getProperty(CmsConstants.CN);
-                       if (cn != null)
-                               sb.append(" " + CmsConstants.CN + ": " + cn);
-                       Object factoryPid = sr.getProperty(ConfigurationAdmin.SERVICE_FACTORYPID);
-                       if (factoryPid != null)
-                               sb.append(" " + ConfigurationAdmin.SERVICE_FACTORYPID + ": " + factoryPid);
-                       // else {
-                       // Object servicePid = sr.getProperty(Constants.SERVICE_PID);
-                       // if (servicePid != null)
-                       // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid);
-                       // }
-                       // servlets
-                       Object whiteBoardPattern = sr.getProperty(KernelConstants.WHITEBOARD_PATTERN_PROP);
-                       if (whiteBoardPattern != null) {
-                               if (whiteBoardPattern instanceof String) {
-                                       sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern);
-                               } else {
-                                       sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": "
-                                                       + arrayToString((String[]) whiteBoardPattern));
-                               }
-                       }
-                       // RWT
-                       Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP);
-                       if (contextName != null)
-                               sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName);
-
-                       // user directories
-                       Object baseDn = sr.getProperty(UserAdminConf.baseDn.name());
-                       if (baseDn != null)
-                               sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn);
-
-               }
-               return sb.toString();
-       }
-
-       private String arrayToString(Object[] arr) {
-               StringBuilder sb = new StringBuilder();
-               sb.append('[');
-               for (int i = 0; i < arr.length; i++) {
-                       if (i != 0)
-                               sb.append(',');
-                       sb.append(arr[i]);
-               }
-               sb.append(']');
-               return sb.toString();
-       }
-
-       private boolean isSpringApplicationContext(String[] objectClasses) {
-               for (String clss : objectClasses) {
-                       if (clss.equals("org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext")) {
-                               return true;
-                       }
-               }
-               return false;
-       }
-
-       //
-       // ARGEO LOGGER
-       //
-
-       public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
-               String username = CurrentUser.getUsername();
-               if (username == null)
-                       throw new CmsException("Only authenticated users can register a log listener");
-
-               if (!userListeners.containsKey(username)) {
-                       List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
-                       userListeners.put(username, lst);
-               }
-               userListeners.get(username).add(listener);
-               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
-               for (LogEvent evt : lastEvents)
-                       dispatchEvent(listener, evt);
-       }
-
-       public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
-                       boolean everything) {
-               if (everything)
-                       everythingListeners.add(listener);
-               else
-                       allUsersListeners.add(listener);
-               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
-               for (LogEvent evt : lastEvents)
-                       if (everything || evt.getUsername() != null)
-                               dispatchEvent(listener, evt);
-       }
-
-       public synchronized void unregister(ArgeoLogListener listener) {
-               String username = CurrentUser.getUsername();
-               if (username == null)// FIXME
-                       return;
-               if (!userListeners.containsKey(username))
-                       throw new CmsException("No user listeners " + listener + " registered for user " + username);
-               if (!userListeners.get(username).contains(listener))
-                       throw new CmsException("No user listeners " + listener + " registered for user " + username);
-               userListeners.get(username).remove(listener);
-               if (userListeners.get(username).isEmpty())
-                       userListeners.remove(username);
-
-       }
-
-       public synchronized void unregisterForAll(ArgeoLogListener listener) {
-               everythingListeners.remove(listener);
-               allUsersListeners.remove(listener);
-       }
-
-       /** For development purpose, since using regular logging is not easy here */
-       private static void stdOut(Object obj) {
-               System.out.println(obj);
-       }
-
-       private static void stdErr(Object obj) {
-               System.err.println(obj);
-       }
-
-       private static void debug(Object obj) {
-               if (debug)
-                       System.out.println(obj);
-       }
-
-       private static boolean isInternalDebugEnabled() {
-               return debug;
-       }
-
-       // public void setPattern(String pattern) {
-       // this.pattern = pattern;
-       // }
-
-       public void setDisabled(Boolean disabled) {
-               this.disabled = disabled;
-       }
-
-       public void setLevel(String level) {
-               this.level = level;
-       }
-
-       public void setConfiguration(Properties configuration) {
-               this.configuration = configuration;
-       }
-
-       public void updateConfiguration(Properties configuration) {
-               setConfiguration(configuration);
-               reloadConfiguration();
-       }
-
-       public Properties getConfiguration() {
-               return configuration;
-       }
-
-       /**
-        * Reloads configuration (if the configuration {@link Properties} is set)
-        */
-       protected void reloadConfiguration() {
-               if (configuration != null) {
-//                     LogManager.resetConfiguration();
-//                     PropertyConfigurator.configure(configuration);
-               }
-       }
-
-       protected synchronized void processLoggingEvent(LogEvent event) {
-               if (disabled)
-                       return;
-
-               if (dispatching.get())
-                       return;
-
-               if (level != null && !level.trim().equals("")) {
-//                     if (log4jLevel == null || !log4jLevel.toString().equals(level))
-//                             try {
-//                                     log4jLevel = Level.toLevel(level);
-//                             } catch (Exception e) {
-//                                     System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
-//                                     e.printStackTrace();
-//                                     level = null;
-//                             }
-//
-//                     if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
-//                             return;
-//                     }
-               }
-
-               try {
-                       // admin listeners
-                       Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
-                       while (everythingIt.hasNext())
-                               dispatchEvent(everythingIt.next(), event);
-
-                       if (event.getUsername() != null) {
-                               Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
-                               while (allUsersIt.hasNext())
-                                       dispatchEvent(allUsersIt.next(), event);
-
-                               if (userListeners.containsKey(event.getUsername())) {
-                                       Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
-                                       while (userIt.hasNext())
-                                               dispatchEvent(userIt.next(), event);
-                               }
-                       }
-               } catch (Exception e) {
-                       stdOut("Cannot process logging event");
-                       e.printStackTrace();
-               }
-       }
-
-       protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
-//             LoggingEvent event = evt.getLoggingEvent();
-//             logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
-//                             event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
-       }
-
-       private class AppenderImpl { //extends AppenderSkeleton {
-               public boolean requiresLayout() {
-                       return false;
-               }
-
-               public void close() {
-               }
-
-//             @Override
-//             protected void append(LoggingEvent event) {
-//                     if (events != null) {
-//                             try {
-//                                     String username = CurrentUser.getUsername();
-//                                     events.put(new LogEvent(username, event));
-//                             } catch (InterruptedException e) {
-//                                     // silent
-//                             }
-//                     }
-//             }
-
-       }
-
-       private class LogDispatcherThread extends Thread {
-               /** encapsulated in order to simplify concurrency management */
-               private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
-
-               public LogDispatcherThread() {
-                       super("Argeo Logging Dispatcher Thread");
-               }
-
-               public void run() {
-                       while (events != null) {
-                               try {
-                                       LogEvent loggingEvent = events.take();
-                                       processLoggingEvent(loggingEvent);
-                                       addLastEvent(loggingEvent);
-                               } catch (InterruptedException e) {
-                                       if (events == null)
-                                               return;
-                               }
-                       }
-               }
-
-               protected synchronized void addLastEvent(LogEvent loggingEvent) {
-                       if (lastEvents.size() >= maxLastEventsCount)
-                               lastEvents.poll();
-                       lastEvents.add(loggingEvent);
-               }
-
-               public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
-                       LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
-                       ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
-                       int count = 0;
-                       while (it.hasPrevious() && (count < maxCount)) {
-                               LogEvent evt = it.previous();
-                               if (username == null || username.equals(evt.getUsername())) {
-                                       evts.push(evt);
-                                       count++;
-                               }
-                       }
-                       return evts;
-               }
-       }
-
-       private class LogEvent {
-               private final String username;
-//             private final LoggingEvent loggingEvent;
-
-               public LogEvent(String username) {
-                       super();
-                       this.username = username;
-//                     this.loggingEvent = loggingEvent;
-               }
-
-//             @Override
-//             public int hashCode() {
-//                     return loggingEvent.hashCode();
-//             }
-//
-//             @Override
-//             public boolean equals(Object obj) {
-//                     return loggingEvent.equals(obj);
-//             }
-//
-//             @Override
-//             public String toString() {
-//                     return username + "@ " + loggingEvent.toString();
-//             }
-
-               public String getUsername() {
-                       return username;
-               }
-
-//             public LoggingEvent getLoggingEvent() {
-//                     return loggingEvent;
-//             }
-
-       }
-
-       private class Log4jConfWatcherThread extends Thread {
-               private Path log4jConfigurationPath;
-
-               public Log4jConfWatcherThread(Path log4jConfigurationPath) {
-                       super("Log4j Configuration Watcher");
-                       try {
-                               this.log4jConfigurationPath = log4jConfigurationPath.toRealPath();
-                       } catch (IOException e) {
-                               this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath();
-                               stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage());
-                       }
-               }
-
-               public void run() {
-                       Path parentDir = log4jConfigurationPath.getParent();
-                       try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
-                               parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
-                               WatchKey wk;
-                               watching: while ((wk = watchService.take()) != null) {
-                                       for (WatchEvent<?> event : wk.pollEvents()) {
-                                               final Path changed = (Path) event.context();
-                                               if (log4jConfigurationPath.equals(parentDir.resolve(changed))) {
-                                                       if (isInternalDebugEnabled())
-                                                               debug(log4jConfigurationPath + " has changed, reloading.");
-//                                                     PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL());
-                                               }
-                                       }
-                                       // reset the key
-                                       boolean valid = wk.reset();
-                                       if (!valid) {
-                                               break watching;
-                                       }
-                               }
-                       } catch (IOException | InterruptedException e) {
-                               stdErr("Log4j configuration watcher failed: " + e.getMessage());
-                       }
-               }
-       }
-}
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
deleted file mode 100644 (file)
index 17daa3e..0000000
+++ /dev/null
@@ -1,349 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.kerberos.KerberosPrincipal;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.commons.httpclient.auth.AuthPolicy;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-import org.apache.commons.httpclient.params.DefaultHttpParams;
-import org.apache.commons.httpclient.params.HttpMethodParams;
-import org.apache.commons.httpclient.params.HttpParams;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.internal.auth.CmsUserManagerImpl;
-import org.argeo.cms.internal.http.client.HttpCredentialProvider;
-import org.argeo.cms.internal.http.client.SpnegoAuthScheme;
-import org.argeo.osgi.transaction.WorkControl;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.osgi.useradmin.AbstractUserDirectory;
-import org.argeo.osgi.useradmin.AggregatingUserAdmin;
-import org.argeo.osgi.useradmin.LdapUserAdmin;
-import org.argeo.osgi.useradmin.LdifUserAdmin;
-import org.argeo.osgi.useradmin.OsUserDirectory;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.osgi.useradmin.UserDirectory;
-import org.argeo.util.naming.DnsBrowser;
-import org.ietf.jgss.GSSCredential;
-import org.ietf.jgss.GSSException;
-import org.ietf.jgss.GSSManager;
-import org.ietf.jgss.GSSName;
-import org.ietf.jgss.Oid;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.cm.ConfigurationException;
-import org.osgi.service.cm.ManagedServiceFactory;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * Aggregates multiple {@link UserDirectory} and integrates them with system
- * roles.
- */
-class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactory, KernelConstants {
-       private final static CmsLog log = CmsLog.getLog(NodeUserAdmin.class);
-//     private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       // OSGi
-       private Map<String, LdapName> pidToBaseDn = new HashMap<>();
-//     private Map<String, ServiceRegistration<UserDirectory>> pidToServiceRegs = new HashMap<>();
-//     private ServiceRegistration<UserAdmin> userAdminReg;
-
-       // JTA
-       private final ServiceTracker<WorkControl, WorkControl> tmTracker;
-       // private final String cacheName = UserDirectory.class.getName();
-
-       // GSS API
-       private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH);
-       private GSSCredential acceptorCredentials;
-
-       private boolean singleUser = false;
-//     private boolean systemRolesAvailable = false;
-
-       CmsUserManagerImpl userManager;
-
-       public NodeUserAdmin(String systemRolesBaseDn, String tokensBaseDn) {
-               super(systemRolesBaseDn, tokensBaseDn);
-               BundleContext bc = Activator.getBundleContext();
-               if (bc != null) {
-                       tmTracker = new ServiceTracker<>(bc, WorkControl.class, null) {
-
-                               @Override
-                               public WorkControl addingService(ServiceReference<WorkControl> reference) {
-                                       WorkControl workControl = super.addingService(reference);
-                                       userManager = new CmsUserManagerImpl();
-                                       userManager.setUserAdmin(NodeUserAdmin.this);
-                                       // FIXME make it more robust
-                                       userManager.setUserTransaction((WorkTransaction) workControl);
-                                       bc.registerService(CmsUserManager.class, userManager, null);
-                                       return workControl;
-                               }
-                       };
-                       tmTracker.open();
-               } else {
-                       tmTracker = null;
-               }
-       }
-
-       @Override
-       public void updated(String pid, Dictionary<String, ?> 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 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 (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);
-               } else if (UserAdminConf.SCHEME_OS.equals(u.getScheme())) {
-                       userDirectory = new OsUserDirectory(u, properties);
-                       singleUser = true;
-               } else {
-                       throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
-               }
-               addUserDirectory(userDirectory);
-
-               // OSGi
-               LdapName baseDn = userDirectory.getBaseDn();
-               Hashtable<String, Object> regProps = new Hashtable<>();
-               regProps.put(Constants.SERVICE_PID, pid);
-               if (isSystemRolesBaseDn(baseDn))
-                       regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-               regProps.put(UserAdminConf.baseDn.name(), baseDn);
-               // ServiceRegistration<UserDirectory> reg =
-               // bc.registerService(UserDirectory.class, userDirectory, regProps);
-               Activator.registerService(UserDirectory.class, userDirectory, regProps);
-               userManager.addUserDirectory(userDirectory, regProps);
-               pidToBaseDn.put(pid, baseDn);
-               // pidToServiceRegs.put(pid, reg);
-
-               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
-                       Dictionary<String, Object> userAdminregProps = new Hashtable<>();
-                       userAdminregProps.put(CmsConstants.CN, CmsConstants.DEFAULT);
-                       userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-                       Activator.registerService(UserAdmin.class, this, userAdminregProps);
-               }
-
-//             if (isSystemRolesBaseDn(baseDn))
-//                     systemRolesAvailable = true;
-//
-//             // start publishing only when system roles are available
-//             if (systemRolesAvailable) {
-//                     // The list of baseDns is published as properties
-//                     // TODO clients should rather reference USerDirectory services
-//                     if (userAdminReg != null)
-//                             userAdminReg.unregister();
-//                     // register self as main user admin
-//                     Dictionary<String, Object> userAdminregProps = currentState();
-//                     userAdminregProps.put(NodeConstants.CN, NodeConstants.DEFAULT);
-//                     userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-//                     userAdminReg = bc.registerService(UserAdmin.class, this, userAdminregProps);
-//             }
-       }
-
-       @Override
-       public void deleted(String pid) {
-               // assert pidToServiceRegs.get(pid) != null;
-               assert pidToBaseDn.get(pid) != null;
-               // pidToServiceRegs.remove(pid).unregister();
-               LdapName baseDn = pidToBaseDn.remove(pid);
-               removeUserDirectory(baseDn);
-       }
-
-       @Override
-       public String getName() {
-               return "Node User Admin";
-       }
-
-       @Override
-       protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
-               if (rawAuthorization.getName() == null) {
-                       sysRoles.add(CmsConstants.ROLE_ANONYMOUS);
-               } else {
-                       sysRoles.add(CmsConstants.ROLE_USER);
-               }
-       }
-
-       protected void postAdd(AbstractUserDirectory userDirectory) {
-               // JTA
-               WorkControl tm = tmTracker != null ? tmTracker.getService() : null;
-               if (tm == null)
-                       throw new IllegalStateException("A JTA transaction manager must be available.");
-               userDirectory.setTransactionControl(tm);
-//             if (tmTracker.getService() instanceof BitronixTransactionManager)
-//                     EhCacheXAResourceProducer.registerXAResource(cacheName, userDirectory.getXaResource());
-
-               Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
-               if (realm != null) {
-                       if (Files.exists(nodeKeyTab)) {
-                               String servicePrincipal = getKerberosServicePrincipal(realm.toString());
-                               if (servicePrincipal != null) {
-                                       CallbackHandler callbackHandler = new CallbackHandler() {
-                                               @Override
-                                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                                                       for (Callback callback : callbacks)
-                                                               if (callback instanceof NameCallback)
-                                                                       ((NameCallback) callback).setName(servicePrincipal);
-
-                                               }
-                                       };
-                                       try {
-                                               LoginContext nodeLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_NODE, callbackHandler);
-                                               nodeLc.login();
-                                               acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal);
-                                       } catch (LoginException e) {
-                                               throw new IllegalStateException("Cannot log in kernel", e);
-                                       }
-                               }
-                       }
-
-                       // Register client-side SPNEGO auth scheme
-                       AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
-                       HttpParams params = DefaultHttpParams.getDefaultParams();
-                       ArrayList<String> schemes = new ArrayList<>();
-                       schemes.add(SpnegoAuthScheme.NAME);// SPNEGO preferred
-                       // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
-                       params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
-                       params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
-                       params.setParameter(HttpMethodParams.COOKIE_POLICY, KernelConstants.COOKIE_POLICY_BROWSER_COMPATIBILITY);
-                       // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
-               }
-       }
-
-       protected void preDestroy(AbstractUserDirectory userDirectory) {
-//             if (tmTracker.getService() instanceof BitronixTransactionManager)
-//                     EhCacheXAResourceProducer.unregisterXAResource(cacheName, userDirectory.getXaResource());
-
-               Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
-               if (realm != null) {
-                       if (acceptorCredentials != null) {
-                               try {
-                                       acceptorCredentials.dispose();
-                               } catch (GSSException e) {
-                                       // silent
-                               }
-                               acceptorCredentials = null;
-                       }
-               }
-       }
-
-       private String getKerberosServicePrincipal(String realm) {
-               String hostname;
-               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
-                       InetAddress localhost = InetAddress.getLocalHost();
-                       hostname = localhost.getHostName();
-                       String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
-                       String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
-                       boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
-                       String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
-                       if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) {
-                               return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
-                       } else
-                               return null;
-               } catch (Exception e) {
-                       log.warn("Exception when determining kerberos principal", e);
-                       return null;
-               }
-       }
-
-       private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) {
-               // GSS
-               Iterator<KerberosPrincipal> krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator();
-               if (!krb5It.hasNext())
-                       return null;
-               KerberosPrincipal krb5Principal = null;
-               while (krb5It.hasNext()) {
-                       KerberosPrincipal principal = krb5It.next();
-                       if (principal.getName().equals(servicePrincipal))
-                               krb5Principal = principal;
-               }
-
-               if (krb5Principal == null)
-                       return null;
-
-               GSSManager manager = GSSManager.getInstance();
-               try {
-                       GSSName gssName = manager.createName(krb5Principal.getName(), null);
-                       GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
-
-                               @Override
-                               public GSSCredential run() throws GSSException {
-                                       return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
-                                                       GSSCredential.ACCEPT_ONLY);
-
-                               }
-
-                       });
-                       if (log.isDebugEnabled())
-                               log.debug("GSS acceptor configured for " + krb5Principal);
-                       return serverCredentials;
-               } catch (Exception gsse) {
-                       throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse);
-               }
-       }
-
-       public GSSCredential getAcceptorCredentials() {
-               return acceptorCredentials;
-       }
-
-       public boolean isSingleUser() {
-               return singleUser;
-       }
-
-       public final static Oid KERBEROS_OID;
-       static {
-               try {
-                       KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
-               } catch (GSSException e) {
-                       throw new IllegalStateException("Cannot create Kerberos OID", e);
-               }
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java
deleted file mode 100644 (file)
index 2105e05..0000000
+++ /dev/null
@@ -1,259 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.math.BigInteger;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.PrivateKey;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.cert.X509CertificateHolder;
-import org.bouncycastle.cert.X509v3CertificateBuilder;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
-import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openssl.PEMParser;
-import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
-import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
-import org.bouncycastle.operator.ContentSigner;
-import org.bouncycastle.operator.InputDecryptorProvider;
-import org.bouncycastle.operator.OperatorCreationException;
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
-import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
-import org.bouncycastle.pkcs.PKCSException;
-
-/**
- * Utilities around private keys and certificate, mostly wrapping BouncyCastle
- * implementations.
- */
-class PkiUtils {
-       final static String PKCS12 = "PKCS12";
-
-       private final static String SECURITY_PROVIDER;
-       static {
-               Security.addProvider(new BouncyCastleProvider());
-               SECURITY_PROVIDER = "BC";
-       }
-
-       public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal,
-                       int keySize, char[] keyPassword) {
-               try {
-                       KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", SECURITY_PROVIDER);
-                       kpGen.initialize(keySize, new SecureRandom());
-                       KeyPair pair = kpGen.generateKeyPair();
-                       Date notBefore = new Date(System.currentTimeMillis() - 10000);
-                       Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000);
-                       BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
-                       X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore,
-                                       notAfter, x500Principal, pair.getPublic());
-                       ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER)
-                                       .build(pair.getPrivate());
-                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
-                                       .getCertificate(certGen.build(sigGen));
-                       cert.checkValidity(new Date());
-                       cert.verify(cert.getPublicKey());
-
-                       keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert });
-                       return cert;
-               } catch (GeneralSecurityException | OperatorCreationException e) {
-                       throw new RuntimeException("Cannot generate self-signed certificate", e);
-               }
-       }
-
-       public static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) {
-               try {
-                       KeyStore store = KeyStore.getInstance(keyStoreType, SECURITY_PROVIDER);
-                       if (Files.exists(keyStoreFile)) {
-                               try (InputStream fis = Files.newInputStream(keyStoreFile)) {
-                                       store.load(fis, keyStorePassword);
-                               }
-                       } else {
-                               store.load(null);
-                       }
-                       return store;
-               } catch (GeneralSecurityException | IOException e) {
-                       throw new RuntimeException("Cannot load keystore " + keyStoreFile, e);
-               }
-       }
-
-       public static void saveKeyStore(Path keyStoreFile, char[] keyStorePassword, KeyStore keyStore) {
-               try {
-                       try (OutputStream fis = Files.newOutputStream(keyStoreFile)) {
-                               keyStore.store(fis, keyStorePassword);
-                       }
-               } catch (GeneralSecurityException | IOException e) {
-                       throw new RuntimeException("Cannot save keystore " + keyStoreFile, e);
-               }
-       }
-
-//     public static byte[] pemToPKCS12(final String keyFile, final String cerFile, final String password)
-//                     throws Exception {
-//             // Get the private key
-//             FileReader reader = new FileReader(keyFile);
-//
-//             PEMReader pem = new PemReader(reader, new PasswordFinder() {
-//                     @Override
-//                     public char[] getPassword() {
-//                             return password.toCharArray();
-//                     }
-//             });
-//
-//             PrivateKey key = ((KeyPair) pem.readObject()).getPrivate();
-//
-//             pem.close();
-//             reader.close();
-//
-//             // Get the certificate
-//             reader = new FileReader(cerFile);
-//             pem = new PEMReader(reader);
-//
-//             X509Certificate cert = (X509Certificate) pem.readObject();
-//
-//             pem.close();
-//             reader.close();
-//
-//             // Put them into a PKCS12 keystore and write it to a byte[]
-//             ByteArrayOutputStream bos = new ByteArrayOutputStream();
-//             KeyStore ks = KeyStore.getInstance("PKCS12");
-//             ks.load(null);
-//             ks.setKeyEntry("alias", (Key) key, password.toCharArray(), new java.security.cert.Certificate[] { cert });
-//             ks.store(bos, password.toCharArray());
-//             bos.close();
-//             return bos.toByteArray();
-//     }
-
-       public static void loadPem(KeyStore keyStore, Reader key, char[] keyPassword, Reader cert) {
-               PrivateKey privateKey = loadPemPrivateKey(key, keyPassword);
-               X509Certificate certificate = loadPemCertificate(cert);
-               try {
-                       keyStore.setKeyEntry(certificate.getSubjectX500Principal().getName(), privateKey, keyPassword,
-                                       new java.security.cert.Certificate[] { certificate });
-               } catch (KeyStoreException e) {
-                       throw new RuntimeException("Cannot store PEM certificate", e);
-               }
-       }
-
-       public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) {
-               try (PEMParser pemParser = new PEMParser(reader)) {
-                       JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
-                       Object object = pemParser.readObject();
-                       PrivateKeyInfo privateKeyInfo;
-                       if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
-                               if (keyPassword == null)
-                                       throw new IllegalArgumentException("A key password is required");
-                               InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword);
-                               privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv);
-                       } else if (object instanceof PrivateKeyInfo) {
-                               privateKeyInfo = (PrivateKeyInfo) object;
-                       } else {
-                               throw new IllegalArgumentException("Unsupported format for private key");
-                       }
-                       return converter.getPrivateKey(privateKeyInfo);
-               } catch (IOException | OperatorCreationException | PKCSException e) {
-                       throw new RuntimeException("Cannot read private key", e);
-               }
-       }
-
-       public static X509Certificate loadPemCertificate(Reader reader) {
-               try (PEMParser pemParser = new PEMParser(reader)) {
-                       X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject();
-                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
-                                       .getCertificate(certHolder);
-                       return cert;
-               } catch (IOException | CertificateException e) {
-                       throw new RuntimeException("Cannot read private key", e);
-               }
-       }
-
-       public static void main(String[] args) throws Exception {
-               final String ALGORITHM = "RSA";
-               final String provider = "BC";
-               SecureRandom secureRandom = new SecureRandom();
-               long begin = System.currentTimeMillis();
-               for (int i = 512; i < 1024; i = i + 2) {
-                       try {
-                               KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, provider);
-                               keyGen.initialize(i, secureRandom);
-                               keyGen.generateKeyPair();
-                       } catch (Exception e) {
-                               System.err.println(i + " : " + e.getMessage());
-                       }
-               }
-               System.out.println((System.currentTimeMillis() - begin) + " ms");
-
-               // // String text = "a";
-               // String text =
-               // "testtesttesttesttesttesttesttesttesttesttesttesttesttesttest";
-               // try {
-               // System.out.println(text);
-               // PrivateKey privateKey;
-               // PublicKey publicKey;
-               // char[] password = "changeit".toCharArray();
-               // String alias = "CN=test";
-               // KeyStore keyStore = KeyStore.getInstance("pkcs12");
-               // File p12file = new File("test.p12");
-               // p12file.delete();
-               // if (!p12file.exists()) {
-               // keyStore.load(null);
-               // generateSelfSignedCertificate(keyStore, new X500Principal(alias),
-               // 513, password);
-               // try (OutputStream out = new FileOutputStream(p12file)) {
-               // keyStore.store(out, password);
-               // }
-               // }
-               // try (InputStream in = new FileInputStream(p12file)) {
-               // keyStore.load(in, password);
-               // privateKey = (PrivateKey) keyStore.getKey(alias, password);
-               // publicKey = keyStore.getCertificateChain(alias)[0].getPublicKey();
-               // }
-               // // KeyPair key;
-               // // final KeyPairGenerator keyGen =
-               // // KeyPairGenerator.getInstance(ALGORITHM);
-               // // keyGen.initialize(4096, new SecureRandom());
-               // // long begin = System.currentTimeMillis();
-               // // key = keyGen.generateKeyPair();
-               // // System.out.println((System.currentTimeMillis() - begin) + " ms");
-               // // keyStore.load(null);
-               // // keyStore.setKeyEntry("test", key.getPrivate(), password, null);
-               // // try(OutputStream out=new FileOutputStream(p12file)) {
-               // // keyStore.store(out, password);
-               // // }
-               // // privateKey = key.getPrivate();
-               // // publicKey = key.getPublic();
-               //
-               // Cipher encrypt = Cipher.getInstance(ALGORITHM);
-               // encrypt.init(Cipher.ENCRYPT_MODE, publicKey);
-               // byte[] encrypted = encrypt.doFinal(text.getBytes());
-               // String encryptedBase64 =
-               // Base64.getEncoder().encodeToString(encrypted);
-               // System.out.println(encryptedBase64);
-               // byte[] encryptedFromBase64 =
-               // Base64.getDecoder().decode(encryptedBase64);
-               //
-               // Cipher decrypt = Cipher.getInstance(ALGORITHM);
-               // decrypt.init(Cipher.DECRYPT_MODE, privateKey);
-               // byte[] decrypted = decrypt.doFinal(encryptedFromBase64);
-               // System.out.println(new String(decrypted));
-               // } catch (Exception e) {
-               // e.printStackTrace();
-               // }
-
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/SecurityProfile.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/SecurityProfile.java
deleted file mode 100644 (file)
index 34ec9bc..0000000
+++ /dev/null
@@ -1,324 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.FilePermission;
-import java.lang.reflect.ReflectPermission;
-import java.net.SocketPermission;
-import java.security.AllPermission;
-import java.util.PropertyPermission;
-
-import javax.security.auth.AuthPermission;
-
-import org.osgi.framework.AdminPermission;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServicePermission;
-import org.osgi.service.cm.ConfigurationPermission;
-import org.osgi.service.condpermadmin.BundleLocationCondition;
-import org.osgi.service.condpermadmin.ConditionInfo;
-import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
-import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
-import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
-import org.osgi.service.permissionadmin.PermissionAdmin;
-import org.osgi.service.permissionadmin.PermissionInfo;
-
-/** Security profile based on OSGi {@link PermissionAdmin}. */
-public interface SecurityProfile {
-       BundleContext bc = FrameworkUtil.getBundle(SecurityProfile.class).getBundleContext();
-
-       default void applySystemPermissions(ConditionalPermissionAdmin permissionAdmin) {
-               ConditionalPermissionUpdate update = permissionAdmin.newConditionalPermissionUpdate();
-               // Self
-//             String nodeAPiBundleLocation = locate(NodeUtils.class);
-//             update.getConditionalPermissionInfos()
-//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
-//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                                             new String[] { nodeAPiBundleLocation }) },
-//                                             new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
-//                                             ConditionalPermissionInfo.ALLOW));
-               String cmsBundleLocation = locate(SecurityProfile.class);
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { cmsBundleLocation }) },
-                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
-                                               ConditionalPermissionInfo.ALLOW));
-               String frameworkBundleLocation = bc.getBundle(0).getLocation();
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { frameworkBundleLocation }) },
-                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
-                                               ConditionalPermissionInfo.ALLOW));
-               // All
-               // FIXME understand why Jetty and Jackrabbit require that
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null, null, new PermissionInfo[] {
-                                               new PermissionInfo(SocketPermission.class.getName(), "localhost:7070", "listen,resolve"),
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "DEBUG", "read"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "STOP.*", "read"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "org.apache.jackrabbit.*", "read"),
-                                               new PermissionInfo(RuntimePermission.class.getName(), "*", "*"), },
-                                               ConditionalPermissionInfo.ALLOW));
-
-               // Eclipse
-               // update.getConditionalPermissionInfos()
-               // .add(permissionAdmin.newConditionalPermissionInfo(null,
-               // new ConditionInfo[] { new
-               // ConditionInfo(BundleLocationCondition.class.getName(),
-               // new String[] { "*/org.eclipse.*" }) },
-               // new PermissionInfo[] { new
-               // PermissionInfo(RuntimePermission.class.getName(), "*", "*"),
-               // new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
-               // new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
-               // new PermissionInfo(ServicePermission.class.getName(), "*",
-               // "register"),
-               // new PermissionInfo(TopicPermission.class.getName(), "*", "publish"),
-               // new PermissionInfo(TopicPermission.class.getName(), "*",
-               // "subscribe"),
-               // new PermissionInfo(PropertyPermission.class.getName(), "osgi.*",
-               // "read"),
-               // new PermissionInfo(PropertyPermission.class.getName(), "eclipse.*",
-               // "read"),
-               // new PermissionInfo(PropertyPermission.class.getName(),
-               // "org.eclipse.*", "read"),
-               // new PermissionInfo(PropertyPermission.class.getName(), "equinox.*",
-               // "read"),
-               // new PermissionInfo(PropertyPermission.class.getName(), "xml.*",
-               // "read"),
-               // new PermissionInfo("org.eclipse.equinox.log.LogPermission", "*",
-               // "log"), },
-               // ConditionalPermissionInfo.ALLOW));
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { "*/org.eclipse.*" }) },
-                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null), },
-                                               ConditionalPermissionInfo.ALLOW));
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { "*/org.apache.felix.*" }) },
-                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null), },
-                                               ConditionalPermissionInfo.ALLOW));
-
-               // Configuration admin
-//             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-//                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                             new String[] { locate(configurationAdmin.getService().getClass()) }) },
-//                             new PermissionInfo[] { new PermissionInfo(ConfigurationPermission.class.getName(), "*", "configure"),
-//                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
-//                                             new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"), },
-//                             ConditionalPermissionInfo.ALLOW));
-
-               // Bitronix
-//             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-//                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                             new String[] { locate(BitronixTransactionManager.class) }) },
-//                             new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "bitronix.tm.*", "read"),
-//                                             new PermissionInfo(RuntimePermission.class.getName(), "getClassLoader", null),
-//                                             new PermissionInfo(MBeanServerPermission.class.getName(), "createMBeanServer", null),
-//                                             new PermissionInfo(MBeanPermission.class.getName(), "bitronix.tm.*", "registerMBean"),
-//                                             new PermissionInfo(MBeanTrustPermission.class.getName(), "register", null) },
-//                             ConditionalPermissionInfo.ALLOW));
-
-               // DS
-               Bundle dsBundle = findBundle("org.eclipse.equinox.ds");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { dsBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(ConfigurationPermission.class.getName(), "*", "configure"),
-                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
-                                               new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
-                                               new PermissionInfo(ServicePermission.class.getName(), "*", "register"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "xml.*", "read"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "equinox.*", "read"),
-                                               new PermissionInfo(RuntimePermission.class.getName(), "accessDeclaredMembers", null),
-                                               new PermissionInfo(RuntimePermission.class.getName(), "getClassLoader", null),
-                                               new PermissionInfo(ReflectPermission.class.getName(), "suppressAccessChecks", null), },
-                               ConditionalPermissionInfo.ALLOW));
-
-               // Jetty
-               // Bundle jettyUtilBundle = findBundle("org.eclipse.equinox.http.jetty");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { "*/org.eclipse.jetty.*" }) },
-                               new PermissionInfo[] {
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
-                               ConditionalPermissionInfo.ALLOW));
-               Bundle servletBundle = findBundle("javax.servlet");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { servletBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(),
-                                               "org.glassfish.web.rfc2109_cookie_names_enforced", "read") },
-                               ConditionalPermissionInfo.ALLOW));
-
-               // required to be able to get the BundleContext in the customizer
-               Bundle jettyCustomizerBundle = findBundle("org.argeo.ext.equinox.jetty");
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { jettyCustomizerBundle.getLocation() }) },
-                                               new PermissionInfo[] { new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
-                                               ConditionalPermissionInfo.ALLOW));
-
-               // Blueprint
-//             Bundle blueprintBundle = findBundle("org.eclipse.gemini.blueprint.core");
-//             update.getConditionalPermissionInfos()
-//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
-//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                                             new String[] { blueprintBundle.getLocation() }) },
-//                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
-//                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
-//                                             ConditionalPermissionInfo.ALLOW));
-//             Bundle blueprintExtenderBundle = findBundle("org.eclipse.gemini.blueprint.extender");
-//             update.getConditionalPermissionInfos()
-//                             .add(permissionAdmin
-//                                             .newConditionalPermissionInfo(null,
-//                                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                                                             new String[] { blueprintExtenderBundle.getLocation() }) },
-//                                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
-//                                                                             new PermissionInfo(PropertyPermission.class.getName(), "org.eclipse.gemini.*",
-//                                                                                             "read"),
-//                                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
-//                                                                             new PermissionInfo(ServicePermission.class.getName(), "*", "register"), },
-//                                                             ConditionalPermissionInfo.ALLOW));
-//             Bundle springCoreBundle = findBundle("org.springframework.core");
-//             update.getConditionalPermissionInfos()
-//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
-//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                                             new String[] { springCoreBundle.getLocation() }) },
-//                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
-//                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
-//                                             ConditionalPermissionInfo.ALLOW));
-//             Bundle blueprintIoBundle = findBundle("org.eclipse.gemini.blueprint.io");
-//             update.getConditionalPermissionInfos()
-//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
-//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                                             new String[] { blueprintIoBundle.getLocation() }) },
-//                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
-//                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
-//                                             ConditionalPermissionInfo.ALLOW));
-
-               // Equinox
-               Bundle registryBundle = findBundle("org.eclipse.equinox.registry");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { registryBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "eclipse.*", "read"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"),
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
-                               ConditionalPermissionInfo.ALLOW));
-
-               Bundle equinoxUtilBundle = findBundle("org.eclipse.equinox.util");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { equinoxUtilBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "equinox.*", "read"),
-                                               new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
-                                               new PermissionInfo(ServicePermission.class.getName(), "*", "register"), },
-                               ConditionalPermissionInfo.ALLOW));
-               Bundle equinoxCommonBundle = findBundle("org.eclipse.equinox.common");
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { equinoxCommonBundle.getLocation() }) },
-                                               new PermissionInfo[] { new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
-                                               ConditionalPermissionInfo.ALLOW));
-
-               Bundle consoleBundle = findBundle("org.eclipse.equinox.console");
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { consoleBundle.getLocation() }) },
-                                               new PermissionInfo[] { new PermissionInfo(ServicePermission.class.getName(), "*", "register"),
-                                                               new PermissionInfo(AdminPermission.class.getName(), "*", "listener") },
-                                               ConditionalPermissionInfo.ALLOW));
-               Bundle preferencesBundle = findBundle("org.eclipse.equinox.preferences");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { preferencesBundle.getLocation() }) },
-                               new PermissionInfo[] {
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
-                               ConditionalPermissionInfo.ALLOW));
-               Bundle appBundle = findBundle("org.eclipse.equinox.app");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { appBundle.getLocation() }) },
-                               new PermissionInfo[] {
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
-                               ConditionalPermissionInfo.ALLOW));
-
-               // Jackrabbit
-               Bundle jackrabbitCoreBundle = findBundle("org.apache.jackrabbit.core");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { jackrabbitCoreBundle.getLocation() }) },
-                               new PermissionInfo[] {
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "*", "read,write"),
-                                               new PermissionInfo(AuthPermission.class.getName(), "getSubject", null),
-                                               new PermissionInfo(AuthPermission.class.getName(), "getLoginConfiguration", null),
-                                               new PermissionInfo(AuthPermission.class.getName(), "createLoginContext.Jackrabbit", null), },
-                               ConditionalPermissionInfo.ALLOW));
-               Bundle jackrabbitDataBundle = findBundle("org.apache.jackrabbit.data");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { jackrabbitDataBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "*", "read,write") },
-                               ConditionalPermissionInfo.ALLOW));
-               Bundle jackrabbitCommonBundle = findBundle("org.apache.jackrabbit.jcr.commons");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { jackrabbitCommonBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(AuthPermission.class.getName(), "getSubject", null),
-                                               new PermissionInfo(AuthPermission.class.getName(), "createLoginContext.Jackrabbit", null), },
-                               ConditionalPermissionInfo.ALLOW));
-
-               Bundle jackrabbitExtBundle = findBundle("org.argeo.ext.jackrabbit");
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { jackrabbitExtBundle.getLocation() }) },
-                                               new PermissionInfo[] { new PermissionInfo(AuthPermission.class.getName(), "*", "*"), },
-                                               ConditionalPermissionInfo.ALLOW));
-
-               // Tika
-               Bundle tikaCoreBundle = findBundle("org.apache.tika.core");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { tikaCoreBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "*", "read,write"),
-                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*") },
-                               ConditionalPermissionInfo.ALLOW));
-               Bundle luceneBundle = findBundle("org.apache.lucene");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { luceneBundle.getLocation() }) },
-                               new PermissionInfo[] {
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "*", "read"),
-                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*") },
-                               ConditionalPermissionInfo.ALLOW));
-
-               // COMMIT
-               update.commit();
-       }
-
-       /** @return bundle location */
-       default String locate(Class<?> clzz) {
-               return FrameworkUtil.getBundle(clzz).getLocation();
-       }
-
-       /** Can be null */
-       default Bundle findBundle(String symbolicName) {
-               for (Bundle b : bc.getBundles())
-                       if (b.getSymbolicName().equals(symbolicName))
-                               return b;
-               return null;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/dc=example,dc=com.ldif b/org.argeo.cms/src/org/argeo/cms/internal/kernel/dc=example,dc=com.ldif
deleted file mode 100644 (file)
index 43e7ade..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-dn: dc=example,dc=com
-objectClass: domain
-objectClass: extensibleObject
-objectClass: top
-dc: example
-
-dn: ou=Groups,dc=example,dc=com
-objectClass: organizationalUnit
-objectClass: top
-ou: Groups
-
-dn: ou=People,dc=example,dc=com
-objectClass: organizationalUnit
-objectClass: top
-ou: People
-
-dn: uid=demo,ou=People,dc=example,dc=com
-objectClass: inetOrgPerson
-objectClass: organizationalPerson
-objectClass: person
-objectClass: top
-cn: Demo User
-description: Demo user
-givenName: Demo
-mail: demo@localhost
-sn: User
-uid: demo
-userPassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
-
-dn: uid=root,ou=People,dc=example,dc=com
-objectClass: inetOrgPerson
-objectClass: person
-objectClass: organizationalPerson
-objectClass: top
-cn: Super User
-description: Superuser
-givenName: Super
-mail: root@localhost
-sn: User
-uid: root
-userPassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/example-ou=roles,ou=node.ldif b/org.argeo.cms/src/org/argeo/cms/internal/kernel/example-ou=roles,ou=node.ldif
deleted file mode 100644 (file)
index ffa9073..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-dn: cn=admin,ou=roles,ou=node
-objectClass: groupOfNames
-objectClass: top
-cn: admin
-member: uid=root,ou=People,dc=example,dc=com
-
-dn: cn=userAdmin,ou=roles,ou=node
-objectClass: groupOfNames
-objectClass: top
-member: cn=admin,ou=roles,ou=node
-cn: userAdmin
-
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas-ipa.cfg b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas-ipa.cfg
deleted file mode 100644 (file)
index c7c804c..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-USER {
-    org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
-    org.argeo.cms.auth.SpnegoLoginModule optional;
-    com.sun.security.auth.module.Krb5LoginModule optional tryFirstPass=true;
-    org.argeo.cms.auth.UserAdminLoginModule sufficient;
-};
-
-ANONYMOUS {
-    org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
-    org.argeo.cms.auth.AnonymousLoginModule sufficient;
-};
-
-DATA_ADMIN {
-    org.argeo.cms.auth.DataAdminLoginModule requisite;
-};
-
-NODE {
-    com.sun.security.auth.module.Krb5LoginModule optional
-     keyTab="${osgi.instance.area}node/krb5.keytab" 
-     useKeyTab=true
-     storeKey=true;
-    org.argeo.cms.auth.DataAdminLoginModule requisite;
-};
-
-KEYRING {
-    org.argeo.cms.auth.KeyringLoginModule required;
-};
-
-SINGLE_USER {
-    com.sun.security.auth.module.Krb5LoginModule optional
-     principal="${user.name}"
-     storeKey=true
-     useTicketCache=true
-     debug=true;
-    org.argeo.cms.auth.SingleUserLoginModule requisite;
-};
-
-Jackrabbit {
-   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
-};
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg
deleted file mode 100644 (file)
index 364977d..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-USER {
-    org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
-    org.argeo.cms.auth.IdentLoginModule optional;
-    org.argeo.cms.auth.UserAdminLoginModule requisite;
-};
-
-ANONYMOUS {
-    org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
-    org.argeo.cms.auth.AnonymousLoginModule requisite;
-};
-
-DATA_ADMIN {
-    org.argeo.cms.auth.DataAdminLoginModule requisite;
-};
-
-NODE {
-    org.argeo.cms.auth.DataAdminLoginModule requisite;
-};
-
-KEYRING {
-    org.argeo.cms.auth.KeyringLoginModule required;
-};
-
-SINGLE_USER {
-    org.argeo.cms.auth.SingleUserLoginModule requisite;
-};
-
-Jackrabbit {
-   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
-};
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=roles,ou=node.ldif b/org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=roles,ou=node.ldif
deleted file mode 100644 (file)
index 85247ed..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-dn: ou=node
-objectClass: organizationalUnit
-objectClass: top
-ou: node
-
-dn: ou=roles,ou=node
-objectClass: organizationalUnit
-objectClass: top
-ou: roles
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=tokens,ou=node.ldif b/org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=tokens,ou=node.ldif
deleted file mode 100644 (file)
index 4ae9b88..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-dn: ou=tokens,ou=node
-objectClass: organizationalUnit
-objectClass: top
-ou: tokens
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java
new file mode 100644 (file)
index 0000000..d28ffdb
--- /dev/null
@@ -0,0 +1,269 @@
+package org.argeo.cms.internal.osgi;
+
+import java.security.AllPermission;
+import java.util.Dictionary;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ArgeoLogger;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.condpermadmin.BundleLocationCondition;
+import org.osgi.service.condpermadmin.ConditionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.log.LogReaderService;
+import org.osgi.service.permissionadmin.PermissionInfo;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Activates the kernel. Gives access to kernel information for the rest of the
+ * bundle (and only it)
+ */
+public class CmsActivator implements BundleActivator {
+       private final static CmsLog log = CmsLog.getLog(CmsActivator.class);
+
+//     private static Activator instance;
+
+       // TODO make it configurable
+       private boolean hardened = false;
+
+       private static BundleContext bundleContext;
+
+       private LogReaderService logReaderService;
+
+       private NodeLogger logger;
+//     private CmsStateImpl nodeState;
+//     private CmsDeploymentImpl nodeDeployment;
+//     private CmsContextImpl nodeInstance;
+
+//     private ServiceTracker<UserAdmin, NodeUserAdmin> userAdminSt;
+
+//     static {
+//             Bundle bundle = FrameworkUtil.getBundle(Activator.class);
+//             if (bundle != null) {
+//                     bundleContext = bundle.getBundleContext();
+//             }
+//     }
+
+       void init() {
+//             Runtime.getRuntime().addShutdownHook(new CmsShutdown());
+//             instance = this;
+//             this.bc = bundleContext;
+               if (bundleContext != null)
+                       this.logReaderService = getService(LogReaderService.class);
+//             this.internalExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+//
+//             try {
+//                     initSecurity();
+////                   initArgeoLogger();
+//                     initNode();
+//
+//                     if (log.isTraceEnabled())
+//                             log.trace("Kernel bundle started");
+//             } catch (Throwable e) {
+//                     log.error("## FATAL: CMS activator failed", e);
+//             }
+       }
+
+       void destroy() {
+               try {
+//                     if (nodeInstance != null)
+//                             nodeInstance.shutdown();
+//                     if (nodeDeployment != null)
+//                             nodeDeployment.shutdown();
+//                     if (nodeState != null)
+//                             nodeState.shutdown();
+//
+//                     if (userAdminSt != null)
+//                             userAdminSt.close();
+
+//                     internalExecutorService.shutdown();
+//                     instance = null;
+                       bundleContext = null;
+                       this.logReaderService = null;
+                       // this.configurationAdmin = null;
+               } catch (Exception e) {
+                       log.error("CMS activator shutdown failed", e);
+               }
+       }
+
+       private void initSecurity() {
+               // code-level permissions
+               String osgiSecurity = bundleContext.getProperty(Constants.FRAMEWORK_SECURITY);
+               if (osgiSecurity != null && Constants.FRAMEWORK_SECURITY_OSGI.equals(osgiSecurity)) {
+                       // TODO rather use a tracker?
+                       ConditionalPermissionAdmin permissionAdmin = bundleContext
+                                       .getService(bundleContext.getServiceReference(ConditionalPermissionAdmin.class));
+                       if (!hardened) {
+                               // All permissions to all bundles
+                               ConditionalPermissionUpdate update = permissionAdmin.newConditionalPermissionUpdate();
+                               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] {
+                                                               new ConditionInfo(BundleLocationCondition.class.getName(), new String[] { "*" }) },
+                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
+                                               ConditionalPermissionInfo.ALLOW));
+                               // TODO data admin permission
+//                             PermissionInfo dataAdminPerm = new PermissionInfo(AuthPermission.class.getName(),
+//                                             "createLoginContext." + NodeConstants.LOGIN_CONTEXT_DATA_ADMIN, null);
+//                             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+//                                             new ConditionInfo[] {
+//                                                             new ConditionInfo(BundleLocationCondition.class.getName(), new String[] { "*" }) },
+//                                             new PermissionInfo[] { dataAdminPerm }, ConditionalPermissionInfo.DENY));
+//                             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+//                                             new ConditionInfo[] {
+//                                                             new ConditionInfo(BundleSignerCondition.class.getName(), new String[] { "CN=\"Eclipse.org Foundation, Inc.\", OU=IT, O=\"Eclipse.org Foundation, Inc.\", L=Nepean, ST=Ontario, C=CA" }) },
+//                                             new PermissionInfo[] { dataAdminPerm }, ConditionalPermissionInfo.ALLOW));
+                               update.commit();
+                       } else {
+                               SecurityProfile securityProfile = new SecurityProfile() {
+                               };
+                               securityProfile.applySystemPermissions(permissionAdmin);
+                       }
+               }
+
+       }
+
+       private void initArgeoLogger() {
+               logger = new NodeLogger(logReaderService);
+               if (bundleContext != null)
+                       bundleContext.registerService(ArgeoLogger.class, logger, null);
+       }
+
+//     private void initNode() throws IOException {
+//             // Node state
+//             nodeState = new CmsStateImpl();
+//             registerService(CmsState.class, nodeState, null);
+//
+//             // Node deployment
+//             nodeDeployment = new CmsDeploymentImpl();
+////           registerService(NodeDeployment.class, nodeDeployment, null);
+//
+//             // Node instance
+//             nodeInstance = new CmsContextImpl();
+//             registerService(CmsContext.class, nodeInstance, null);
+//     }
+
+       public static <T> void registerService(Class<T> clss, T service, Dictionary<String, ?> properties) {
+               if (bundleContext != null) {
+                       bundleContext.registerService(clss, service, properties);
+               }
+
+       }
+
+       public static <T> T getService(Class<T> clss) {
+               if (bundleContext != null) {
+                       return bundleContext.getService(bundleContext.getServiceReference(clss));
+               } else {
+                       return null;
+               }
+       }
+
+       /*
+        * OSGi
+        */
+
+       @Override
+       public void start(BundleContext bc) throws Exception {
+               bundleContext = bc;
+//             if (!bc.getBundle().equals(bundleContext.getBundle()))
+//                     throw new IllegalStateException(
+//                                     "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle());
+               init();
+//             userAdminSt = new ServiceTracker<>(bundleContext, UserAdmin.class, null);
+//             userAdminSt.open();
+
+               ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
+
+                       @Override
+                       public HttpService addingService(ServiceReference<HttpService> sr) {
+                               Object httpPort = sr.getProperty("http.port");
+                               Object httpsPort = sr.getProperty("https.port");
+                               log.info(httpPortsMsg(httpPort, httpsPort));
+                               close();
+                               return super.addingService(sr);
+                       }
+               };
+               httpSt.open();
+       }
+
+       private String httpPortsMsg(Object httpPort, Object httpsPort) {
+               return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
+       }
+
+       @Override
+       public void stop(BundleContext bc) throws Exception {
+//             if (!bc.getBundle().equals(bundleContext.getBundle()))
+//                     throw new IllegalStateException(
+//                                     "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle());
+               destroy();
+               bundleContext = null;
+       }
+
+//     private <T> T getService(Class<T> clazz) {
+//             ServiceReference<T> sr = bundleContext.getServiceReference(clazz);
+//             if (sr == null)
+//                     throw new IllegalStateException("No service available for " + clazz);
+//             return bundleContext.getService(sr);
+//     }
+
+//     public static GSSCredential getAcceptorCredentials() {
+//             return getNodeUserAdmin().getAcceptorCredentials();
+//     }
+//
+//     @Deprecated
+//     public static boolean isSingleUser() {
+//             return getNodeUserAdmin().isSingleUser();
+//     }
+//
+//     public static UserAdmin getUserAdmin() {
+//             return (UserAdmin) getNodeUserAdmin();
+//     }
+//
+//     public static String getHttpProxySslHeader() {
+//             return KernelUtils.getFrameworkProp(CmsConstants.HTTP_PROXY_SSL_DN);
+//     }
+//
+//     private static NodeUserAdmin getNodeUserAdmin() {
+//             NodeUserAdmin res;
+//             try {
+//                     res = instance.userAdminSt.waitForService(60000);
+//             } catch (InterruptedException e) {
+//                     throw new IllegalStateException("Cannot retrieve Node user admin", e);
+//             }
+//             if (res == null)
+//                     throw new IllegalStateException("No Node user admin found");
+//
+//             return res;
+//             // ServiceReference<UserAdmin> sr =
+//             // instance.bc.getServiceReference(UserAdmin.class);
+//             // NodeUserAdmin userAdmin = (NodeUserAdmin) instance.bc.getService(sr);
+//             // return userAdmin;
+//
+//     }
+
+//     public static ExecutorService getInternalExecutorService() {
+//             return instance.internalExecutorService;
+//     }
+
+       // static CmsSecurity getCmsSecurity() {
+       // return instance.nodeSecurity;
+       // }
+
+//     public String[] getLocales() {
+//             // TODO optimize?
+//             List<Locale> locales = CmsStateImpl.getNodeState().getLocales();
+//             String[] res = new String[locales.size()];
+//             for (int i = 0; i < locales.size(); i++)
+//                     res[i] = locales.get(i).toString();
+//             return res;
+//     }
+
+       public static BundleContext getBundleContext() {
+               return bundleContext;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsShutdown.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsShutdown.java
new file mode 100644 (file)
index 0000000..324462d
--- /dev/null
@@ -0,0 +1,70 @@
+package org.argeo.cms.internal.osgi;
+
+import org.argeo.api.cms.CmsLog;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.launch.Framework;
+
+/** Shutdowns the OSGi framework */
+public class CmsShutdown extends Thread {
+       public final int EXIT_OK = 0;
+       public final int EXIT_ERROR = 1;
+       public final int EXIT_TIMEOUT = 2;
+       public final int EXIT_UNKNOWN = 3;
+
+       private final CmsLog log = CmsLog.getLog(CmsShutdown.class);
+       // private final BundleContext bc =
+       // FrameworkUtil.getBundle(CmsShutdown.class).getBundleContext();
+       private final Framework framework;
+
+       /** Shutdown timeout in ms */
+       private long timeout = 10 * 60 * 1000;
+
+       public CmsShutdown() {
+               super("CMS Shutdown Hook");
+               framework = FrameworkUtil.getBundle(CmsShutdown.class) != null
+                               ? (Framework) FrameworkUtil.getBundle(CmsShutdown.class).getBundleContext().getBundle(0)
+                               : null;
+       }
+
+       @Override
+       public void run() {
+               if (framework != null && framework.getState() != Bundle.ACTIVE) {
+                       return;
+               }
+
+               if (log.isDebugEnabled())
+                       log.debug("Shutting down OSGi framework...");
+               try {
+                       if (framework != null) {
+                               // shutdown framework
+                               framework.stop();
+                               // wait for shutdown
+                               FrameworkEvent shutdownEvent = framework.waitForStop(timeout);
+                               int stoppedType = shutdownEvent.getType();
+                               Runtime runtime = Runtime.getRuntime();
+                               if (stoppedType == FrameworkEvent.STOPPED) {
+                                       // close VM
+                                       // System.exit(EXIT_OK);
+                               } else if (stoppedType == FrameworkEvent.ERROR) {
+                                       log.error("The OSGi framework stopped with an error");
+                                       runtime.halt(EXIT_ERROR);
+                               } else if (stoppedType == FrameworkEvent.WAIT_TIMEDOUT) {
+                                       log.error("The OSGi framework hasn't stopped after " + timeout + "ms."
+                                                       + " Forcibly terminating the JVM...");
+                                       runtime.halt(EXIT_TIMEOUT);
+                               } else {
+                                       log.error("Unknown state of OSGi framework after " + timeout + "ms."
+                                                       + " Forcibly terminating the JVM... (" + shutdownEvent + ")");
+                                       runtime.halt(EXIT_UNKNOWN);
+                               }
+                       }
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       log.error("Unexpected exception " + e + " in shutdown hook. " + " Forcibly terminating the JVM...");
+                       Runtime.getRuntime().halt(EXIT_UNKNOWN);
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java
new file mode 100644 (file)
index 0000000..c31f50d
--- /dev/null
@@ -0,0 +1,415 @@
+package org.argeo.cms.internal.osgi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.naming.InvalidNameException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.runtime.InitUtils;
+import org.argeo.cms.internal.runtime.KernelConstants;
+import org.argeo.cms.internal.runtime.KernelUtils;
+import org.argeo.osgi.useradmin.UserAdminConf;
+import org.argeo.util.naming.AttributesDictionary;
+import org.argeo.util.naming.LdifParser;
+import org.argeo.util.naming.LdifWriter;
+import org.eclipse.equinox.http.jetty.JettyConfigurator;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationEvent;
+import org.osgi.service.cm.ConfigurationListener;
+
+/** Manages the LDIF-based deployment configuration. */
+public class DeployConfig implements ConfigurationListener {
+       private final CmsLog log = CmsLog.getLog(getClass());
+//     private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+       private static Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH);
+       private SortedMap<LdapName, Attributes> deployConfigs = new TreeMap<>();
+//     private final DataModels dataModels;
+
+       private boolean isFirstInit = false;
+
+       private final static String ROLES = "roles";
+
+       private ConfigurationAdmin configurationAdmin;
+
+       public DeployConfig() {
+//             this.dataModels = dataModels;
+               // ConfigurationAdmin configurationAdmin =
+//             // bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
+//             try {
+//                     if (!isInitialized()) { // first init
+//                             isFirstInit = true;
+//                             firstInit();
+//                     }
+//                     this.configurationAdmin = configurationAdmin;
+////                   init(configurationAdmin, isClean, isFirstInit);
+//             } catch (IOException e) {
+//                     throw new RuntimeException("Could not init deploy configs", e);
+//             }
+               // FIXME check race conditions during initialization
+               // bc.registerService(ConfigurationListener.class, this, null);
+       }
+
+       private void firstInit() throws IOException {
+               log.info("## FIRST INIT ##");
+               Files.createDirectories(deployConfigPath.getParent());
+
+               // FirstInit firstInit = new FirstInit();
+               InitUtils.prepareFirstInitInstanceArea();
+
+               if (!Files.exists(deployConfigPath))
+                       deployConfigs = new TreeMap<>();
+               else// config file could have juste been copied by preparation
+                       try (InputStream in = Files.newInputStream(deployConfigPath)) {
+                               deployConfigs = new LdifParser().read(in);
+                       }
+               save();
+       }
+
+       private void setFromFrameworkProperties(boolean isFirstInit) {
+
+               // user admin
+               List<Dictionary<String, Object>> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs();
+               if (userDirectoryConfigs.size() != 0) {
+                       List<String> activeCns = new ArrayList<>();
+                       for (int i = 0; i < userDirectoryConfigs.size(); i++) {
+                               Dictionary<String, Object> userDirectoryConfig = userDirectoryConfigs.get(i);
+                               String baseDn = (String) userDirectoryConfig.get(UserAdminConf.baseDn.name());
+                               String cn;
+                               if (CmsConstants.ROLES_BASEDN.equals(baseDn))
+                                       cn = ROLES;
+                               else
+                                       cn = UserAdminConf.baseDnHash(userDirectoryConfig);
+                               activeCns.add(cn);
+                               userDirectoryConfig.put(CmsConstants.CN, cn);
+                               putFactoryDeployConfig(CmsConstants.NODE_USER_ADMIN_PID, userDirectoryConfig);
+                       }
+                       // disable others
+                       LdapName userAdminFactoryName = serviceFactoryDn(CmsConstants.NODE_USER_ADMIN_PID);
+                       for (LdapName name : deployConfigs.keySet()) {
+                               if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) {
+//                                     try {
+                                       Attributes attrs = deployConfigs.get(name);
+                                       String cn = name.getRdn(name.size() - 1).getValue().toString();
+                                       if (!activeCns.contains(cn)) {
+                                               attrs.put(UserAdminConf.disabled.name(), "true");
+                                       }
+//                                     } catch (Exception e) {
+//                                             throw new CmsException("Cannot disable user directory " + name, e);
+//                                     }
+                               }
+                       }
+               }
+
+               // http server
+//             Dictionary<String, Object> webServerConfig = InitUtils
+//                             .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT));
+//             if (!webServerConfig.isEmpty()) {
+//                     // TODO check for other customizers
+//                     webServerConfig.put("customizer.class", "org.argeo.equinox.jetty.CmsJettyCustomizer");
+//                     putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig);
+//             }
+               LdapName defaultHttpServiceDn = serviceDn(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT);
+               if (deployConfigs.containsKey(defaultHttpServiceDn)) {
+                       // remove old default configs since we have now to start Jetty servlet bridge
+                       // indirectly
+                       deployConfigs.remove(defaultHttpServiceDn);
+               }
+
+               // SAVE
+               save();
+               //
+
+               // Explicitly configures Jetty so that the default server is not started by the
+               // activator of the Equinox Jetty bundle.
+               Dictionary<String, Object> webServerConfig = InitUtils
+                               .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT));
+//             if (!webServerConfig.isEmpty()) {
+//                     webServerConfig.put("customizer.class", KernelConstants.CMS_JETTY_CUSTOMIZER_CLASS);
+//
+//                     // TODO centralise with Jetty extender
+//                     Object webSocketEnabled = webServerConfig.get(InternalHttpConstants.WEBSOCKET_ENABLED);
+//                     if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
+//                             bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
+//                             webServerConfig.put(InternalHttpConstants.WEBSOCKET_ENABLED, "true");
+//                     }
+//             }
+
+               int tryCount = 60;
+               try {
+                       tryGettyJetty: while (tryCount > 0) {
+                               try {
+                                       JettyConfigurator.startServer(KernelConstants.DEFAULT_JETTY_SERVER, webServerConfig);
+                                       // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
+                                       // configuration is not cleaned
+                                       FrameworkUtil.getBundle(JettyConfigurator.class).start();
+                                       break tryGettyJetty;
+                               } catch (IllegalStateException e) {
+                                       // Jetty may not be ready
+                                       try {
+                                               Thread.sleep(1000);
+                                       } catch (Exception e1) {
+                                               // silent
+                                       }
+                                       tryCount--;
+                               }
+                       }
+               } catch (Exception e) {
+                       log.error("Cannot start default Jetty server with config " + webServerConfig, e);
+               }
+
+       }
+
+       public void init() throws IOException {
+               if (!isInitialized()) { // first init
+                       isFirstInit = true;
+                       firstInit();
+               }
+
+               boolean isClean;
+               try {
+                       Configuration[] confs = configurationAdmin
+                                       .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
+                       isClean = confs == null || confs.length == 0;
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot analyse clean state", e);
+               }
+
+               try (InputStream in = Files.newInputStream(deployConfigPath)) {
+                       deployConfigs = new LdifParser().read(in);
+               }
+               if (isClean) {
+                       if (log.isDebugEnabled())
+                               log.debug("Clean state, loading from framework properties...");
+                       setFromFrameworkProperties(isFirstInit);
+                       loadConfigs();
+               }
+               // TODO check consistency if not clean
+       }
+
+       public void destroy() {
+
+       }
+
+       public void loadConfigs() throws IOException {
+               // FIXME make it more robust
+               Configuration systemRolesConf = null;
+               LdapName systemRolesDn;
+               try {
+                       // FIXME make it more robust
+                       systemRolesDn = new LdapName("cn=roles,ou=org.argeo.api.userAdmin,ou=deploy,ou=node");
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException(e);
+               }
+               deployConfigs: for (LdapName dn : deployConfigs.keySet()) {
+                       Rdn lastRdn = dn.getRdn(dn.size() - 1);
+                       LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1);
+                       if (prefix.toString().equals(CmsConstants.DEPLOY_BASEDN)) {
+                               if (lastRdn.getType().equals(CmsConstants.CN)) {
+                                       // service
+                                       String pid = lastRdn.getValue().toString();
+                                       Configuration conf = configurationAdmin.getConfiguration(pid);
+                                       AttributesDictionary dico = new AttributesDictionary(deployConfigs.get(dn));
+                                       conf.update(dico);
+                               } else {
+                                       // service factory definition
+                               }
+                       } else {
+                               Attributes config = deployConfigs.get(dn);
+                               Attribute disabled = config.get(UserAdminConf.disabled.name());
+                               if (disabled != null)
+                                       continue deployConfigs;
+                               // service factory service
+                               Rdn beforeLastRdn = dn.getRdn(dn.size() - 2);
+                               assert beforeLastRdn.getType().equals(CmsConstants.OU);
+                               String factoryPid = beforeLastRdn.getValue().toString();
+                               Configuration conf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
+                               if (systemRolesDn.equals(dn)) {
+                                       systemRolesConf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
+                               } else {
+                                       AttributesDictionary dico = new AttributesDictionary(config);
+                                       conf.update(dico);
+                               }
+                       }
+               }
+
+               // system roles must be last since it triggers node user admin publication
+               if (systemRolesConf == null)
+                       throw new IllegalStateException("System roles are not configured.");
+               systemRolesConf.update(new AttributesDictionary(deployConfigs.get(systemRolesDn)));
+
+       }
+
+       @Override
+       public void configurationEvent(ConfigurationEvent event) {
+               try {
+                       if (ConfigurationEvent.CM_UPDATED == event.getType()) {
+                               Configuration conf = configurationAdmin.getConfiguration(event.getPid(), null);
+                               LdapName serviceDn = null;
+                               String factoryPid = conf.getFactoryPid();
+                               if (factoryPid != null) {
+                                       LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
+                                       if (deployConfigs.containsKey(serviceFactoryDn)) {
+                                               for (LdapName dn : deployConfigs.keySet()) {
+                                                       if (dn.startsWith(serviceFactoryDn)) {
+                                                               Rdn lastRdn = dn.getRdn(dn.size() - 1);
+                                                               assert lastRdn.getType().equals(CmsConstants.CN);
+                                                               Object value = conf.getProperties().get(lastRdn.getType());
+                                                               assert value != null;
+                                                               if (value.equals(lastRdn.getValue())) {
+                                                                       serviceDn = dn;
+                                                                       break;
+                                                               }
+                                                       }
+                                               }
+
+                                               Object cn = conf.getProperties().get(CmsConstants.CN);
+                                               if (cn == null)
+                                                       throw new IllegalArgumentException("Properties must contain cn");
+                                               if (serviceDn == null) {
+                                                       putFactoryDeployConfig(factoryPid, conf.getProperties());
+                                               } else {
+                                                       Attributes attrs = deployConfigs.get(serviceDn);
+                                                       assert attrs != null;
+                                                       AttributesDictionary.copy(conf.getProperties(), attrs);
+                                               }
+                                               save();
+                                               if (log.isDebugEnabled())
+                                                       log.debug("Updated deploy config " + serviceDn(factoryPid, cn.toString()));
+                                       } else {
+                                               // ignore non config-registered service factories
+                                       }
+                               } else {
+                                       serviceDn = serviceDn(event.getPid());
+                                       if (deployConfigs.containsKey(serviceDn)) {
+                                               Attributes attrs = deployConfigs.get(serviceDn);
+                                               assert attrs != null;
+                                               AttributesDictionary.copy(conf.getProperties(), attrs);
+                                               save();
+                                               if (log.isDebugEnabled())
+                                                       log.debug("Updated deploy config " + serviceDn);
+                                       } else {
+                                               // ignore non config-registered services
+                                       }
+                               }
+                       }
+               } catch (Exception e) {
+                       log.error("Could not handle configuration event", e);
+               }
+       }
+
+       public void putFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
+               Object cn = props.get(CmsConstants.CN);
+               if (cn == null)
+                       throw new IllegalArgumentException("cn must be set in properties");
+               LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
+               if (!deployConfigs.containsKey(serviceFactoryDn))
+                       deployConfigs.put(serviceFactoryDn, new BasicAttributes(CmsConstants.OU, factoryPid));
+               LdapName serviceDn = serviceDn(factoryPid, cn.toString());
+               Attributes attrs = new BasicAttributes();
+               AttributesDictionary.copy(props, attrs);
+               deployConfigs.put(serviceDn, attrs);
+       }
+
+       void putDeployConfig(String servicePid, Dictionary<String, Object> props) {
+               LdapName serviceDn = serviceDn(servicePid);
+               Attributes attrs = new BasicAttributes(CmsConstants.CN, servicePid);
+               AttributesDictionary.copy(props, attrs);
+               deployConfigs.put(serviceDn, attrs);
+       }
+
+       public void save() {
+               try (Writer writer = Files.newBufferedWriter(deployConfigPath)) {
+                       new LdifWriter(writer).write(deployConfigs);
+               } catch (IOException e) {
+                       // throw new CmsException("Cannot save deploy configs", e);
+                       log.error("Cannot save deploy configs", e);
+               }
+       }
+
+       public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
+               this.configurationAdmin = configurationAdmin;
+       }
+
+       public boolean hasDomain() {
+               Configuration[] configs;
+               try {
+                       configs = configurationAdmin
+                                       .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
+               } catch (IOException | InvalidSyntaxException e) {
+                       throw new IllegalStateException("Cannot list user directories", e);
+               }
+
+               boolean hasDomain = false;
+               for (Configuration config : configs) {
+                       Object realm = config.getProperties().get(UserAdminConf.realm.name());
+                       if (realm != null) {
+                               log.debug("Found realm: " + realm);
+                               hasDomain = true;
+                       }
+               }
+               return hasDomain;
+       }
+
+       /*
+        * UTILITIES
+        */
+       private LdapName serviceFactoryDn(String factoryPid) {
+               try {
+                       return new LdapName(CmsConstants.OU + "=" + factoryPid + "," + CmsConstants.DEPLOY_BASEDN);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot generate DN from " + factoryPid, e);
+               }
+       }
+
+       private LdapName serviceDn(String servicePid) {
+               try {
+                       return new LdapName(CmsConstants.CN + "=" + servicePid + "," + CmsConstants.DEPLOY_BASEDN);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot generate DN from " + servicePid, e);
+               }
+       }
+
+       private LdapName serviceDn(String factoryPid, String cn) {
+               try {
+                       return (LdapName) serviceFactoryDn(factoryPid).add(new Rdn(CmsConstants.CN, cn));
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot generate DN from " + factoryPid + " and " + cn, e);
+               }
+       }
+
+       public Dictionary<String, Object> getProps(String factoryPid, String cn) {
+               Attributes attrs = deployConfigs.get(serviceDn(factoryPid, cn));
+               if (attrs != null)
+                       return new AttributesDictionary(attrs);
+               else
+                       return null;
+       }
+
+       private static boolean isInitialized() {
+               return Files.exists(deployConfigPath);
+       }
+
+       public boolean isFirstInit() {
+               return isFirstInit;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeLogger.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeLogger.java
new file mode 100644 (file)
index 0000000..69dbec9
--- /dev/null
@@ -0,0 +1,541 @@
+package org.argeo.cms.internal.osgi;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardWatchEventKinds;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.security.SignatureException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ArgeoLogListener;
+import org.argeo.cms.ArgeoLogger;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.internal.runtime.KernelConstants;
+import org.argeo.cms.internal.runtime.KernelUtils;
+import org.argeo.osgi.useradmin.UserAdminConf;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogLevel;
+import org.osgi.service.log.LogListener;
+import org.osgi.service.log.LogReaderService;
+
+/** Not meant to be used directly in standard log4j config */
+public class NodeLogger implements ArgeoLogger, LogListener {
+       /** Internal debug for development purposes. */
+       private static Boolean debug = false;
+
+       private Boolean disabled = false;
+
+       private String level = null;
+
+//     private Level log4jLevel = null;
+
+       private Properties configuration;
+
+       private AppenderImpl appender;
+
+       private final List<ArgeoLogListener> everythingListeners = Collections
+                       .synchronizedList(new ArrayList<ArgeoLogListener>());
+       private final List<ArgeoLogListener> allUsersListeners = Collections
+                       .synchronizedList(new ArrayList<ArgeoLogListener>());
+       private final Map<String, List<ArgeoLogListener>> userListeners = Collections
+                       .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
+
+       private BlockingQueue<LogEvent> events;
+       private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
+
+       private Integer maxLastEventsCount = 10 * 1000;
+
+       /** Marker to prevent stack overflow */
+       private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
+
+               @Override
+               protected Boolean initialValue() {
+                       return false;
+               }
+       };
+
+       public NodeLogger(LogReaderService lrs) {
+               if (lrs != null) {
+                       Enumeration<LogEntry> logEntries = lrs.getLog();
+                       while (logEntries.hasMoreElements())
+                               logged(logEntries.nextElement());
+                       lrs.addLogListener(this);
+
+                       // configure log4j watcher
+                       String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration");
+                       if (log4jConfiguration != null && log4jConfiguration.startsWith("file:")) {
+                               if (log4jConfiguration.contains("..")) {
+                                       if (log4jConfiguration.startsWith("file://"))
+                                               log4jConfiguration = log4jConfiguration.substring("file://".length());
+                                       else if (log4jConfiguration.startsWith("file:"))
+                                               log4jConfiguration = log4jConfiguration.substring("file:".length());
+                               }
+                               try {
+                                       Path log4jconfigPath;
+                                       if (log4jConfiguration.startsWith("file:"))
+                                               log4jconfigPath = Paths.get(new URI(log4jConfiguration));
+                                       else
+                                               log4jconfigPath = Paths.get(log4jConfiguration);
+                                       Thread log4jConfWatcher = new Log4jConfWatcherThread(log4jconfigPath);
+                                       log4jConfWatcher.start();
+                               } catch (Exception e) {
+                                       stdErr("Badly formatted log4j configuration URI " + log4jConfiguration + ": " + e.getMessage());
+                               }
+                       }
+               }
+       }
+
+       public void init() {
+               try {
+                       events = new LinkedBlockingQueue<LogEvent>();
+
+                       // if (layout != null)
+                       // setLayout(layout);
+                       // else
+                       // setLayout(new PatternLayout(pattern));
+                       appender = new AppenderImpl();
+                       reloadConfiguration();
+//                     Logger.getRootLogger().addAppender(appender);
+
+                       logDispatcherThread = new LogDispatcherThread();
+                       logDispatcherThread.start();
+               } catch (Exception e) {
+                       throw new CmsException("Cannot initialize log4j");
+               }
+       }
+
+       public void destroy() throws Exception {
+//             Logger.getRootLogger().removeAppender(appender);
+               allUsersListeners.clear();
+               for (List<ArgeoLogListener> lst : userListeners.values())
+                       lst.clear();
+               userListeners.clear();
+
+               events.clear();
+               events = null;
+               logDispatcherThread.interrupt();
+       }
+
+       // public void setLayout(Layout layout) {
+       // this.layout = layout;
+       // }
+
+       public String toString() {
+               return "Node Logger";
+       }
+
+       //
+       // OSGi LOGGER
+       //
+       @Override
+       public void logged(LogEntry status) {
+               CmsLog pluginLog = CmsLog.getLog(status.getBundle().getSymbolicName());
+               LogLevel severity = status.getLogLevel();
+               if (severity.equals(LogLevel.ERROR) && pluginLog.isErrorEnabled()) {
+                       // FIXME Fix Argeo TP
+                       if (status.getException() instanceof SignatureException)
+                               return;
+                       pluginLog.error(msg(status), status.getException());
+               } else if (severity.equals(LogLevel.WARN) && pluginLog.isWarnEnabled()) {
+                       if (pluginLog.isTraceEnabled())
+                               pluginLog.warn(msg(status), status.getException());
+                       else
+                               pluginLog.warn(msg(status));
+               } else if (severity.equals(LogLevel.INFO) && pluginLog.isDebugEnabled())
+                       pluginLog.debug(msg(status), status.getException());
+               else if (severity.equals(LogLevel.DEBUG) && pluginLog.isTraceEnabled())
+                       pluginLog.trace(msg(status), status.getException());
+               else if (severity.equals(LogLevel.TRACE) && pluginLog.isTraceEnabled())
+                       pluginLog.trace(msg(status), status.getException());
+       }
+
+       private String msg(LogEntry status) {
+               StringBuilder sb = new StringBuilder();
+               sb.append(status.getMessage());
+               Bundle bundle = status.getBundle();
+               if (bundle != null) {
+                       sb.append(" '" + bundle.getSymbolicName() + "'");
+               }
+               ServiceReference<?> sr = status.getServiceReference();
+               if (sr != null) {
+                       sb.append(' ');
+                       String[] objectClasses = (String[]) sr.getProperty(Constants.OBJECTCLASS);
+                       if (isSpringApplicationContext(objectClasses)) {
+                               sb.append("{org.springframework.context.ApplicationContext}");
+                               Object symbolicName = sr.getProperty(Constants.BUNDLE_SYMBOLICNAME);
+                               if (symbolicName != null)
+                                       sb.append(" " + Constants.BUNDLE_SYMBOLICNAME + ": " + symbolicName);
+                       } else {
+                               sb.append(arrayToString(objectClasses));
+                       }
+                       Object cn = sr.getProperty(CmsConstants.CN);
+                       if (cn != null)
+                               sb.append(" " + CmsConstants.CN + ": " + cn);
+                       Object factoryPid = sr.getProperty(ConfigurationAdmin.SERVICE_FACTORYPID);
+                       if (factoryPid != null)
+                               sb.append(" " + ConfigurationAdmin.SERVICE_FACTORYPID + ": " + factoryPid);
+                       // else {
+                       // Object servicePid = sr.getProperty(Constants.SERVICE_PID);
+                       // if (servicePid != null)
+                       // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid);
+                       // }
+                       // servlets
+                       Object whiteBoardPattern = sr.getProperty(KernelConstants.WHITEBOARD_PATTERN_PROP);
+                       if (whiteBoardPattern != null) {
+                               if (whiteBoardPattern instanceof String) {
+                                       sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern);
+                               } else {
+                                       sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": "
+                                                       + arrayToString((String[]) whiteBoardPattern));
+                               }
+                       }
+                       // RWT
+                       Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP);
+                       if (contextName != null)
+                               sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName);
+
+                       // user directories
+                       Object baseDn = sr.getProperty(UserAdminConf.baseDn.name());
+                       if (baseDn != null)
+                               sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn);
+
+               }
+               return sb.toString();
+       }
+
+       private String arrayToString(Object[] arr) {
+               StringBuilder sb = new StringBuilder();
+               sb.append('[');
+               for (int i = 0; i < arr.length; i++) {
+                       if (i != 0)
+                               sb.append(',');
+                       sb.append(arr[i]);
+               }
+               sb.append(']');
+               return sb.toString();
+       }
+
+       private boolean isSpringApplicationContext(String[] objectClasses) {
+               for (String clss : objectClasses) {
+                       if (clss.equals("org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext")) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       //
+       // ARGEO LOGGER
+       //
+
+       public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
+               String username = CurrentUser.getUsername();
+               if (username == null)
+                       throw new CmsException("Only authenticated users can register a log listener");
+
+               if (!userListeners.containsKey(username)) {
+                       List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
+                       userListeners.put(username, lst);
+               }
+               userListeners.get(username).add(listener);
+               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
+               for (LogEvent evt : lastEvents)
+                       dispatchEvent(listener, evt);
+       }
+
+       public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
+                       boolean everything) {
+               if (everything)
+                       everythingListeners.add(listener);
+               else
+                       allUsersListeners.add(listener);
+               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
+               for (LogEvent evt : lastEvents)
+                       if (everything || evt.getUsername() != null)
+                               dispatchEvent(listener, evt);
+       }
+
+       public synchronized void unregister(ArgeoLogListener listener) {
+               String username = CurrentUser.getUsername();
+               if (username == null)// FIXME
+                       return;
+               if (!userListeners.containsKey(username))
+                       throw new CmsException("No user listeners " + listener + " registered for user " + username);
+               if (!userListeners.get(username).contains(listener))
+                       throw new CmsException("No user listeners " + listener + " registered for user " + username);
+               userListeners.get(username).remove(listener);
+               if (userListeners.get(username).isEmpty())
+                       userListeners.remove(username);
+
+       }
+
+       public synchronized void unregisterForAll(ArgeoLogListener listener) {
+               everythingListeners.remove(listener);
+               allUsersListeners.remove(listener);
+       }
+
+       /** For development purpose, since using regular logging is not easy here */
+       private static void stdOut(Object obj) {
+               System.out.println(obj);
+       }
+
+       private static void stdErr(Object obj) {
+               System.err.println(obj);
+       }
+
+       private static void debug(Object obj) {
+               if (debug)
+                       System.out.println(obj);
+       }
+
+       private static boolean isInternalDebugEnabled() {
+               return debug;
+       }
+
+       // public void setPattern(String pattern) {
+       // this.pattern = pattern;
+       // }
+
+       public void setDisabled(Boolean disabled) {
+               this.disabled = disabled;
+       }
+
+       public void setLevel(String level) {
+               this.level = level;
+       }
+
+       public void setConfiguration(Properties configuration) {
+               this.configuration = configuration;
+       }
+
+       public void updateConfiguration(Properties configuration) {
+               setConfiguration(configuration);
+               reloadConfiguration();
+       }
+
+       public Properties getConfiguration() {
+               return configuration;
+       }
+
+       /**
+        * Reloads configuration (if the configuration {@link Properties} is set)
+        */
+       protected void reloadConfiguration() {
+               if (configuration != null) {
+//                     LogManager.resetConfiguration();
+//                     PropertyConfigurator.configure(configuration);
+               }
+       }
+
+       protected synchronized void processLoggingEvent(LogEvent event) {
+               if (disabled)
+                       return;
+
+               if (dispatching.get())
+                       return;
+
+               if (level != null && !level.trim().equals("")) {
+//                     if (log4jLevel == null || !log4jLevel.toString().equals(level))
+//                             try {
+//                                     log4jLevel = Level.toLevel(level);
+//                             } catch (Exception e) {
+//                                     System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
+//                                     e.printStackTrace();
+//                                     level = null;
+//                             }
+//
+//                     if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
+//                             return;
+//                     }
+               }
+
+               try {
+                       // admin listeners
+                       Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
+                       while (everythingIt.hasNext())
+                               dispatchEvent(everythingIt.next(), event);
+
+                       if (event.getUsername() != null) {
+                               Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
+                               while (allUsersIt.hasNext())
+                                       dispatchEvent(allUsersIt.next(), event);
+
+                               if (userListeners.containsKey(event.getUsername())) {
+                                       Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
+                                       while (userIt.hasNext())
+                                               dispatchEvent(userIt.next(), event);
+                               }
+                       }
+               } catch (Exception e) {
+                       stdOut("Cannot process logging event");
+                       e.printStackTrace();
+               }
+       }
+
+       protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
+//             LoggingEvent event = evt.getLoggingEvent();
+//             logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
+//                             event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
+       }
+
+       private class AppenderImpl { //extends AppenderSkeleton {
+               public boolean requiresLayout() {
+                       return false;
+               }
+
+               public void close() {
+               }
+
+//             @Override
+//             protected void append(LoggingEvent event) {
+//                     if (events != null) {
+//                             try {
+//                                     String username = CurrentUser.getUsername();
+//                                     events.put(new LogEvent(username, event));
+//                             } catch (InterruptedException e) {
+//                                     // silent
+//                             }
+//                     }
+//             }
+
+       }
+
+       private class LogDispatcherThread extends Thread {
+               /** encapsulated in order to simplify concurrency management */
+               private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
+
+               public LogDispatcherThread() {
+                       super("Argeo Logging Dispatcher Thread");
+               }
+
+               public void run() {
+                       while (events != null) {
+                               try {
+                                       LogEvent loggingEvent = events.take();
+                                       processLoggingEvent(loggingEvent);
+                                       addLastEvent(loggingEvent);
+                               } catch (InterruptedException e) {
+                                       if (events == null)
+                                               return;
+                               }
+                       }
+               }
+
+               protected synchronized void addLastEvent(LogEvent loggingEvent) {
+                       if (lastEvents.size() >= maxLastEventsCount)
+                               lastEvents.poll();
+                       lastEvents.add(loggingEvent);
+               }
+
+               public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
+                       LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
+                       ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
+                       int count = 0;
+                       while (it.hasPrevious() && (count < maxCount)) {
+                               LogEvent evt = it.previous();
+                               if (username == null || username.equals(evt.getUsername())) {
+                                       evts.push(evt);
+                                       count++;
+                               }
+                       }
+                       return evts;
+               }
+       }
+
+       private class LogEvent {
+               private final String username;
+//             private final LoggingEvent loggingEvent;
+
+               public LogEvent(String username) {
+                       super();
+                       this.username = username;
+//                     this.loggingEvent = loggingEvent;
+               }
+
+//             @Override
+//             public int hashCode() {
+//                     return loggingEvent.hashCode();
+//             }
+//
+//             @Override
+//             public boolean equals(Object obj) {
+//                     return loggingEvent.equals(obj);
+//             }
+//
+//             @Override
+//             public String toString() {
+//                     return username + "@ " + loggingEvent.toString();
+//             }
+
+               public String getUsername() {
+                       return username;
+               }
+
+//             public LoggingEvent getLoggingEvent() {
+//                     return loggingEvent;
+//             }
+
+       }
+
+       private class Log4jConfWatcherThread extends Thread {
+               private Path log4jConfigurationPath;
+
+               public Log4jConfWatcherThread(Path log4jConfigurationPath) {
+                       super("Log4j Configuration Watcher");
+                       try {
+                               this.log4jConfigurationPath = log4jConfigurationPath.toRealPath();
+                       } catch (IOException e) {
+                               this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath();
+                               stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage());
+                       }
+               }
+
+               public void run() {
+                       Path parentDir = log4jConfigurationPath.getParent();
+                       try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
+                               parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
+                               WatchKey wk;
+                               watching: while ((wk = watchService.take()) != null) {
+                                       for (WatchEvent<?> event : wk.pollEvents()) {
+                                               final Path changed = (Path) event.context();
+                                               if (log4jConfigurationPath.equals(parentDir.resolve(changed))) {
+                                                       if (isInternalDebugEnabled())
+                                                               debug(log4jConfigurationPath + " has changed, reloading.");
+//                                                     PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL());
+                                               }
+                                       }
+                                       // reset the key
+                                       boolean valid = wk.reset();
+                                       if (!valid) {
+                                               break watching;
+                                       }
+                               }
+                       } catch (IOException | InterruptedException e) {
+                               stdErr("Log4j configuration watcher failed: " + e.getMessage());
+                       }
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java
new file mode 100644 (file)
index 0000000..d9524e8
--- /dev/null
@@ -0,0 +1,395 @@
+package org.argeo.cms.internal.osgi;
+
+import java.io.IOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.commons.httpclient.auth.AuthPolicy;
+import org.apache.commons.httpclient.auth.CredentialsProvider;
+import org.apache.commons.httpclient.params.DefaultHttpParams;
+import org.apache.commons.httpclient.params.HttpMethodParams;
+import org.apache.commons.httpclient.params.HttpParams;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.http.client.HttpCredentialProvider;
+import org.argeo.cms.internal.http.client.SpnegoAuthScheme;
+import org.argeo.cms.internal.runtime.KernelConstants;
+import org.argeo.cms.internal.runtime.KernelUtils;
+import org.argeo.osgi.transaction.WorkControl;
+import org.argeo.osgi.transaction.WorkTransaction;
+import org.argeo.osgi.useradmin.AbstractUserDirectory;
+import org.argeo.osgi.useradmin.AggregatingUserAdmin;
+import org.argeo.osgi.useradmin.LdapUserAdmin;
+import org.argeo.osgi.useradmin.LdifUserAdmin;
+import org.argeo.osgi.useradmin.OsUserDirectory;
+import org.argeo.osgi.useradmin.UserAdminConf;
+import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.naming.DnsBrowser;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Aggregates multiple {@link UserDirectory} and integrates them with system
+ * roles.
+ */
+public class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactory, KernelConstants {
+       private final static CmsLog log = CmsLog.getLog(NodeUserAdmin.class);
+
+       // OSGi
+       private Map<String, LdapName> pidToBaseDn = new HashMap<>();
+//     private Map<String, ServiceRegistration<UserDirectory>> pidToServiceRegs = new HashMap<>();
+//     private ServiceRegistration<UserAdmin> userAdminReg;
+
+       // JTA
+//     private final ServiceTracker<WorkControl, WorkControl> tmTracker;
+       // private final String cacheName = UserDirectory.class.getName();
+
+       // GSS API
+       private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH);
+       private GSSCredential acceptorCredentials;
+
+       private boolean singleUser = false;
+//     private boolean systemRolesAvailable = false;
+
+//     CmsUserManagerImpl userManager;
+       private WorkControl transactionManager;
+       private WorkTransaction userTransaction;
+
+       public NodeUserAdmin() {
+               super(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
+//             BundleContext bc = Activator.getBundleContext();
+//             if (bc != null) {
+//                     tmTracker = new ServiceTracker<>(bc, WorkControl.class, null) {
+//
+//                             @Override
+//                             public WorkControl addingService(ServiceReference<WorkControl> reference) {
+//                                     WorkControl workControl = super.addingService(reference);
+//                                     userManager = new CmsUserManagerImpl();
+//                                     userManager.setUserAdmin(NodeUserAdmin.this);
+//                                     // FIXME make it more robust
+//                                     userManager.setUserTransaction((WorkTransaction) workControl);
+//                                     bc.registerService(CmsUserManager.class, userManager, null);
+//                                     return workControl;
+//                             }
+//                     };
+//                     tmTracker.open();
+//             } else {
+//                     tmTracker = null;
+//             }
+       }
+
+       public void init() {
+       }
+
+       public void destroy() {
+       }
+
+       @Override
+       public void updated(String pid, Dictionary<String, ?> 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 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 (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);
+               } else if (UserAdminConf.SCHEME_OS.equals(u.getScheme())) {
+                       userDirectory = new OsUserDirectory(u, properties);
+                       singleUser = true;
+               } else {
+                       throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
+               }
+               addUserDirectory(userDirectory);
+
+               // OSGi
+               LdapName baseDn = userDirectory.getBaseDn();
+               Hashtable<String, Object> regProps = new Hashtable<>();
+               regProps.put(Constants.SERVICE_PID, pid);
+               if (isSystemRolesBaseDn(baseDn))
+                       regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
+               regProps.put(UserAdminConf.baseDn.name(), baseDn);
+               // ServiceRegistration<UserDirectory> reg =
+               // bc.registerService(UserDirectory.class, userDirectory, regProps);
+               CmsActivator.getBundleContext().registerService(UserDirectory.class, userDirectory, regProps);
+//             userManager.addUserDirectory(userDirectory, regProps);
+               pidToBaseDn.put(pid, baseDn);
+               // pidToServiceRegs.put(pid, reg);
+
+               if (log.isDebugEnabled()) {
+                       log.debug("User directory " + userDirectory.getBaseDn() + (u != null ? " [" + u.getScheme() + "]" : "")
+                                       + " enabled." + (realm != null ? " " + realm + " realm." : ""));
+               }
+
+               if (isSystemRolesBaseDn(baseDn)) {
+                       addStandardSystemRoles();
+
+                       // publishes itself as user admin only when system roles are available
+                       Dictionary<String, Object> userAdminregProps = new Hashtable<>();
+                       userAdminregProps.put(CmsConstants.CN, CmsConstants.DEFAULT);
+                       userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
+                       CmsActivator.getBundleContext().registerService(UserAdmin.class, this, userAdminregProps);
+               }
+
+//             if (isSystemRolesBaseDn(baseDn))
+//                     systemRolesAvailable = true;
+//
+//             // start publishing only when system roles are available
+//             if (systemRolesAvailable) {
+//                     // The list of baseDns is published as properties
+//                     // TODO clients should rather reference USerDirectory services
+//                     if (userAdminReg != null)
+//                             userAdminReg.unregister();
+//                     // register self as main user admin
+//                     Dictionary<String, Object> userAdminregProps = currentState();
+//                     userAdminregProps.put(NodeConstants.CN, NodeConstants.DEFAULT);
+//                     userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
+//                     userAdminReg = bc.registerService(UserAdmin.class, this, userAdminregProps);
+//             }
+       }
+
+       private void addStandardSystemRoles() {
+               // we assume UserTransaction is already available (TODO make it more robust)
+               try {
+                       userTransaction.begin();
+                       Role adminRole = getRole(CmsConstants.ROLE_ADMIN);
+                       if (adminRole == null) {
+                               adminRole = createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
+                       }
+                       if (getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
+                               Group userAdminRole = (Group) createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
+                               userAdminRole.addMember(adminRole);
+                       }
+                       userTransaction.commit();
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               // silent
+                       }
+                       throw new IllegalStateException("Cannot add standard system roles", e);
+               }
+       }
+
+       @Override
+       public void deleted(String pid) {
+               // assert pidToServiceRegs.get(pid) != null;
+               assert pidToBaseDn.get(pid) != null;
+               // pidToServiceRegs.remove(pid).unregister();
+               LdapName baseDn = pidToBaseDn.remove(pid);
+               removeUserDirectory(baseDn);
+       }
+
+       @Override
+       public String getName() {
+               return "Node User Admin";
+       }
+
+       @Override
+       protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
+               if (rawAuthorization.getName() == null) {
+                       sysRoles.add(CmsConstants.ROLE_ANONYMOUS);
+               } else {
+                       sysRoles.add(CmsConstants.ROLE_USER);
+               }
+       }
+
+       protected void postAdd(AbstractUserDirectory userDirectory) {
+               // JTA
+//             WorkControl tm = tmTracker != null ? tmTracker.getService() : null;
+//             if (tm == null)
+//                     throw new IllegalStateException("A JTA transaction manager must be available.");
+               userDirectory.setTransactionControl(transactionManager);
+//             if (tmTracker.getService() instanceof BitronixTransactionManager)
+//                     EhCacheXAResourceProducer.registerXAResource(cacheName, userDirectory.getXaResource());
+
+               Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
+               if (realm != null) {
+                       if (Files.exists(nodeKeyTab)) {
+                               String servicePrincipal = getKerberosServicePrincipal(realm.toString());
+                               if (servicePrincipal != null) {
+                                       CallbackHandler callbackHandler = new CallbackHandler() {
+                                               @Override
+                                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                                                       for (Callback callback : callbacks)
+                                                               if (callback instanceof NameCallback)
+                                                                       ((NameCallback) callback).setName(servicePrincipal);
+
+                                               }
+                                       };
+                                       try {
+                                               LoginContext nodeLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_NODE, callbackHandler);
+                                               nodeLc.login();
+                                               acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal);
+                                       } catch (LoginException e) {
+                                               throw new IllegalStateException("Cannot log in kernel", e);
+                                       }
+                               }
+                       }
+
+                       // Register client-side SPNEGO auth scheme
+                       AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
+                       HttpParams params = DefaultHttpParams.getDefaultParams();
+                       ArrayList<String> schemes = new ArrayList<>();
+                       schemes.add(SpnegoAuthScheme.NAME);// SPNEGO preferred
+                       // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
+                       params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
+                       params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
+                       params.setParameter(HttpMethodParams.COOKIE_POLICY, KernelConstants.COOKIE_POLICY_BROWSER_COMPATIBILITY);
+                       // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
+               }
+       }
+
+       protected void preDestroy(AbstractUserDirectory userDirectory) {
+//             if (tmTracker.getService() instanceof BitronixTransactionManager)
+//                     EhCacheXAResourceProducer.unregisterXAResource(cacheName, userDirectory.getXaResource());
+
+               Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
+               if (realm != null) {
+                       if (acceptorCredentials != null) {
+                               try {
+                                       acceptorCredentials.dispose();
+                               } catch (GSSException e) {
+                                       // silent
+                               }
+                               acceptorCredentials = null;
+                       }
+               }
+       }
+
+       private String getKerberosServicePrincipal(String realm) {
+               String hostname;
+               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+                       InetAddress localhost = InetAddress.getLocalHost();
+                       hostname = localhost.getHostName();
+                       String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
+                       String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
+                       boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
+                       String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
+                       if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) {
+                               return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
+                       } else
+                               return null;
+               } catch (Exception e) {
+                       log.warn("Exception when determining kerberos principal", e);
+                       return null;
+               }
+       }
+
+       private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) {
+               // GSS
+               Iterator<KerberosPrincipal> krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator();
+               if (!krb5It.hasNext())
+                       return null;
+               KerberosPrincipal krb5Principal = null;
+               while (krb5It.hasNext()) {
+                       KerberosPrincipal principal = krb5It.next();
+                       if (principal.getName().equals(servicePrincipal))
+                               krb5Principal = principal;
+               }
+
+               if (krb5Principal == null)
+                       return null;
+
+               GSSManager manager = GSSManager.getInstance();
+               try {
+                       GSSName gssName = manager.createName(krb5Principal.getName(), null);
+                       GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
+
+                               @Override
+                               public GSSCredential run() throws GSSException {
+                                       return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
+                                                       GSSCredential.ACCEPT_ONLY);
+
+                               }
+
+                       });
+                       if (log.isDebugEnabled())
+                               log.debug("GSS acceptor configured for " + krb5Principal);
+                       return serverCredentials;
+               } catch (Exception gsse) {
+                       throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse);
+               }
+       }
+
+       public GSSCredential getAcceptorCredentials() {
+               return acceptorCredentials;
+       }
+
+       public boolean hasAcceptorCredentials() {
+               return acceptorCredentials != null;
+       }
+
+       public boolean isSingleUser() {
+               return singleUser;
+       }
+
+       public void setTransactionManager(WorkControl transactionManager) {
+               this.transactionManager = transactionManager;
+       }
+
+       public void setUserTransaction(WorkTransaction userTransaction) {
+               this.userTransaction = userTransaction;
+       }
+
+       /*
+        * STATIC
+        */
+
+       public final static Oid KERBEROS_OID;
+       static {
+               try {
+                       KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
+               } catch (GSSException e) {
+                       throw new IllegalStateException("Cannot create Kerberos OID", e);
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/SecurityProfile.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/SecurityProfile.java
new file mode 100644 (file)
index 0000000..7055538
--- /dev/null
@@ -0,0 +1,324 @@
+package org.argeo.cms.internal.osgi;
+
+import java.io.FilePermission;
+import java.lang.reflect.ReflectPermission;
+import java.net.SocketPermission;
+import java.security.AllPermission;
+import java.util.PropertyPermission;
+
+import javax.security.auth.AuthPermission;
+
+import org.osgi.framework.AdminPermission;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServicePermission;
+import org.osgi.service.cm.ConfigurationPermission;
+import org.osgi.service.condpermadmin.BundleLocationCondition;
+import org.osgi.service.condpermadmin.ConditionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
+import org.osgi.service.permissionadmin.PermissionAdmin;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/** Security profile based on OSGi {@link PermissionAdmin}. */
+public interface SecurityProfile {
+       BundleContext bc = FrameworkUtil.getBundle(SecurityProfile.class).getBundleContext();
+
+       default void applySystemPermissions(ConditionalPermissionAdmin permissionAdmin) {
+               ConditionalPermissionUpdate update = permissionAdmin.newConditionalPermissionUpdate();
+               // Self
+//             String nodeAPiBundleLocation = locate(NodeUtils.class);
+//             update.getConditionalPermissionInfos()
+//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
+//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                                             new String[] { nodeAPiBundleLocation }) },
+//                                             new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
+//                                             ConditionalPermissionInfo.ALLOW));
+               String cmsBundleLocation = locate(SecurityProfile.class);
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { cmsBundleLocation }) },
+                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
+                                               ConditionalPermissionInfo.ALLOW));
+               String frameworkBundleLocation = bc.getBundle(0).getLocation();
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { frameworkBundleLocation }) },
+                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
+                                               ConditionalPermissionInfo.ALLOW));
+               // All
+               // FIXME understand why Jetty and Jackrabbit require that
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null, null, new PermissionInfo[] {
+                                               new PermissionInfo(SocketPermission.class.getName(), "localhost:7070", "listen,resolve"),
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "DEBUG", "read"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "STOP.*", "read"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "org.apache.jackrabbit.*", "read"),
+                                               new PermissionInfo(RuntimePermission.class.getName(), "*", "*"), },
+                                               ConditionalPermissionInfo.ALLOW));
+
+               // Eclipse
+               // update.getConditionalPermissionInfos()
+               // .add(permissionAdmin.newConditionalPermissionInfo(null,
+               // new ConditionInfo[] { new
+               // ConditionInfo(BundleLocationCondition.class.getName(),
+               // new String[] { "*/org.eclipse.*" }) },
+               // new PermissionInfo[] { new
+               // PermissionInfo(RuntimePermission.class.getName(), "*", "*"),
+               // new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
+               // new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
+               // new PermissionInfo(ServicePermission.class.getName(), "*",
+               // "register"),
+               // new PermissionInfo(TopicPermission.class.getName(), "*", "publish"),
+               // new PermissionInfo(TopicPermission.class.getName(), "*",
+               // "subscribe"),
+               // new PermissionInfo(PropertyPermission.class.getName(), "osgi.*",
+               // "read"),
+               // new PermissionInfo(PropertyPermission.class.getName(), "eclipse.*",
+               // "read"),
+               // new PermissionInfo(PropertyPermission.class.getName(),
+               // "org.eclipse.*", "read"),
+               // new PermissionInfo(PropertyPermission.class.getName(), "equinox.*",
+               // "read"),
+               // new PermissionInfo(PropertyPermission.class.getName(), "xml.*",
+               // "read"),
+               // new PermissionInfo("org.eclipse.equinox.log.LogPermission", "*",
+               // "log"), },
+               // ConditionalPermissionInfo.ALLOW));
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { "*/org.eclipse.*" }) },
+                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null), },
+                                               ConditionalPermissionInfo.ALLOW));
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { "*/org.apache.felix.*" }) },
+                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null), },
+                                               ConditionalPermissionInfo.ALLOW));
+
+               // Configuration admin
+//             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+//                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                             new String[] { locate(configurationAdmin.getService().getClass()) }) },
+//                             new PermissionInfo[] { new PermissionInfo(ConfigurationPermission.class.getName(), "*", "configure"),
+//                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
+//                                             new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"), },
+//                             ConditionalPermissionInfo.ALLOW));
+
+               // Bitronix
+//             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+//                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                             new String[] { locate(BitronixTransactionManager.class) }) },
+//                             new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "bitronix.tm.*", "read"),
+//                                             new PermissionInfo(RuntimePermission.class.getName(), "getClassLoader", null),
+//                                             new PermissionInfo(MBeanServerPermission.class.getName(), "createMBeanServer", null),
+//                                             new PermissionInfo(MBeanPermission.class.getName(), "bitronix.tm.*", "registerMBean"),
+//                                             new PermissionInfo(MBeanTrustPermission.class.getName(), "register", null) },
+//                             ConditionalPermissionInfo.ALLOW));
+
+               // DS
+               Bundle dsBundle = findBundle("org.eclipse.equinox.ds");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { dsBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(ConfigurationPermission.class.getName(), "*", "configure"),
+                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
+                                               new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
+                                               new PermissionInfo(ServicePermission.class.getName(), "*", "register"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "xml.*", "read"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "equinox.*", "read"),
+                                               new PermissionInfo(RuntimePermission.class.getName(), "accessDeclaredMembers", null),
+                                               new PermissionInfo(RuntimePermission.class.getName(), "getClassLoader", null),
+                                               new PermissionInfo(ReflectPermission.class.getName(), "suppressAccessChecks", null), },
+                               ConditionalPermissionInfo.ALLOW));
+
+               // Jetty
+               // Bundle jettyUtilBundle = findBundle("org.eclipse.equinox.http.jetty");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { "*/org.eclipse.jetty.*" }) },
+                               new PermissionInfo[] {
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
+                               ConditionalPermissionInfo.ALLOW));
+               Bundle servletBundle = findBundle("javax.servlet");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { servletBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(),
+                                               "org.glassfish.web.rfc2109_cookie_names_enforced", "read") },
+                               ConditionalPermissionInfo.ALLOW));
+
+               // required to be able to get the BundleContext in the customizer
+               Bundle jettyCustomizerBundle = findBundle("org.argeo.ext.equinox.jetty");
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { jettyCustomizerBundle.getLocation() }) },
+                                               new PermissionInfo[] { new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
+                                               ConditionalPermissionInfo.ALLOW));
+
+               // Blueprint
+//             Bundle blueprintBundle = findBundle("org.eclipse.gemini.blueprint.core");
+//             update.getConditionalPermissionInfos()
+//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
+//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                                             new String[] { blueprintBundle.getLocation() }) },
+//                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
+//                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
+//                                             ConditionalPermissionInfo.ALLOW));
+//             Bundle blueprintExtenderBundle = findBundle("org.eclipse.gemini.blueprint.extender");
+//             update.getConditionalPermissionInfos()
+//                             .add(permissionAdmin
+//                                             .newConditionalPermissionInfo(null,
+//                                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                                                             new String[] { blueprintExtenderBundle.getLocation() }) },
+//                                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
+//                                                                             new PermissionInfo(PropertyPermission.class.getName(), "org.eclipse.gemini.*",
+//                                                                                             "read"),
+//                                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
+//                                                                             new PermissionInfo(ServicePermission.class.getName(), "*", "register"), },
+//                                                             ConditionalPermissionInfo.ALLOW));
+//             Bundle springCoreBundle = findBundle("org.springframework.core");
+//             update.getConditionalPermissionInfos()
+//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
+//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                                             new String[] { springCoreBundle.getLocation() }) },
+//                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
+//                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
+//                                             ConditionalPermissionInfo.ALLOW));
+//             Bundle blueprintIoBundle = findBundle("org.eclipse.gemini.blueprint.io");
+//             update.getConditionalPermissionInfos()
+//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
+//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                                             new String[] { blueprintIoBundle.getLocation() }) },
+//                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
+//                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
+//                                             ConditionalPermissionInfo.ALLOW));
+
+               // Equinox
+               Bundle registryBundle = findBundle("org.eclipse.equinox.registry");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { registryBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "eclipse.*", "read"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"),
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
+                               ConditionalPermissionInfo.ALLOW));
+
+               Bundle equinoxUtilBundle = findBundle("org.eclipse.equinox.util");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { equinoxUtilBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "equinox.*", "read"),
+                                               new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
+                                               new PermissionInfo(ServicePermission.class.getName(), "*", "register"), },
+                               ConditionalPermissionInfo.ALLOW));
+               Bundle equinoxCommonBundle = findBundle("org.eclipse.equinox.common");
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { equinoxCommonBundle.getLocation() }) },
+                                               new PermissionInfo[] { new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
+                                               ConditionalPermissionInfo.ALLOW));
+
+               Bundle consoleBundle = findBundle("org.eclipse.equinox.console");
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { consoleBundle.getLocation() }) },
+                                               new PermissionInfo[] { new PermissionInfo(ServicePermission.class.getName(), "*", "register"),
+                                                               new PermissionInfo(AdminPermission.class.getName(), "*", "listener") },
+                                               ConditionalPermissionInfo.ALLOW));
+               Bundle preferencesBundle = findBundle("org.eclipse.equinox.preferences");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { preferencesBundle.getLocation() }) },
+                               new PermissionInfo[] {
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
+                               ConditionalPermissionInfo.ALLOW));
+               Bundle appBundle = findBundle("org.eclipse.equinox.app");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { appBundle.getLocation() }) },
+                               new PermissionInfo[] {
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
+                               ConditionalPermissionInfo.ALLOW));
+
+               // Jackrabbit
+               Bundle jackrabbitCoreBundle = findBundle("org.apache.jackrabbit.core");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { jackrabbitCoreBundle.getLocation() }) },
+                               new PermissionInfo[] {
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "*", "read,write"),
+                                               new PermissionInfo(AuthPermission.class.getName(), "getSubject", null),
+                                               new PermissionInfo(AuthPermission.class.getName(), "getLoginConfiguration", null),
+                                               new PermissionInfo(AuthPermission.class.getName(), "createLoginContext.Jackrabbit", null), },
+                               ConditionalPermissionInfo.ALLOW));
+               Bundle jackrabbitDataBundle = findBundle("org.apache.jackrabbit.data");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { jackrabbitDataBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "*", "read,write") },
+                               ConditionalPermissionInfo.ALLOW));
+               Bundle jackrabbitCommonBundle = findBundle("org.apache.jackrabbit.jcr.commons");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { jackrabbitCommonBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(AuthPermission.class.getName(), "getSubject", null),
+                                               new PermissionInfo(AuthPermission.class.getName(), "createLoginContext.Jackrabbit", null), },
+                               ConditionalPermissionInfo.ALLOW));
+
+               Bundle jackrabbitExtBundle = findBundle("org.argeo.ext.jackrabbit");
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { jackrabbitExtBundle.getLocation() }) },
+                                               new PermissionInfo[] { new PermissionInfo(AuthPermission.class.getName(), "*", "*"), },
+                                               ConditionalPermissionInfo.ALLOW));
+
+               // Tika
+               Bundle tikaCoreBundle = findBundle("org.apache.tika.core");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { tikaCoreBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "*", "read,write"),
+                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*") },
+                               ConditionalPermissionInfo.ALLOW));
+               Bundle luceneBundle = findBundle("org.apache.lucene");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { luceneBundle.getLocation() }) },
+                               new PermissionInfo[] {
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "*", "read"),
+                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*") },
+                               ConditionalPermissionInfo.ALLOW));
+
+               // COMMIT
+               update.commit();
+       }
+
+       /** @return bundle location */
+       default String locate(Class<?> clzz) {
+               return FrameworkUtil.getBundle(clzz).getLocation();
+       }
+
+       /** Can be null */
+       default Bundle findBundle(String symbolicName) {
+               for (Bundle b : bc.getBundles())
+                       if (b.getSymbolicName().equals(symbolicName))
+                               return b;
+               return null;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/.gitignore b/org.argeo.cms/src/org/argeo/cms/internal/runtime/.gitignore
new file mode 100644 (file)
index 0000000..50e1322
--- /dev/null
@@ -0,0 +1 @@
+/*.log
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java
new file mode 100644 (file)
index 0000000..7ce2e8b
--- /dev/null
@@ -0,0 +1,201 @@
+package org.argeo.cms.internal.runtime;
+
+import static java.util.Locale.ENGLISH;
+
+import java.lang.management.ManagementFactory;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.internal.osgi.NodeUserAdmin;
+import org.ietf.jgss.GSSCredential;
+import org.osgi.service.useradmin.UserAdmin;
+
+public class CmsContextImpl implements CmsContext {
+       private final CmsLog log = CmsLog.getLog(getClass());
+//     private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+//     private EgoRepository egoRepository;
+       private static CompletableFuture<CmsContextImpl> instance = new CompletableFuture<CmsContextImpl>();
+
+       private CmsState cmsState;
+       private CmsDeployment cmsDeployment;
+       private UserAdmin userAdmin;
+
+       // i18n
+       private Locale defaultLocale;
+       private List<Locale> locales = null;
+
+       private Long availableSince;
+
+//     public CmsContextImpl() {
+//             initTrackers();
+//     }
+
+       public void init() {
+               Object defaultLocaleValue = KernelUtils.getFrameworkProp(CmsConstants.I18N_DEFAULT_LOCALE);
+               defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString())
+                               : new Locale(ENGLISH.getLanguage());
+               locales = LocaleUtils.asLocaleList(KernelUtils.getFrameworkProp(CmsConstants.I18N_LOCALES));
+               // node repository
+//             new ServiceTracker<Repository, Repository>(bc, Repository.class, null) {
+//                     @Override
+//                     public Repository addingService(ServiceReference<Repository> reference) {
+//                             Object cn = reference.getProperty(NodeConstants.CN);
+//                             if (cn != null && cn.equals(NodeConstants.EGO_REPOSITORY)) {
+////                                   egoRepository = (EgoRepository) bc.getService(reference);
+//                                     if (log.isTraceEnabled())
+//                                             log.trace("Home repository is available");
+//                             }
+//                             return super.addingService(reference);
+//                     }
+//
+//                     @Override
+//                     public void removedService(ServiceReference<Repository> reference, Repository service) {
+//                             super.removedService(reference, service);
+////                           egoRepository = null;
+//                     }
+//
+//             }.open();
+
+               checkReadiness();
+
+               setInstance(this);
+       }
+
+       public void destroy() {
+               setInstance(null);
+       }
+
+       /**
+        * Checks whether the deployment is available according to expectations, and
+        * mark it as available.
+        */
+       private void checkReadiness() {
+               if (isAvailable())
+                       return;
+               if (cmsDeployment != null && userAdmin != null) {
+                       String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
+                       String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
+                       availableSince = System.currentTimeMillis();
+                       long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+                       String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
+                       log.info("## ARGEO CMS AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
+                       if (log.isDebugEnabled()) {
+                               log.debug("## state: " + state);
+                               if (data != null)
+                                       log.debug("## data: " + data);
+                       }
+                       long begin = cmsState.getAvailableSince();
+                       long initDuration = System.currentTimeMillis() - begin;
+                       if (log.isTraceEnabled())
+                               log.trace("Kernel initialization took " + initDuration + "ms");
+                       tributeToFreeSoftware(initDuration);
+               } else {
+                       throw new IllegalStateException("Deployment is not available");
+               }
+       }
+
+       final private void tributeToFreeSoftware(long initDuration) {
+               if (log.isTraceEnabled()) {
+                       long ms = initDuration / 100;
+                       log.trace("Spend " + ms + "ms" + " reflecting on the progress brought to mankind" + " by Free Software...");
+                       long beginNano = System.nanoTime();
+                       try {
+                               Thread.sleep(ms, 0);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
+                       long durationNano = System.nanoTime() - beginNano;
+                       final double M = 1000d * 1000d;
+                       double sleepAccuracy = ((double) durationNano) / (ms * M);
+                       log.trace("Sleep accuracy: " + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100)) + " %");
+               }
+       }
+
+       @Override
+       public void createWorkgroup(String dn) {
+//             if (egoRepository == null)
+//                     throw new CmsException("Ego repository is not available");
+//             // TODO add check that the group exists
+//             egoRepository.createWorkgroup(dn);
+               throw new UnsupportedOperationException();
+       }
+
+       public void setCmsDeployment(CmsDeployment cmsDeployment) {
+               this.cmsDeployment = cmsDeployment;
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+       public void setUserAdmin(UserAdmin userAdmin) {
+               this.userAdmin = userAdmin;
+       }
+
+       @Override
+       public Locale getDefaultLocale() {
+               return defaultLocale;
+       }
+
+       @Override
+       public List<Locale> getLocales() {
+               return locales;
+       }
+
+       @Override
+       public synchronized Long getAvailableSince() {
+               return availableSince;
+       }
+
+       public synchronized boolean isAvailable() {
+               return availableSince != null;
+       }
+
+       /*
+        * STATIC
+        */
+
+       public synchronized static CmsContext getCmsContext() {
+               return getInstance();
+       }
+
+       /** Required by USER login module. */
+       public synchronized static UserAdmin getUserAdmin() {
+               return getInstance().userAdmin;
+       }
+
+       /** Required by SPNEGO login module. */
+       @Deprecated
+       public synchronized static GSSCredential getAcceptorCredentials() {
+               // FIXME find a cleaner way
+               return ((NodeUserAdmin) getInstance().userAdmin).getAcceptorCredentials();
+       }
+
+       private synchronized static void setInstance(CmsContextImpl cmsContextImpl) {
+               if (cmsContextImpl != null) {
+                       if (instance.isDone())
+                               throw new IllegalStateException("CMS Context is already set");
+                       instance.complete(cmsContextImpl);
+               } else {
+                       instance = new CompletableFuture<CmsContextImpl>();
+               }
+       }
+
+       private synchronized static CmsContextImpl getInstance() {
+               try {
+                       return instance.get();
+               } catch (InterruptedException | ExecutionException e) {
+                       throw new IllegalStateException("Cannot retrieve CMS Context", e);
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java
new file mode 100644 (file)
index 0000000..83f688a
--- /dev/null
@@ -0,0 +1,207 @@
+package org.argeo.cms.internal.runtime;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Dictionary;
+
+import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.internal.osgi.DeployConfig;
+import org.eclipse.equinox.http.jetty.JettyConfigurator;
+import org.osgi.service.http.HttpService;
+
+/** Implementation of a CMS deployment. */
+public class CmsDeploymentImpl implements CmsDeployment {
+       private final CmsLog log = CmsLog.getLog(getClass());
+//     private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+//     private Long availableSince;
+
+       // Readiness
+//     private boolean nodeAvailable = false;
+//     private boolean userAdminAvailable = false;
+       private boolean httpExpected = false;
+//     private boolean httpAvailable = false;
+       private HttpService httpService;
+
+       private CmsState cmsState;
+       private DeployConfig deployConfig;
+
+       public CmsDeploymentImpl() {
+//             ServiceReference<NodeState> nodeStateSr = bc.getServiceReference(NodeState.class);
+//             if (nodeStateSr == null)
+//                     throw new CmsException("No node state available");
+
+//             NodeState nodeState = bc.getService(nodeStateSr);
+//             cleanState = nodeState.isClean();
+
+//             nodeHttp = new NodeHttp();
+               initTrackers();
+       }
+
+       private void initTrackers() {
+//             ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
+//
+//                     @Override
+//                     public HttpService addingService(ServiceReference<HttpService> sr) {
+//                             httpAvailable = true;
+//                             Object httpPort = sr.getProperty("http.port");
+//                             Object httpsPort = sr.getProperty("https.port");
+//                             log.info(httpPortsMsg(httpPort, httpsPort));
+//                             checkReadiness();
+//                             return super.addingService(sr);
+//                     }
+//             };
+//             // httpSt.open();
+//             KernelUtils.asyncOpen(httpSt);
+
+//             ServiceTracker<?, ?> userAdminSt = new ServiceTracker<UserAdmin, UserAdmin>(bc, UserAdmin.class, null) {
+//                     @Override
+//                     public UserAdmin addingService(ServiceReference<UserAdmin> reference) {
+//                             UserAdmin userAdmin = super.addingService(reference);
+//                             addStandardSystemRoles(userAdmin);
+//                             userAdminAvailable = true;
+//                             checkReadiness();
+//                             return userAdmin;
+//                     }
+//             };
+//             // userAdminSt.open();
+//             KernelUtils.asyncOpen(userAdminSt);
+
+//             ServiceTracker<?, ?> confAdminSt = new ServiceTracker<ConfigurationAdmin, ConfigurationAdmin>(bc,
+//                             ConfigurationAdmin.class, null) {
+//                     @Override
+//                     public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> reference) {
+//                             ConfigurationAdmin configurationAdmin = bc.getService(reference);
+////                           boolean isClean;
+////                           try {
+////                                   Configuration[] confs = configurationAdmin
+////                                                   .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
+////                                   isClean = confs == null || confs.length == 0;
+////                           } catch (Exception e) {
+////                                   throw new IllegalStateException("Cannot analyse clean state", e);
+////                           }
+//                             deployConfig = new DeployConfig(configurationAdmin, isClean);
+//                             Activator.registerService(CmsDeployment.class, CmsDeploymentImpl.this, null);
+////                           JcrInitUtils.addToDeployment(CmsDeployment.this);
+//                             httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
+//                             try {
+//                                     Configuration[] configs = configurationAdmin
+//                                                     .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
+//
+//                                     boolean hasDomain = false;
+//                                     for (Configuration config : configs) {
+//                                             Object realm = config.getProperties().get(UserAdminConf.realm.name());
+//                                             if (realm != null) {
+//                                                     log.debug("Found realm: " + realm);
+//                                                     hasDomain = true;
+//                                             }
+//                                     }
+//                                     if (hasDomain) {
+//                                             loadIpaJaasConfiguration();
+//                                     }
+//                             } catch (Exception e) {
+//                                     throw new IllegalStateException("Cannot initialize config", e);
+//                             }
+//                             return super.addingService(reference);
+//                     }
+//             };
+//             // confAdminSt.open();
+//             KernelUtils.asyncOpen(confAdminSt);
+       }
+
+       public void init() {
+               httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
+               if (deployConfig.hasDomain()) {
+                       loadIpaJaasConfiguration();
+               }
+
+//             while (!isHttpAvailableOrNotExpected()) {
+//                     try {
+//                             Thread.sleep(100);
+//                     } catch (InterruptedException e) {
+//                             log.error("Interrupted while waiting for http");
+//                     }
+//             }
+       }
+
+       public void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
+               deployConfig.putFactoryDeployConfig(factoryPid, props);
+               deployConfig.save();
+               try {
+                       deployConfig.loadConfigs();
+               } catch (IOException e) {
+                       throw new IllegalStateException(e);
+               }
+       }
+
+       public Dictionary<String, Object> getProps(String factoryPid, String cn) {
+               return deployConfig.getProps(factoryPid, cn);
+       }
+
+//     private void addStandardSystemRoles(UserAdmin userAdmin) {
+//             // we assume UserTransaction is already available (TODO make it more robust)
+//             WorkTransaction userTransaction = bc.getService(bc.getServiceReference(WorkTransaction.class));
+//             try {
+//                     userTransaction.begin();
+//                     Role adminRole = userAdmin.getRole(CmsConstants.ROLE_ADMIN);
+//                     if (adminRole == null) {
+//                             adminRole = userAdmin.createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
+//                     }
+//                     if (userAdmin.getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
+//                             Group userAdminRole = (Group) userAdmin.createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
+//                             userAdminRole.addMember(adminRole);
+//                     }
+//                     userTransaction.commit();
+//             } catch (Exception e) {
+//                     try {
+//                             userTransaction.rollback();
+//                     } catch (Exception e1) {
+//                             // silent
+//                     }
+//                     throw new IllegalStateException("Cannot add standard system roles", e);
+//             }
+//     }
+
+       public boolean isHttpAvailableOrNotExpected() {
+               return (httpExpected ? httpService != null : true);
+       }
+
+       private void loadIpaJaasConfiguration() {
+               if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
+                       String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
+                       URL url = getClass().getClassLoader().getResource(jaasConfig);
+                       KernelUtils.setJaasConfiguration(url);
+                       log.debug("Set IPA JAAS configuration.");
+               }
+       }
+
+       public void destroy() {
+//             if (nodeHttp != null)
+//                     nodeHttp.destroy();
+
+               try {
+                       JettyConfigurator.stopServer(KernelConstants.DEFAULT_JETTY_SERVER);
+               } catch (Exception e) {
+                       log.error("Cannot stop default Jetty server.", e);
+               }
+
+               if (deployConfig != null) {
+                       new Thread(() -> deployConfig.save(), "Save Argeo Deploy Config").start();
+               }
+       }
+
+       public void setDeployConfig(DeployConfig deployConfig) {
+               this.deployConfig = deployConfig;
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+       public void setHttpService(HttpService httpService) {
+               this.httpService = httpService;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java
new file mode 100644 (file)
index 0000000..7b47b52
--- /dev/null
@@ -0,0 +1,241 @@
+package org.argeo.cms.internal.runtime;
+
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.security.auth.login.Configuration;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.auth.ident.IdentClient;
+import org.argeo.cms.internal.osgi.CmsShutdown;
+import org.osgi.framework.Constants;
+
+/**
+ * Implementation of a {@link CmsState}, initialising the required services.
+ */
+public class CmsStateImpl implements CmsState {
+       private final static CmsLog log = CmsLog.getLog(CmsStateImpl.class);
+//     private final BundleContext bc = FrameworkUtil.getBundle(CmsState.class).getBundleContext();
+
+//     private static CmsStateImpl instance;
+
+//     private ExecutorService internalExecutorService;
+
+       // REFERENCES
+       private Long availableSince;
+
+
+//     private ThreadGroup threadGroup = new ThreadGroup("CMS");
+       private List<Runnable> stopHooks = new ArrayList<>();
+
+       private String stateUuid;
+//     private final boolean cleanState;
+       private String hostname;
+
+       public void init() {
+//             instance = this;
+
+               Runtime.getRuntime().addShutdownHook(new CmsShutdown());
+//             this.internalExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+
+               try {
+                       initSecurity();
+//                     initArgeoLogger();
+//                     initNode();
+
+                       if (log.isTraceEnabled())
+                               log.trace("CMS State started");
+               } catch (Throwable e) {
+                       log.error("## FATAL: CMS activator failed", e);
+               }
+
+               this.stateUuid = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_UUID);
+//             this.cleanState = stateUuid.equals(frameworkUuid);
+               try {
+                       this.hostname = InetAddress.getLocalHost().getHostName();
+               } catch (UnknownHostException e) {
+                       log.error("Cannot set hostname: " + e);
+               }
+
+               availableSince = System.currentTimeMillis();
+               if (log.isDebugEnabled())
+                       // log.debug("## CMS starting... stateUuid=" + this.stateUuid + (cleanState ? "
+                       // (clean state) " : " "));
+                       log.debug("## CMS starting... (" + stateUuid + ")");
+
+//             initI18n();
+//             initServices();
+
+       }
+
+       private void initSecurity() {
+               if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
+                       String jaasConfig = KernelConstants.JAAS_CONFIG;
+                       URL url = getClass().getResource(jaasConfig);
+                       // System.setProperty(KernelConstants.JAAS_CONFIG_PROP,
+                       // url.toExternalForm());
+                       KernelUtils.setJaasConfiguration(url);
+               }
+               // explicitly load JAAS configuration
+               Configuration.getConfiguration();
+       }
+
+//     private void initI18n() {
+//             Object defaultLocaleValue = KernelUtils.getFrameworkProp(CmsConstants.I18N_DEFAULT_LOCALE);
+//             defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString())
+//                             : new Locale(ENGLISH.getLanguage());
+//             locales = LocaleUtils.asLocaleList(KernelUtils.getFrameworkProp(CmsConstants.I18N_LOCALES));
+//     }
+
+       private void initServices() {
+               // JTA
+//             String tmType = KernelUtils.getFrameworkProp(CmsConstants.TRANSACTION_MANAGER,
+//                             CmsConstants.TRANSACTION_MANAGER_SIMPLE);
+//             if (CmsConstants.TRANSACTION_MANAGER_SIMPLE.equals(tmType)) {
+//                     initSimpleTransactionManager();
+//             } else if (CmsConstants.TRANSACTION_MANAGER_BITRONIX.equals(tmType)) {
+////                   initBitronixTransactionManager();
+//                     throw new UnsupportedOperationException(
+//                                     "Bitronix is not supported anymore, but could be again if there is enough interest.");
+//             } else {
+//                     throw new IllegalArgumentException("Usupported transaction manager type " + tmType);
+//             }
+
+               // POI
+//             POIXMLTypeLoader.setClassLoader(CTConnection.class.getClassLoader());
+
+               // Tika
+//             OpenDocumentParser odfParser = new OpenDocumentParser();
+//             bc.registerService(Parser.class, odfParser, new Hashtable());
+//             PDFParser pdfParser = new PDFParser();
+//             bc.registerService(Parser.class, pdfParser, new Hashtable());
+//             OOXMLParser ooxmlParser = new OOXMLParser();
+//             bc.registerService(Parser.class, ooxmlParser, new Hashtable());
+//             TesseractOCRParser ocrParser = new TesseractOCRParser();
+//             ocrParser.setLanguage("ara");
+//             bc.registerService(Parser.class, ocrParser, new Hashtable());
+
+//             // JCR
+//             RepositoryServiceFactory repositoryServiceFactory = new RepositoryServiceFactory();
+//             stopHooks.add(() -> repositoryServiceFactory.shutdown());
+//             Activator.registerService(ManagedServiceFactory.class, repositoryServiceFactory,
+//                             LangUtils.dict(Constants.SERVICE_PID, NodeConstants.NODE_REPOS_FACTORY_PID));
+//
+//             NodeRepositoryFactory repositoryFactory = new NodeRepositoryFactory();
+//             Activator.registerService(RepositoryFactory.class, repositoryFactory, null);
+
+               // Security
+//             NodeUserAdmin userAdmin = new NodeUserAdmin(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
+//             stopHooks.add(() -> userAdmin.destroy());
+//             Activator.registerService(ManagedServiceFactory.class, userAdmin,
+//                             LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_USER_ADMIN_PID));
+
+       }
+
+//     private void initSimpleTransactionManager() {
+//             SimpleTransactionManager transactionManager = new SimpleTransactionManager();
+//             Activator.registerService(WorkControl.class, transactionManager, null);
+//             Activator.registerService(WorkTransaction.class, transactionManager, null);
+////           Activator.registerService(TransactionManager.class, transactionManager, null);
+////           Activator.registerService(UserTransaction.class, transactionManager, null);
+//             // TODO TransactionSynchronizationRegistry
+//     }
+
+//     private void initBitronixTransactionManager() {
+//             // TODO manage it in a managed service, as startup could be long
+//             ServiceReference<TransactionManager> existingTm = bc.getServiceReference(TransactionManager.class);
+//             if (existingTm != null) {
+//                     if (log.isDebugEnabled())
+//                             log.debug("Using provided transaction manager " + existingTm);
+//                     return;
+//             }
+//
+//             if (!TransactionManagerServices.isTransactionManagerRunning()) {
+//                     bitronix.tm.Configuration tmConf = TransactionManagerServices.getConfiguration();
+//                     tmConf.setServerId(UUID.randomUUID().toString());
+//
+//                     Bundle bitronixBundle = FrameworkUtil.getBundle(bitronix.tm.Configuration.class);
+//                     File tmBaseDir = bitronixBundle.getDataFile(KernelConstants.DIR_TRANSACTIONS);
+//                     File tmDir1 = new File(tmBaseDir, "btm1");
+//                     tmDir1.mkdirs();
+//                     tmConf.setLogPart1Filename(new File(tmDir1, tmDir1.getName() + ".tlog").getAbsolutePath());
+//                     File tmDir2 = new File(tmBaseDir, "btm2");
+//                     tmDir2.mkdirs();
+//                     tmConf.setLogPart2Filename(new File(tmDir2, tmDir2.getName() + ".tlog").getAbsolutePath());
+//             }
+//             BitronixTransactionManager transactionManager = getTransactionManager();
+//             stopHooks.add(() -> transactionManager.shutdown());
+//             BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry = getTransactionSynchronizationRegistry();
+//             // register
+//             bc.registerService(TransactionManager.class, transactionManager, null);
+//             bc.registerService(UserTransaction.class, transactionManager, null);
+//             bc.registerService(TransactionSynchronizationRegistry.class, transactionSynchronizationRegistry, null);
+//             if (log.isDebugEnabled())
+//                     log.debug("Initialised default Bitronix transaction manager");
+//     }
+
+       public void destroy() {
+               if (log.isDebugEnabled())
+                       log.debug("CMS stopping...  (" + this.stateUuid + ")");
+
+               // In a different thread in order to avoid interruptions
+               Thread stopHookThread = new Thread(() -> applyStopHooks(), "Apply Argeo Stop Hooks");
+               stopHookThread.start();
+               try {
+                       stopHookThread.join(10 * 60 * 1000);
+               } catch (InterruptedException e) {
+                       // silent
+               }
+
+//             internalExecutorService.shutdown();
+
+               long duration = ((System.currentTimeMillis() - availableSince) / 1000) / 60;
+               log.info("## ARGEO CMS STOPPED after " + (duration / 60) + "h " + (duration % 60) + "min uptime ##");
+       }
+
+       /** Apply shutdown hoos in reverse order. */
+       private void applyStopHooks() {
+               for (int i = stopHooks.size() - 1; i >= 0; i--) {
+                       try {
+                               stopHooks.get(i).run();
+                       } catch (Exception e) {
+                               log.error("Could not run shutdown hook #" + i);
+                       }
+               }
+               // Clean hanging Gogo shell thread
+               new GogoShellKiller().start();
+
+//             instance = null;
+       }
+
+//     @Override
+//     public boolean isClean() {
+//             return cleanState;
+//     }
+
+       @Override
+       public Long getAvailableSince() {
+               return availableSince;
+       }
+
+       /*
+        * ACCESSORS
+        */
+       public String getHostname() {
+               return hostname;
+       }
+
+       /*
+        * STATIC
+        */
+       public static IdentClient getIdentClient(String remoteAddr) {
+               if (!IdentClient.isDefaultAuthdPassphraseFileAvailable())
+                       return null;
+               // TODO make passphrase more configurable
+               return new IdentClient(remoteAddr);
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/GogoShellKiller.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/GogoShellKiller.java
new file mode 100644 (file)
index 0000000..56df43a
--- /dev/null
@@ -0,0 +1,64 @@
+package org.argeo.cms.internal.runtime;
+
+/**
+ * Workaround for killing Gogo shell by system shutdown.
+ * 
+ * @see https://issues.apache.org/jira/browse/FELIX-4208
+ */
+class GogoShellKiller extends Thread {
+
+       public GogoShellKiller() {
+               super("Gogo Shell Killer");
+               setDaemon(true);
+       }
+
+       @Override
+       public void run() {
+               ThreadGroup rootTg = getRootThreadGroup(null);
+               Thread gogoShellThread = findGogoShellThread(rootTg);
+               if (gogoShellThread == null)
+                       return;
+               while (getNonDaemonCount(rootTg) > 2) {
+                       try {
+                               Thread.sleep(100);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
+               }
+               gogoShellThread = findGogoShellThread(rootTg);
+               if (gogoShellThread == null)
+                       return;
+               // No non-deamon threads left, forcibly halting the VM
+               Runtime.getRuntime().halt(0);
+       }
+
+       private ThreadGroup getRootThreadGroup(ThreadGroup tg) {
+               if (tg == null)
+                       tg = Thread.currentThread().getThreadGroup();
+               if (tg.getParent() == null)
+                       return tg;
+               else
+                       return getRootThreadGroup(tg.getParent());
+       }
+
+       private int getNonDaemonCount(ThreadGroup rootThreadGroup) {
+               Thread[] threads = new Thread[rootThreadGroup.activeCount()];
+               rootThreadGroup.enumerate(threads);
+               int nonDameonCount = 0;
+               for (Thread t : threads)
+                       if (t != null && !t.isDaemon())
+                               nonDameonCount++;
+               return nonDameonCount;
+       }
+
+       private Thread findGogoShellThread(ThreadGroup rootThreadGroup) {
+               Thread[] threads = new Thread[rootThreadGroup.activeCount()];
+               rootThreadGroup.enumerate(threads, true);
+               for (Thread thread : threads) {
+                       if (thread.getName().equals("pipe-gosh --login --noshutdown"))
+                               return thread;
+               }
+               return null;
+       }
+
+}
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java
new file mode 100644 (file)
index 0000000..70ea9ec
--- /dev/null
@@ -0,0 +1,287 @@
+package org.argeo.cms.internal.runtime;
+
+import static org.argeo.cms.internal.runtime.KernelUtils.getFrameworkProp;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.Reader;
+import java.net.InetAddress;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.commons.io.FileUtils;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.http.InternalHttpConstants;
+import org.argeo.osgi.useradmin.UserAdminConf;
+
+/**
+ * Interprets framework properties in order to generate the initial deploy
+ * configuration.
+ */
+public class InitUtils {
+       private final static CmsLog log = CmsLog.getLog(InitUtils.class);
+
+       /** Override the provided config with the framework properties */
+       public static Dictionary<String, Object> getHttpServerConfig(Dictionary<String, Object> provided) {
+               String httpPort = getFrameworkProp("org.osgi.service.http.port");
+               String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
+               /// TODO make it more generic
+               String httpHost = getFrameworkProp(
+                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTP_HOST);
+               String httpsHost = getFrameworkProp(
+                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTPS_HOST);
+               String webSocketEnabled = getFrameworkProp(
+                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.WEBSOCKET_ENABLED);
+
+               final Hashtable<String, Object> props = new Hashtable<String, Object>();
+               // try {
+               if (httpPort != null || httpsPort != null) {
+                       boolean httpEnabled = httpPort != null;
+                       props.put(InternalHttpConstants.HTTP_ENABLED, httpEnabled);
+                       boolean httpsEnabled = httpsPort != null;
+                       props.put(InternalHttpConstants.HTTPS_ENABLED, httpsEnabled);
+
+                       if (httpEnabled) {
+                               props.put(InternalHttpConstants.HTTP_PORT, httpPort);
+                               if (httpHost != null)
+                                       props.put(InternalHttpConstants.HTTP_HOST, httpHost);
+                       }
+
+                       if (httpsEnabled) {
+                               props.put(InternalHttpConstants.HTTPS_PORT, httpsPort);
+                               if (httpsHost != null)
+                                       props.put(InternalHttpConstants.HTTPS_HOST, httpsHost);
+
+                               // server certificate
+                               Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH);
+                               Path pemKeyPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_KEY_PATH);
+                               Path pemCertPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_CERT_PATH);
+                               String keyStorePasswordStr = getFrameworkProp(
+                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD);
+                               char[] keyStorePassword;
+                               if (keyStorePasswordStr == null)
+                                       keyStorePassword = "changeit".toCharArray();
+                               else
+                                       keyStorePassword = keyStorePasswordStr.toCharArray();
+
+                               // if PEM files both exists, update the PKCS12 file
+                               if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) {
+                                       // TODO check certificate update time? monitor changes?
+                                       KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
+                                       try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII);
+                                                       Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) {
+                                               PkiUtils.loadPem(keyStore, key, keyStorePassword, cert);
+                                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
+                                               if (log.isDebugEnabled())
+                                                       log.debug("PEM certificate stored in " + keyStorePath);
+                                       } catch (IOException e) {
+                                               log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e);
+                                       }
+                               }
+
+                               if (!Files.exists(keyStorePath))
+                                       createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
+                               props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12);
+                               props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString());
+                               props.put(InternalHttpConstants.SSL_PASSWORD, new String(keyStorePassword));
+
+//                             props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11");
+//                             props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb");
+//                             props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword);
+
+                               // client certificate authentication
+                               String wantClientAuth = getFrameworkProp(
+                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_WANTCLIENTAUTH);
+                               if (wantClientAuth != null)
+                                       props.put(InternalHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
+                               String needClientAuth = getFrameworkProp(
+                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_NEEDCLIENTAUTH);
+                               if (needClientAuth != null)
+                                       props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
+                       }
+
+                       // web socket
+                       if (webSocketEnabled != null && webSocketEnabled.equals("true"))
+                               props.put(InternalHttpConstants.WEBSOCKET_ENABLED, true);
+
+                       props.put(CmsConstants.CN, CmsConstants.DEFAULT);
+               }
+               return props;
+       }
+
+       public static List<Dictionary<String, Object>> getUserDirectoryConfigs() {
+               List<Dictionary<String, Object>> res = new ArrayList<>();
+               File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile();
+               List<String> uris = new ArrayList<>();
+
+               // node roles
+               String nodeRolesUri = getFrameworkProp(CmsConstants.ROLES_URI);
+               String baseNodeRoleDn = CmsConstants.ROLES_BASEDN;
+               if (nodeRolesUri == null) {
+                       nodeRolesUri = baseNodeRoleDn + ".ldif";
+                       File nodeRolesFile = new File(nodeBaseDir, nodeRolesUri);
+                       if (!nodeRolesFile.exists())
+                               try {
+                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeRoleDn + ".ldif"),
+                                                       nodeRolesFile);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot copy demo resource", e);
+                               }
+                       // nodeRolesUri = nodeRolesFile.toURI().toString();
+               }
+               uris.add(nodeRolesUri);
+
+               // node tokens
+               String nodeTokensUri = getFrameworkProp(CmsConstants.TOKENS_URI);
+               String baseNodeTokensDn = CmsConstants.TOKENS_BASEDN;
+               if (nodeTokensUri == null) {
+                       nodeTokensUri = baseNodeTokensDn + ".ldif";
+                       File nodeTokensFile = new File(nodeBaseDir, nodeTokensUri);
+                       if (!nodeTokensFile.exists())
+                               try {
+                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeTokensDn + ".ldif"),
+                                                       nodeTokensFile);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot copy demo resource", e);
+                               }
+                       // nodeRolesUri = nodeRolesFile.toURI().toString();
+               }
+               uris.add(nodeTokensUri);
+
+               // Business roles
+               String userAdminUris = getFrameworkProp(CmsConstants.USERADMIN_URIS);
+               if (userAdminUris == null) {
+                       String demoBaseDn = "dc=example,dc=com";
+                       userAdminUris = demoBaseDn + ".ldif";
+                       File businessRolesFile = new File(nodeBaseDir, userAdminUris);
+                       File systemRolesFile = new File(nodeBaseDir, "ou=roles,ou=node.ldif");
+                       if (!businessRolesFile.exists())
+                               try {
+                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(demoBaseDn + ".ldif"),
+                                                       businessRolesFile);
+                                       if (!systemRolesFile.exists())
+                                               FileUtils.copyInputStreamToFile(
+                                                               InitUtils.class.getResourceAsStream("example-ou=roles,ou=node.ldif"), systemRolesFile);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot copy demo resources", e);
+                               }
+                       // userAdminUris = businessRolesFile.toURI().toString();
+                       log.warn("## DEV Using dummy base DN " + demoBaseDn);
+                       // TODO downgrade security level
+               }
+               for (String userAdminUri : userAdminUris.split(" "))
+                       uris.add(userAdminUri);
+
+               // Interprets URIs
+               for (String uri : uris) {
+                       URI u;
+                       try {
+                               u = new URI(uri);
+                               if (u.getPath() == null)
+                                       throw new IllegalArgumentException(
+                                                       "URI " + uri + " must have a path in order to determine base DN");
+                               if (u.getScheme() == null) {
+                                       if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
+                                               u = new File(uri).getCanonicalFile().toURI();
+                                       else if (!uri.contains("/")) {
+                                               // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri);
+                                               u = new URI(uri);
+                                       } else
+                                               throw new IllegalArgumentException("Cannot interpret " + uri + " as an uri");
+                               } else if (u.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
+                                       u = new File(u).getCanonicalFile().toURI();
+                               }
+                       } catch (Exception e) {
+                               throw new RuntimeException("Cannot interpret " + uri + " as an uri", e);
+                       }
+                       Dictionary<String, Object> properties = UserAdminConf.uriAsProperties(u.toString());
+                       res.add(properties);
+               }
+
+               return res;
+       }
+
+       /**
+        * Called before node initialisation, in order populate OSGi instance are with
+        * some files (typically LDIF, etc).
+        */
+       public static void prepareFirstInitInstanceArea() {
+               String nodeInits = getFrameworkProp(CmsConstants.NODE_INIT);
+               if (nodeInits == null)
+                       nodeInits = "../../init";
+
+               for (String nodeInit : nodeInits.split(",")) {
+
+                       if (nodeInit.startsWith("http")) {
+                               // TODO reconnect it
+                               // registerRemoteInit(nodeInit);
+                       } else {
+
+                               // TODO use java.nio.file
+                               File initDir;
+                               if (nodeInit.startsWith("."))
+                                       initDir = KernelUtils.getExecutionDir(nodeInit);
+                               else
+                                       initDir = new File(nodeInit);
+                               // TODO also uncompress archives
+                               if (initDir.exists())
+                                       try {
+                                               FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstanceDir(), new FileFilter() {
+
+                                                       @Override
+                                                       public boolean accept(File pathname) {
+                                                               if (pathname.getName().equals(".svn") || pathname.getName().equals(".git"))
+                                                                       return false;
+                                                               return true;
+                                                       }
+                                               });
+                                               log.info("CMS initialized from " + initDir.getCanonicalPath());
+                                       } catch (IOException e) {
+                                               throw new RuntimeException("Cannot initialize from " + initDir, e);
+                                       }
+                       }
+               }
+       }
+
+       private static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) {
+               // for (Provider provider : Security.getProviders())
+               // System.out.println(provider.getName());
+//             File keyStoreFile = keyStorePath.toFile();
+               char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length);
+               if (!Files.exists(keyStorePath)) {
+                       try {
+                               Files.createDirectories(keyStorePath.getParent());
+                               KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
+                               PkiUtils.generateSelfSignedCertificate(keyStore,
+                                               new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"),
+                                               1024, keyPwd);
+                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
+                               if (log.isDebugEnabled())
+                                       log.debug("Created self-signed unsecure keystore " + keyStorePath);
+                       } catch (Exception e) {
+                               try {
+                                       if (Files.size(keyStorePath) == 0)
+                                               Files.delete(keyStorePath);
+                               } catch (IOException e1) {
+                                       // silent
+                               }
+                               log.error("Cannot create keystore " + keyStorePath, e);
+                       }
+               } else {
+                       throw new IllegalStateException("Keystore " + keyStorePath + " already exists");
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java
new file mode 100644 (file)
index 0000000..7c397de
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.cms.internal.runtime;
+
+import org.argeo.api.cms.CmsConstants;
+
+/** Internal CMS constants. */
+public interface KernelConstants {
+       // Directories
+       String DIR_NODE = "node";
+       String DIR_REPOS = "repos";
+       String DIR_INDEXES = "indexes";
+       String DIR_TRANSACTIONS = "transactions";
+
+       // Files
+       String DEPLOY_CONFIG_PATH = DIR_NODE + '/' + CmsConstants.DEPLOY_BASEDN + ".ldif";
+       String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".p12";
+       String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".key";
+       String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".crt";
+       String NODE_KEY_TAB_PATH = DIR_NODE + "/krb5.keytab";
+
+       // Security
+       String JAAS_CONFIG = "/org/argeo/cms/internal/kernel/jaas.cfg";
+       String JAAS_CONFIG_IPA = "/org/argeo/cms/internal/kernel/jaas-ipa.cfg";
+
+       // Java
+       String JAAS_CONFIG_PROP = "java.security.auth.login.config";
+
+       // DEFAULTS JCR PATH
+       String DEFAULT_HOME_BASE_PATH = "/home";
+       String DEFAULT_USERS_BASE_PATH = "/users";
+       String DEFAULT_GROUPS_BASE_PATH = "/groups";
+       
+       // KERBEROS
+       String DEFAULT_KERBEROS_SERVICE = "HTTP";
+
+       // HTTP client
+       String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility";
+
+       // RWT / RAP
+       // String PATH_WORKBENCH = "/ui";
+       // String PATH_WORKBENCH_PUBLIC = PATH_WORKBENCH + "/public";
+
+       String JETTY_FACTORY_PID = "org.eclipse.equinox.http.jetty.config";
+       String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern";
+       // default Jetty server configured via JettyConfigurator
+       String DEFAULT_JETTY_SERVER = "default";
+       String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
+
+       // avoid dependencies
+       String CONTEXT_NAME_PROP = "contextName";
+       String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
+       String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java
new file mode 100644 (file)
index 0000000..afcb9ff
--- /dev/null
@@ -0,0 +1,247 @@
+package org.argeo.cms.internal.runtime;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.URIParameter;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.osgi.CmsActivator;
+
+/** Package utilities */
+public class KernelUtils implements KernelConstants {
+       final static String OSGI_INSTANCE_AREA = "osgi.instance.area";
+       final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
+
+       static void setJaasConfiguration(URL jaasConfigurationUrl) {
+               try {
+                       URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
+                       javax.security.auth.login.Configuration jaasConfiguration = javax.security.auth.login.Configuration
+                                       .getInstance("JavaLoginConfig", uriParameter);
+                       javax.security.auth.login.Configuration.setConfiguration(jaasConfiguration);
+               } catch (Exception e) {
+                       throw new IllegalArgumentException("Cannot set configuration " + jaasConfigurationUrl, e);
+               }
+       }
+
+       static Dictionary<String, ?> asDictionary(Properties props) {
+               Hashtable<String, Object> hashtable = new Hashtable<String, Object>();
+               for (Object key : props.keySet()) {
+                       hashtable.put(key.toString(), props.get(key));
+               }
+               return hashtable;
+       }
+
+       static Dictionary<String, ?> asDictionary(ClassLoader cl, String resource) {
+               Properties props = new Properties();
+               try {
+                       props.load(cl.getResourceAsStream(resource));
+               } catch (IOException e) {
+                       throw new IllegalArgumentException("Cannot load " + resource + " from classpath", e);
+               }
+               return asDictionary(props);
+       }
+
+       static File getExecutionDir(String relativePath) {
+               File executionDir = new File(getFrameworkProp("user.dir"));
+               if (relativePath == null)
+                       return executionDir;
+               try {
+                       return new File(executionDir, relativePath).getCanonicalFile();
+               } catch (IOException e) {
+                       throw new IllegalArgumentException("Cannot get canonical file", e);
+               }
+       }
+
+       static File getOsgiInstanceDir() {
+               return new File(CmsActivator.getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length()))
+                               .getAbsoluteFile();
+       }
+
+       public static Path getOsgiInstancePath(String relativePath) {
+               return Paths.get(getOsgiInstanceUri(relativePath));
+       }
+
+       public static URI getOsgiInstanceUri(String relativePath) {
+               String osgiInstanceBaseUri = getFrameworkProp(OSGI_INSTANCE_AREA);
+               if (osgiInstanceBaseUri != null)
+                       return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : ""));
+               else
+                       return Paths.get(System.getProperty("user.dir")).toUri();
+       }
+
+       static File getOsgiConfigurationFile(String relativePath) {
+               try {
+                       return new File(new URI(CmsActivator.getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath))
+                                       .getCanonicalFile();
+               } catch (Exception e) {
+                       throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e);
+               }
+       }
+
+       static String getFrameworkProp(String key, String def) {
+               String value;
+               if (CmsActivator.getBundleContext() != null)
+                       value = CmsActivator.getBundleContext().getProperty(key);
+               else
+                       value = System.getProperty(key);
+               if (value == null)
+                       return def;
+               return value;
+       }
+
+       public static String getFrameworkProp(String key) {
+               return getFrameworkProp(key, null);
+       }
+
+       // Security
+       // static Subject anonymousLogin() {
+       // Subject subject = new Subject();
+       // LoginContext lc;
+       // try {
+       // lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject);
+       // lc.login();
+       // return subject;
+       // } catch (LoginException e) {
+       // throw new CmsException("Cannot login as anonymous", e);
+       // }
+       // }
+
+       static void logFrameworkProperties(CmsLog log) {
+               for (Object sysProp : new TreeSet<Object>(System.getProperties().keySet())) {
+                       log.debug(sysProp + "=" + getFrameworkProp(sysProp.toString()));
+               }
+               // String[] keys = { Constants.FRAMEWORK_STORAGE,
+               // Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_OS_VERSION,
+               // Constants.FRAMEWORK_PROCESSOR, Constants.FRAMEWORK_SECURITY,
+               // Constants.FRAMEWORK_TRUST_REPOSITORIES,
+               // Constants.FRAMEWORK_WINDOWSYSTEM, Constants.FRAMEWORK_VENDOR,
+               // Constants.FRAMEWORK_VERSION, Constants.FRAMEWORK_STORAGE_CLEAN,
+               // Constants.FRAMEWORK_LANGUAGE, Constants.FRAMEWORK_UUID };
+               // for (String key : keys)
+               // log.debug(key + "=" + bc.getProperty(key));
+       }
+
+       static void printSystemProperties(PrintStream out) {
+               TreeMap<String, String> display = new TreeMap<>();
+               for (Object key : System.getProperties().keySet())
+                       display.put(key.toString(), System.getProperty(key.toString()));
+               for (String key : display.keySet())
+                       out.println(key + "=" + display.get(key));
+       }
+
+//     static Session openAdminSession(Repository repository) {
+//             return openAdminSession(repository, null);
+//     }
+//
+//     static Session openAdminSession(final Repository repository, final String workspaceName) {
+//             LoginContext loginContext = loginAsDataAdmin();
+//             return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
+//
+//                     @Override
+//                     public Session run() {
+//                             try {
+//                                     return repository.login(workspaceName);
+//                             } catch (RepositoryException e) {
+//                                     throw new IllegalStateException("Cannot open admin session", e);
+//                             } finally {
+//                                     try {
+//                                             loginContext.logout();
+//                                     } catch (LoginException e) {
+//                                             throw new IllegalStateException(e);
+//                                     }
+//                             }
+//                     }
+//
+//             });
+//     }
+//
+//     static LoginContext loginAsDataAdmin() {
+//             ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
+//             Thread.currentThread().setContextClassLoader(KernelUtils.class.getClassLoader());
+//             LoginContext loginContext;
+//             try {
+//                     loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_DATA_ADMIN);
+//                     loginContext.login();
+//             } catch (LoginException e1) {
+//                     throw new IllegalStateException("Could not login as data admin", e1);
+//             } finally {
+//                     Thread.currentThread().setContextClassLoader(currentCl);
+//             }
+//             return loginContext;
+//     }
+
+//     static void doAsDataAdmin(Runnable action) {
+//             LoginContext loginContext = loginAsDataAdmin();
+//             Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
+//
+//                     @Override
+//                     public Void run() {
+//                             try {
+//                                     action.run();
+//                                     return null;
+//                             } finally {
+//                                     try {
+//                                             loginContext.logout();
+//                                     } catch (LoginException e) {
+//                                             throw new IllegalStateException(e);
+//                                     }
+//                             }
+//                     }
+//
+//             });
+//     }
+
+//     public static void asyncOpen(ServiceTracker<?, ?> st) {
+//             Runnable run = new Runnable() {
+//
+//                     @Override
+//                     public void run() {
+//                             st.open();
+//                     }
+//             };
+//             Activator.getInternalExecutorService().execute(run);
+////           new Thread(run, "Open service tracker " + st).start();
+//     }
+
+//     static BundleContext getBundleContext() {
+//             return Activator.getBundleContext();
+//     }
+
+       static boolean asBoolean(String value) {
+               if (value == null)
+                       return false;
+               switch (value) {
+               case "true":
+                       return true;
+               case "false":
+                       return false;
+               default:
+                       throw new IllegalArgumentException("Unsupported value for boolean attribute : " + value);
+               }
+       }
+
+       private static URI safeUri(String uri) {
+               if (uri == null)
+                       throw new IllegalArgumentException("URI cannot be null");
+               try {
+                       return new URI(uri);
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException("Badly formatted URI " + uri, e);
+               }
+       }
+
+       private KernelUtils() {
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java
new file mode 100644 (file)
index 0000000..474a899
--- /dev/null
@@ -0,0 +1,259 @@
+package org.argeo.cms.internal.runtime;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.math.BigInteger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.pkcs.PKCSException;
+
+/**
+ * Utilities around private keys and certificate, mostly wrapping BouncyCastle
+ * implementations.
+ */
+class PkiUtils {
+       final static String PKCS12 = "PKCS12";
+
+       private final static String SECURITY_PROVIDER;
+       static {
+               Security.addProvider(new BouncyCastleProvider());
+               SECURITY_PROVIDER = "BC";
+       }
+
+       public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal,
+                       int keySize, char[] keyPassword) {
+               try {
+                       KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", SECURITY_PROVIDER);
+                       kpGen.initialize(keySize, new SecureRandom());
+                       KeyPair pair = kpGen.generateKeyPair();
+                       Date notBefore = new Date(System.currentTimeMillis() - 10000);
+                       Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000);
+                       BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
+                       X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore,
+                                       notAfter, x500Principal, pair.getPublic());
+                       ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER)
+                                       .build(pair.getPrivate());
+                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
+                                       .getCertificate(certGen.build(sigGen));
+                       cert.checkValidity(new Date());
+                       cert.verify(cert.getPublicKey());
+
+                       keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert });
+                       return cert;
+               } catch (GeneralSecurityException | OperatorCreationException e) {
+                       throw new RuntimeException("Cannot generate self-signed certificate", e);
+               }
+       }
+
+       public static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) {
+               try {
+                       KeyStore store = KeyStore.getInstance(keyStoreType, SECURITY_PROVIDER);
+                       if (Files.exists(keyStoreFile)) {
+                               try (InputStream fis = Files.newInputStream(keyStoreFile)) {
+                                       store.load(fis, keyStorePassword);
+                               }
+                       } else {
+                               store.load(null);
+                       }
+                       return store;
+               } catch (GeneralSecurityException | IOException e) {
+                       throw new RuntimeException("Cannot load keystore " + keyStoreFile, e);
+               }
+       }
+
+       public static void saveKeyStore(Path keyStoreFile, char[] keyStorePassword, KeyStore keyStore) {
+               try {
+                       try (OutputStream fis = Files.newOutputStream(keyStoreFile)) {
+                               keyStore.store(fis, keyStorePassword);
+                       }
+               } catch (GeneralSecurityException | IOException e) {
+                       throw new RuntimeException("Cannot save keystore " + keyStoreFile, e);
+               }
+       }
+
+//     public static byte[] pemToPKCS12(final String keyFile, final String cerFile, final String password)
+//                     throws Exception {
+//             // Get the private key
+//             FileReader reader = new FileReader(keyFile);
+//
+//             PEMReader pem = new PemReader(reader, new PasswordFinder() {
+//                     @Override
+//                     public char[] getPassword() {
+//                             return password.toCharArray();
+//                     }
+//             });
+//
+//             PrivateKey key = ((KeyPair) pem.readObject()).getPrivate();
+//
+//             pem.close();
+//             reader.close();
+//
+//             // Get the certificate
+//             reader = new FileReader(cerFile);
+//             pem = new PEMReader(reader);
+//
+//             X509Certificate cert = (X509Certificate) pem.readObject();
+//
+//             pem.close();
+//             reader.close();
+//
+//             // Put them into a PKCS12 keystore and write it to a byte[]
+//             ByteArrayOutputStream bos = new ByteArrayOutputStream();
+//             KeyStore ks = KeyStore.getInstance("PKCS12");
+//             ks.load(null);
+//             ks.setKeyEntry("alias", (Key) key, password.toCharArray(), new java.security.cert.Certificate[] { cert });
+//             ks.store(bos, password.toCharArray());
+//             bos.close();
+//             return bos.toByteArray();
+//     }
+
+       public static void loadPem(KeyStore keyStore, Reader key, char[] keyPassword, Reader cert) {
+               PrivateKey privateKey = loadPemPrivateKey(key, keyPassword);
+               X509Certificate certificate = loadPemCertificate(cert);
+               try {
+                       keyStore.setKeyEntry(certificate.getSubjectX500Principal().getName(), privateKey, keyPassword,
+                                       new java.security.cert.Certificate[] { certificate });
+               } catch (KeyStoreException e) {
+                       throw new RuntimeException("Cannot store PEM certificate", e);
+               }
+       }
+
+       public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) {
+               try (PEMParser pemParser = new PEMParser(reader)) {
+                       JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
+                       Object object = pemParser.readObject();
+                       PrivateKeyInfo privateKeyInfo;
+                       if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
+                               if (keyPassword == null)
+                                       throw new IllegalArgumentException("A key password is required");
+                               InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword);
+                               privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv);
+                       } else if (object instanceof PrivateKeyInfo) {
+                               privateKeyInfo = (PrivateKeyInfo) object;
+                       } else {
+                               throw new IllegalArgumentException("Unsupported format for private key");
+                       }
+                       return converter.getPrivateKey(privateKeyInfo);
+               } catch (IOException | OperatorCreationException | PKCSException e) {
+                       throw new RuntimeException("Cannot read private key", e);
+               }
+       }
+
+       public static X509Certificate loadPemCertificate(Reader reader) {
+               try (PEMParser pemParser = new PEMParser(reader)) {
+                       X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject();
+                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
+                                       .getCertificate(certHolder);
+                       return cert;
+               } catch (IOException | CertificateException e) {
+                       throw new RuntimeException("Cannot read private key", e);
+               }
+       }
+
+       public static void main(String[] args) throws Exception {
+               final String ALGORITHM = "RSA";
+               final String provider = "BC";
+               SecureRandom secureRandom = new SecureRandom();
+               long begin = System.currentTimeMillis();
+               for (int i = 512; i < 1024; i = i + 2) {
+                       try {
+                               KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, provider);
+                               keyGen.initialize(i, secureRandom);
+                               keyGen.generateKeyPair();
+                       } catch (Exception e) {
+                               System.err.println(i + " : " + e.getMessage());
+                       }
+               }
+               System.out.println((System.currentTimeMillis() - begin) + " ms");
+
+               // // String text = "a";
+               // String text =
+               // "testtesttesttesttesttesttesttesttesttesttesttesttesttesttest";
+               // try {
+               // System.out.println(text);
+               // PrivateKey privateKey;
+               // PublicKey publicKey;
+               // char[] password = "changeit".toCharArray();
+               // String alias = "CN=test";
+               // KeyStore keyStore = KeyStore.getInstance("pkcs12");
+               // File p12file = new File("test.p12");
+               // p12file.delete();
+               // if (!p12file.exists()) {
+               // keyStore.load(null);
+               // generateSelfSignedCertificate(keyStore, new X500Principal(alias),
+               // 513, password);
+               // try (OutputStream out = new FileOutputStream(p12file)) {
+               // keyStore.store(out, password);
+               // }
+               // }
+               // try (InputStream in = new FileInputStream(p12file)) {
+               // keyStore.load(in, password);
+               // privateKey = (PrivateKey) keyStore.getKey(alias, password);
+               // publicKey = keyStore.getCertificateChain(alias)[0].getPublicKey();
+               // }
+               // // KeyPair key;
+               // // final KeyPairGenerator keyGen =
+               // // KeyPairGenerator.getInstance(ALGORITHM);
+               // // keyGen.initialize(4096, new SecureRandom());
+               // // long begin = System.currentTimeMillis();
+               // // key = keyGen.generateKeyPair();
+               // // System.out.println((System.currentTimeMillis() - begin) + " ms");
+               // // keyStore.load(null);
+               // // keyStore.setKeyEntry("test", key.getPrivate(), password, null);
+               // // try(OutputStream out=new FileOutputStream(p12file)) {
+               // // keyStore.store(out, password);
+               // // }
+               // // privateKey = key.getPrivate();
+               // // publicKey = key.getPublic();
+               //
+               // Cipher encrypt = Cipher.getInstance(ALGORITHM);
+               // encrypt.init(Cipher.ENCRYPT_MODE, publicKey);
+               // byte[] encrypted = encrypt.doFinal(text.getBytes());
+               // String encryptedBase64 =
+               // Base64.getEncoder().encodeToString(encrypted);
+               // System.out.println(encryptedBase64);
+               // byte[] encryptedFromBase64 =
+               // Base64.getDecoder().decode(encryptedBase64);
+               //
+               // Cipher decrypt = Cipher.getInstance(ALGORITHM);
+               // decrypt.init(Cipher.DECRYPT_MODE, privateKey);
+               // byte[] decrypted = decrypt.doFinal(encryptedFromBase64);
+               // System.out.println(new String(decrypted));
+               // } catch (Exception e) {
+               // e.printStackTrace();
+               // }
+
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/dc=example,dc=com.ldif b/org.argeo.cms/src/org/argeo/cms/internal/runtime/dc=example,dc=com.ldif
new file mode 100644 (file)
index 0000000..43e7ade
--- /dev/null
@@ -0,0 +1,41 @@
+dn: dc=example,dc=com
+objectClass: domain
+objectClass: extensibleObject
+objectClass: top
+dc: example
+
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: top
+ou: Groups
+
+dn: ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: top
+ou: People
+
+dn: uid=demo,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: Demo User
+description: Demo user
+givenName: Demo
+mail: demo@localhost
+sn: User
+uid: demo
+userPassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
+
+dn: uid=root,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: organizationalPerson
+objectClass: top
+cn: Super User
+description: Superuser
+givenName: Super
+mail: root@localhost
+sn: User
+uid: root
+userPassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/example-ou=roles,ou=node.ldif b/org.argeo.cms/src/org/argeo/cms/internal/runtime/example-ou=roles,ou=node.ldif
new file mode 100644 (file)
index 0000000..ffa9073
--- /dev/null
@@ -0,0 +1,12 @@
+dn: cn=admin,ou=roles,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: admin
+member: uid=root,ou=People,dc=example,dc=com
+
+dn: cn=userAdmin,ou=roles,ou=node
+objectClass: groupOfNames
+objectClass: top
+member: cn=admin,ou=roles,ou=node
+cn: userAdmin
+
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg
new file mode 100644 (file)
index 0000000..c7c804c
--- /dev/null
@@ -0,0 +1,40 @@
+USER {
+    org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
+    org.argeo.cms.auth.SpnegoLoginModule optional;
+    com.sun.security.auth.module.Krb5LoginModule optional tryFirstPass=true;
+    org.argeo.cms.auth.UserAdminLoginModule sufficient;
+};
+
+ANONYMOUS {
+    org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
+    org.argeo.cms.auth.AnonymousLoginModule sufficient;
+};
+
+DATA_ADMIN {
+    org.argeo.cms.auth.DataAdminLoginModule requisite;
+};
+
+NODE {
+    com.sun.security.auth.module.Krb5LoginModule optional
+     keyTab="${osgi.instance.area}node/krb5.keytab" 
+     useKeyTab=true
+     storeKey=true;
+    org.argeo.cms.auth.DataAdminLoginModule requisite;
+};
+
+KEYRING {
+    org.argeo.cms.auth.KeyringLoginModule required;
+};
+
+SINGLE_USER {
+    com.sun.security.auth.module.Krb5LoginModule optional
+     principal="${user.name}"
+     storeKey=true
+     useTicketCache=true
+     debug=true;
+    org.argeo.cms.auth.SingleUserLoginModule requisite;
+};
+
+Jackrabbit {
+   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg
new file mode 100644 (file)
index 0000000..364977d
--- /dev/null
@@ -0,0 +1,30 @@
+USER {
+    org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
+    org.argeo.cms.auth.IdentLoginModule optional;
+    org.argeo.cms.auth.UserAdminLoginModule requisite;
+};
+
+ANONYMOUS {
+    org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
+    org.argeo.cms.auth.AnonymousLoginModule requisite;
+};
+
+DATA_ADMIN {
+    org.argeo.cms.auth.DataAdminLoginModule requisite;
+};
+
+NODE {
+    org.argeo.cms.auth.DataAdminLoginModule requisite;
+};
+
+KEYRING {
+    org.argeo.cms.auth.KeyringLoginModule required;
+};
+
+SINGLE_USER {
+    org.argeo.cms.auth.SingleUserLoginModule requisite;
+};
+
+Jackrabbit {
+   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=roles,ou=node.ldif b/org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=roles,ou=node.ldif
new file mode 100644 (file)
index 0000000..85247ed
--- /dev/null
@@ -0,0 +1,9 @@
+dn: ou=node
+objectClass: organizationalUnit
+objectClass: top
+ou: node
+
+dn: ou=roles,ou=node
+objectClass: organizationalUnit
+objectClass: top
+ou: roles
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=tokens,ou=node.ldif b/org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=tokens,ou=node.ldif
new file mode 100644 (file)
index 0000000..4ae9b88
--- /dev/null
@@ -0,0 +1,4 @@
+dn: ou=tokens,ou=node
+objectClass: organizationalUnit
+objectClass: top
+ou: tokens
index bee513546cdd25a0f0adad343d07b2857ca8cab7..c274ed97e581c934515d11adce2bf19b82e2661a 100644 (file)
@@ -31,6 +31,7 @@ public class AggregatingUserAdmin implements UserAdmin {
        private AbstractUserDirectory tokens = null;
        private Map<LdapName, AbstractUserDirectory> businessRoles = new HashMap<LdapName, AbstractUserDirectory>();
 
+       // TODO rather use an empty constructor and an init method
        public AggregatingUserAdmin(String systemRolesBaseDn, String tokensBaseDn) {
                try {
                        this.systemRolesBaseDn = new LdapName(systemRolesBaseDn);