From: Mathieu Baudier Date: Sun, 9 Jan 2022 07:42:55 +0000 (+0100) Subject: Refactor CMS life cycle. X-Git-Tag: argeo-commons-2.3.5~92 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=f4da6777015da3fc392138f0c01cea2f2add9ed3 Refactor CMS life cycle. --- diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsContext.java b/org.argeo.api/src/org/argeo/api/cms/CmsContext.java index a9616c8ae..fa26b253a 100644 --- a/org.argeo.api/src/org/argeo/api/cms/CmsContext.java +++ b/org.argeo.api/src/org/argeo/api/cms/CmsContext.java @@ -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 getLocales(); + + Long getAvailableSince(); + + /** Mark this group as a workgroup */ void createWorkgroup(String groupDn); } diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsDeployment.java b/org.argeo.api/src/org/argeo/api/cms/CmsDeployment.java index 9498f96f3..5893d2ec5 100644 --- a/org.argeo.api/src/org/argeo/api/cms/CmsDeployment.java +++ b/org.argeo.api/src/org/argeo/api/cms/CmsDeployment.java @@ -4,9 +4,8 @@ import java.util.Dictionary; /** A configured node deployment. */ public interface CmsDeployment { - Long getAvailableSince(); - + void addFactoryDeployConfig(String factoryPid, Dictionary props); - Dictionary getProps(String factoryPid, String cn); + Dictionary getProps(String factoryPid, String cn); } diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsState.java b/org.argeo.api/src/org/argeo/api/cms/CmsState.java index 26427003e..ed8698fca 100644 --- a/org.argeo.api/src/org/argeo/api/cms/CmsState.java +++ b/org.argeo.api/src/org/argeo/api/cms/CmsState.java @@ -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 getLocales(); - String getHostname(); Long getAvailableSince(); diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java index 6aaa1692f..e713f53e1 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java @@ -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 nodeDeploymentRef = bc.getServiceReference(CmsDeployment.class); + ServiceReference nodeDeploymentRef = bc.getServiceReference(CmsContext.class); Label label = new Label(composite, SWT.WRAP); CmsSwtUtils.markup(label); if (nodeDeploymentRef == null) { label.setText("Not yet deployed on
" + nodeState.getHostname() + "
, 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()); diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java index 42eedf1a3..9c8680c4d 100644 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java +++ b/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java @@ -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 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 index 000000000..c46b3a4e1 --- /dev/null +++ b/org.argeo.cms/OSGI-INF/cmsContext.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/cmsDeployment.xml b/org.argeo.cms/OSGI-INF/cmsDeployment.xml new file mode 100644 index 000000000..4093f3e21 --- /dev/null +++ b/org.argeo.cms/OSGI-INF/cmsDeployment.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/cmsState.xml b/org.argeo.cms/OSGI-INF/cmsState.xml new file mode 100644 index 000000000..9e9ecc4df --- /dev/null +++ b/org.argeo.cms/OSGI-INF/cmsState.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/deployConfig.xml b/org.argeo.cms/OSGI-INF/deployConfig.xml new file mode 100644 index 000000000..10fcb547f --- /dev/null +++ b/org.argeo.cms/OSGI-INF/deployConfig.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml b/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml new file mode 100644 index 000000000..7becce748 --- /dev/null +++ b/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/simpleTransactionManager.xml b/org.argeo.cms/OSGI-INF/simpleTransactionManager.xml new file mode 100644 index 000000000..c331aa430 --- /dev/null +++ b/org.argeo.cms/OSGI-INF/simpleTransactionManager.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index 61cb6cbab..e75adcdc3 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -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,\ diff --git a/org.argeo.cms/build.properties b/org.argeo.cms/build.properties index 67b98f4d0..db86d95a5 100644 --- a/org.argeo.cms/build.properties +++ b/org.argeo.cms/build.properties @@ -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/ diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java b/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java index afb6360b4..85a482464 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java @@ -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 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) diff --git a/org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java index ccf7fc724..097e588e4 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java @@ -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; diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java index 4d3617eef..962094d4a 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java @@ -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 diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java index c94480cb5..2dbad96d2 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java @@ -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; } } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java index b9f8d4a51..738b507e7 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java @@ -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); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java b/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java index 334e43c85..4abdd1458 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java @@ -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 index 50e13221d..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/.gitignore +++ /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 index f50594210..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java +++ /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 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 void registerService(Class clss, T service, Dictionary properties) { - if (bundleContext != null) { - bundleContext.registerService(clss, service, properties); - } - - } - - public static T getService(Class 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 getService(Class clazz) { -// ServiceReference 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 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 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 index 822959b63..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsContextImpl.java +++ /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(bc, Repository.class, null) { -// @Override -// public Repository addingService(ServiceReference 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 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 index 4c7cb1dbe..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeploymentImpl.java +++ /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 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(bc, HttpService.class, null) { - - @Override - public HttpService addingService(ServiceReference 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(bc, UserAdmin.class, null) { - @Override - public UserAdmin addingService(ServiceReference reference) { - UserAdmin userAdmin = super.addingService(reference); - addStandardSystemRoles(userAdmin); - userAdminAvailable = true; - checkReadiness(); - return userAdmin; - } - }; - // userAdminSt.open(); - KernelUtils.asyncOpen(userAdminSt); - - ServiceTracker confAdminSt = new ServiceTracker(bc, - ConfigurationAdmin.class, null) { - @Override - public ConfigurationAdmin addingService(ServiceReference 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 props) { - deployConfig.putFactoryDeployConfig(factoryPid, props); - deployConfig.save(); - try { - deployConfig.loadConfigs(); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - - public Dictionary 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 index eb7657edc..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsShutdown.java +++ /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 index 6bc8ac888..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsStateImpl.java +++ /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 locales = null; - - private ThreadGroup threadGroup = new ThreadGroup("CMS"); - private List 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 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 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 index 4a88dd1b4..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java +++ /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 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> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs(); - if (userDirectoryConfigs.size() != 0) { - List activeCns = new ArrayList<>(); - for (int i = 0; i < userDirectoryConfigs.size(); i++) { - Dictionary 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 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 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 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 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 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 index 39b11a531..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/GogoShellKiller.java +++ /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 index a2006a704..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java +++ /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 getHttpServerConfig(Dictionary 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 props = new Hashtable(); - // 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> getUserDirectoryConfigs() { - List> res = new ArrayList<>(); - File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile(); - List 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 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 index 90f2382d7..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java +++ /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 index f267933cf..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java +++ /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 asDictionary(Properties props) { - Hashtable hashtable = new Hashtable(); - for (Object key : props.keySet()) { - hashtable.put(key.toString(), props.get(key)); - } - return hashtable; - } - - static Dictionary 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(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 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() { -// -// @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() { -// -// @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 index 0b4103794..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java +++ /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 everythingListeners = Collections - .synchronizedList(new ArrayList()); - private final List allUsersListeners = Collections - .synchronizedList(new ArrayList()); - private final Map> userListeners = Collections - .synchronizedMap(new HashMap>()); - - private BlockingQueue events; - private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); - - private Integer maxLastEventsCount = 10 * 1000; - - /** Marker to prevent stack overflow */ - private ThreadLocal dispatching = new ThreadLocal() { - - @Override - protected Boolean initialValue() { - return false; - } - }; - - public NodeLogger(LogReaderService lrs) { - if (lrs != null) { - Enumeration 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(); - - // 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 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 lst = Collections.synchronizedList(new ArrayList()); - userListeners.put(username, lst); - } - userListeners.get(username).add(listener); - List 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 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 everythingIt = everythingListeners.iterator(); - while (everythingIt.hasNext()) - dispatchEvent(everythingIt.next(), event); - - if (event.getUsername() != null) { - Iterator allUsersIt = allUsersListeners.iterator(); - while (allUsersIt.hasNext()) - dispatchEvent(allUsersIt.next(), event); - - if (userListeners.containsKey(event.getUsername())) { - Iterator 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 lastEvents = new LinkedList(); - - 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 getLastEvents(String username, Integer maxCount) { - LinkedList evts = new LinkedList(); - ListIterator 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 index 17daa3e14..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java +++ /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 pidToBaseDn = new HashMap<>(); -// private Map> pidToServiceRegs = new HashMap<>(); -// private ServiceRegistration userAdminReg; - - // JTA - private final ServiceTracker 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 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 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 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 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 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 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 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 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 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() { - - @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 index 2105e05d0..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java +++ /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 index 34ec9bcba..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/SecurityProfile.java +++ /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(), "<>", "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(), "<>", "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(), "<>", "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(), "<>", "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(), "<>", "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(), "<>", "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(), "<>", "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 index 43e7ade33..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/dc=example,dc=com.ldif +++ /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 index ffa9073ef..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/example-ou=roles,ou=node.ldif +++ /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 index c7c804c64..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas-ipa.cfg +++ /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 index 364977d4b..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg +++ /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 index 85247edce..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=roles,ou=node.ldif +++ /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 index 4ae9b88dd..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=tokens,ou=node.ldif +++ /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 index 000000000..d28ffdbb2 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java @@ -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 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 void registerService(Class clss, T service, Dictionary properties) { + if (bundleContext != null) { + bundleContext.registerService(clss, service, properties); + } + + } + + public static T getService(Class 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(bc, HttpService.class, null) { + + @Override + public HttpService addingService(ServiceReference 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 getService(Class clazz) { +// ServiceReference 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 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 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 index 000000000..324462d39 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsShutdown.java @@ -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 index 000000000..c31f50ded --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java @@ -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 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> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs(); + if (userDirectoryConfigs.size() != 0) { + List activeCns = new ArrayList<>(); + for (int i = 0; i < userDirectoryConfigs.size(); i++) { + Dictionary 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 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 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 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 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 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 index 000000000..69dbec9f7 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeLogger.java @@ -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 everythingListeners = Collections + .synchronizedList(new ArrayList()); + private final List allUsersListeners = Collections + .synchronizedList(new ArrayList()); + private final Map> userListeners = Collections + .synchronizedMap(new HashMap>()); + + private BlockingQueue events; + private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); + + private Integer maxLastEventsCount = 10 * 1000; + + /** Marker to prevent stack overflow */ + private ThreadLocal dispatching = new ThreadLocal() { + + @Override + protected Boolean initialValue() { + return false; + } + }; + + public NodeLogger(LogReaderService lrs) { + if (lrs != null) { + Enumeration 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(); + + // 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 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 lst = Collections.synchronizedList(new ArrayList()); + userListeners.put(username, lst); + } + userListeners.get(username).add(listener); + List 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 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 everythingIt = everythingListeners.iterator(); + while (everythingIt.hasNext()) + dispatchEvent(everythingIt.next(), event); + + if (event.getUsername() != null) { + Iterator allUsersIt = allUsersListeners.iterator(); + while (allUsersIt.hasNext()) + dispatchEvent(allUsersIt.next(), event); + + if (userListeners.containsKey(event.getUsername())) { + Iterator 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 lastEvents = new LinkedList(); + + 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 getLastEvents(String username, Integer maxCount) { + LinkedList evts = new LinkedList(); + ListIterator 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 index 000000000..d9524e897 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java @@ -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 pidToBaseDn = new HashMap<>(); +// private Map> pidToServiceRegs = new HashMap<>(); +// private ServiceRegistration userAdminReg; + + // JTA +// private final ServiceTracker 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 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 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 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 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 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 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 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 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 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() { + + @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 index 000000000..7055538c3 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/SecurityProfile.java @@ -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(), "<>", "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(), "<>", "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(), "<>", "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(), "<>", "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(), "<>", "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(), "<>", "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(), "<>", "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 index 000000000..50e13221d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/.gitignore @@ -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 index 000000000..7ce2e8bd5 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java @@ -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 instance = new CompletableFuture(); + + private CmsState cmsState; + private CmsDeployment cmsDeployment; + private UserAdmin userAdmin; + + // i18n + private Locale defaultLocale; + private List 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(bc, Repository.class, null) { +// @Override +// public Repository addingService(ServiceReference 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 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 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(); + } + } + + 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 index 000000000..83f688a6a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java @@ -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 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(bc, HttpService.class, null) { +// +// @Override +// public HttpService addingService(ServiceReference 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(bc, UserAdmin.class, null) { +// @Override +// public UserAdmin addingService(ServiceReference reference) { +// UserAdmin userAdmin = super.addingService(reference); +// addStandardSystemRoles(userAdmin); +// userAdminAvailable = true; +// checkReadiness(); +// return userAdmin; +// } +// }; +// // userAdminSt.open(); +// KernelUtils.asyncOpen(userAdminSt); + +// ServiceTracker confAdminSt = new ServiceTracker(bc, +// ConfigurationAdmin.class, null) { +// @Override +// public ConfigurationAdmin addingService(ServiceReference 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 props) { + deployConfig.putFactoryDeployConfig(factoryPid, props); + deployConfig.save(); + try { + deployConfig.loadConfigs(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public Dictionary 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 index 000000000..7b47b520d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java @@ -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 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 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 index 000000000..56df43a8a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/GogoShellKiller.java @@ -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 index 000000000..70ea9ec48 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java @@ -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 getHttpServerConfig(Dictionary 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 props = new Hashtable(); + // 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> getUserDirectoryConfigs() { + List> res = new ArrayList<>(); + File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile(); + List 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 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 index 000000000..7c397de6c --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java @@ -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 index 000000000..afcb9ff26 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java @@ -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 asDictionary(Properties props) { + Hashtable hashtable = new Hashtable(); + for (Object key : props.keySet()) { + hashtable.put(key.toString(), props.get(key)); + } + return hashtable; + } + + static Dictionary 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(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 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() { +// +// @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() { +// +// @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 index 000000000..474a89950 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java @@ -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 index 000000000..43e7ade33 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/dc=example,dc=com.ldif @@ -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 index 000000000..ffa9073ef --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/example-ou=roles,ou=node.ldif @@ -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 index 000000000..c7c804c64 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg @@ -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 index 000000000..364977d4b --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg @@ -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 index 000000000..85247edce --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=roles,ou=node.ldif @@ -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 index 000000000..4ae9b88dd --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=tokens,ou=node.ldif @@ -0,0 +1,4 @@ +dn: ou=tokens,ou=node +objectClass: organizationalUnit +objectClass: top +ou: tokens diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java index bee513546..c274ed97e 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java @@ -31,6 +31,7 @@ public class AggregatingUserAdmin implements UserAdmin { private AbstractUserDirectory tokens = null; private Map businessRoles = new HashMap(); + // TODO rather use an empty constructor and an init method public AggregatingUserAdmin(String systemRolesBaseDn, String tokensBaseDn) { try { this.systemRolesBaseDn = new LdapName(systemRolesBaseDn);