From 06acf73a99f0e3908fe8998f1ff08dee109c5562 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sat, 6 Aug 2016 19:52:41 +0000 Subject: [PATCH] Make CMS production ready git-svn-id: https://svn.argeo.org/commons/trunk@9069 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- demo/argeo_node_rap.properties | 2 + dep/org.argeo.dep.cms.node/pom.xml | 5 + org.argeo.cms.api/.classpath | 7 + org.argeo.cms.api/.project | 28 + org.argeo.cms.api/bnd.bnd | 1 + org.argeo.cms.api/build.properties | 4 + org.argeo.cms.api/pom.xml | 16 + .../org/argeo/node/DataAdminPrincipal.java | 7 +- .../org/argeo/node}/DataModelNamespace.java | 4 +- .../src/org/argeo/node/NodeConstants.java | 33 + .../src/org/argeo/node/NodeDeployment.java | 5 + .../src/org/argeo/node/NodeInstance.java | 5 + .../src/org/argeo/node/NodeState.java | 10 + .../src/org/argeo/node}/RepoConf.java | 5 +- org.argeo.cms.api/src/org/argeo/node/node.cnd | 49 ++ .../src/org/argeo/node/package-info.java | 5 + .../src/org/argeo/node/packageinfo | 1 + org.argeo.cms/bnd.bnd | 2 +- org.argeo.cms/build.properties | 5 +- org.argeo.cms/pom.xml | 8 +- .../src/org/argeo/cms/auth/AuthConstants.java | 2 +- .../argeo/cms/auth/DataAdminLoginModule.java | 45 ++ .../org/argeo/cms/auth/HttpLoginModule.java | 5 +- .../cms/auth/SimpleRoleRegistration.java | 89 +++ .../argeo/cms/auth/UserAdminLoginModule.java | 5 +- .../argeo/cms/internal/kernel/Activator.java | 170 ++++- .../cms/internal/kernel/CmsDeployment.java | 117 +++ .../argeo/cms/internal/kernel/CmsState.java | 389 ++++++++++ .../argeo/cms/internal/kernel/DataHttp.java | 75 +- .../cms/internal/kernel/HomeRepository.java | 181 +++++ .../JackrabbitRepositoryServiceFactory.java | 202 +++--- .../org/argeo/cms/internal/kernel/Kernel.java | 672 ++++++++++++++---- .../cms/internal/kernel/KernelConstants.java | 33 +- .../cms/internal/kernel/KernelUtils.java | 99 ++- .../argeo/cms/internal/kernel/NodeHttp.java | 4 +- .../argeo/cms/internal/kernel/NodeLogger.java | 62 +- .../cms/internal/kernel/NodeRepository.java | 166 ----- .../cms/internal/kernel/NodeUserAdmin.java | 281 +++++--- .../org/argeo/cms/internal/kernel/jaas.cfg | 4 +- .../META-INF/spring/osgi.xml | 2 +- org.argeo.security.jackrabbit/pom.xml | 13 +- .../SystemJackrabbitLoginModule.java | 40 +- .../src/org/argeo/jcr/ArgeoJcrConstants.java | 2 + .../src/org/argeo/util/DictionaryKeys.java | 42 ++ .../src/org/argeo/util/LangUtils.java | 80 +++ .../src/org/argeo/util/LocaleChoice.java | 22 +- pom.xml | 20 +- 47 files changed, 2277 insertions(+), 747 deletions(-) create mode 100644 org.argeo.cms.api/.classpath create mode 100644 org.argeo.cms.api/.project create mode 100644 org.argeo.cms.api/bnd.bnd create mode 100644 org.argeo.cms.api/build.properties create mode 100644 org.argeo.cms.api/pom.xml rename org.argeo.security.core/src/org/argeo/security/SystemAuth.java => org.argeo.cms.api/src/org/argeo/node/DataAdminPrincipal.java (64%) rename {org.argeo.cms/src/org/argeo/cms/internal/kernel => org.argeo.cms.api/src/org/argeo/node}/DataModelNamespace.java (79%) create mode 100644 org.argeo.cms.api/src/org/argeo/node/NodeConstants.java create mode 100644 org.argeo.cms.api/src/org/argeo/node/NodeDeployment.java create mode 100644 org.argeo.cms.api/src/org/argeo/node/NodeInstance.java create mode 100644 org.argeo.cms.api/src/org/argeo/node/NodeState.java rename {org.argeo.server.jcr/src/org/argeo/jcr => org.argeo.cms.api/src/org/argeo/node}/RepoConf.java (89%) create mode 100644 org.argeo.cms.api/src/org/argeo/node/node.cnd create mode 100644 org.argeo.cms.api/src/org/argeo/node/package-info.java create mode 100644 org.argeo.cms.api/src/org/argeo/node/packageinfo create mode 100644 org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java create mode 100644 org.argeo.cms/src/org/argeo/cms/auth/SimpleRoleRegistration.java create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/kernel/HomeRepository.java rename org.argeo.server.jcr/src/org/argeo/jackrabbit/ManagedJackrabbitRepository.java => org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitRepositoryServiceFactory.java (50%) delete mode 100644 org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeRepository.java create mode 100644 org.argeo.util/src/org/argeo/util/DictionaryKeys.java create mode 100644 org.argeo.util/src/org/argeo/util/LangUtils.java diff --git a/demo/argeo_node_rap.properties b/demo/argeo_node_rap.properties index f96484a0d..e0805f8a1 100644 --- a/demo/argeo_node_rap.properties +++ b/demo/argeo_node_rap.properties @@ -4,6 +4,8 @@ org.eclipse.equinox.http.jetty,\ org.eclipse.equinox.cm,\ org.eclipse.rap.rwt.osgi +#org.eclipse.equinox.metatype,\ + argeo.osgi.start.3.node=\ org.argeo.cms diff --git a/dep/org.argeo.dep.cms.node/pom.xml b/dep/org.argeo.dep.cms.node/pom.xml index bcf6cfdee..91889fff9 100644 --- a/dep/org.argeo.dep.cms.node/pom.xml +++ b/dep/org.argeo.dep.cms.node/pom.xml @@ -17,6 +17,11 @@ org.argeo.dep.cms.client 2.1.45-SNAPSHOT + + org.argeo.commons + org.argeo.cms.api + 2.1.45-SNAPSHOT + org.argeo.commons org.argeo.cms diff --git a/org.argeo.cms.api/.classpath b/org.argeo.cms.api/.classpath new file mode 100644 index 000000000..eca7bdba8 --- /dev/null +++ b/org.argeo.cms.api/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms.api/.project b/org.argeo.cms.api/.project new file mode 100644 index 000000000..346a99ff4 --- /dev/null +++ b/org.argeo.cms.api/.project @@ -0,0 +1,28 @@ + + + org.argeo.cms.api + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.cms.api/bnd.bnd b/org.argeo.cms.api/bnd.bnd new file mode 100644 index 000000000..52a2bdd57 --- /dev/null +++ b/org.argeo.cms.api/bnd.bnd @@ -0,0 +1 @@ +Provide-Capability: cms.datamodel;name=node;cnd=/org/argeo/node/node.cnd \ No newline at end of file diff --git a/org.argeo.cms.api/build.properties b/org.argeo.cms.api/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/org.argeo.cms.api/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/org.argeo.cms.api/pom.xml b/org.argeo.cms.api/pom.xml new file mode 100644 index 000000000..f13c7545f --- /dev/null +++ b/org.argeo.cms.api/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + + org.argeo.commons + argeo-commons + 2.1.45-SNAPSHOT + .. + + org.argeo.cms.api + Commons CMS API + jar + + + \ No newline at end of file diff --git a/org.argeo.security.core/src/org/argeo/security/SystemAuth.java b/org.argeo.cms.api/src/org/argeo/node/DataAdminPrincipal.java similarity index 64% rename from org.argeo.security.core/src/org/argeo/security/SystemAuth.java rename to org.argeo.cms.api/src/org/argeo/node/DataAdminPrincipal.java index 76a03fb8c..df9bf35b7 100644 --- a/org.argeo.security.core/src/org/argeo/security/SystemAuth.java +++ b/org.argeo.cms.api/src/org/argeo/node/DataAdminPrincipal.java @@ -1,9 +1,10 @@ -package org.argeo.security; +package org.argeo.node; import java.security.Principal; -public final class SystemAuth implements Principal { - private final String name = "init"; +/** Allows to modify any data. */ +public final class DataAdminPrincipal implements Principal { + private final String name = "ou=dataAdmin"; @Override public String getName() { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataModelNamespace.java b/org.argeo.cms.api/src/org/argeo/node/DataModelNamespace.java similarity index 79% rename from org.argeo.cms/src/org/argeo/cms/internal/kernel/DataModelNamespace.java rename to org.argeo.cms.api/src/org/argeo/node/DataModelNamespace.java index 898ab956c..578419609 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataModelNamespace.java +++ b/org.argeo.cms.api/src/org/argeo/node/DataModelNamespace.java @@ -1,9 +1,9 @@ -package org.argeo.cms.internal.kernel; +package org.argeo.node; import org.osgi.resource.Namespace; /** CMS Data Model capability namespace. */ -class DataModelNamespace extends Namespace { +public class DataModelNamespace extends Namespace { public static final String CMS_DATA_MODEL_NAMESPACE = "cms.datamodel"; public static final String CAPABILITY_NAME_ATTRIBUTE = "name"; diff --git a/org.argeo.cms.api/src/org/argeo/node/NodeConstants.java b/org.argeo.cms.api/src/org/argeo/node/NodeConstants.java new file mode 100644 index 000000000..7b7d6dc35 --- /dev/null +++ b/org.argeo.cms.api/src/org/argeo/node/NodeConstants.java @@ -0,0 +1,33 @@ +package org.argeo.node; + +public interface NodeConstants { + /* + * PIDs + */ + String NODE_STATE_PID = "org.argeo.node.state"; + String NODE_DEPLOYMENT_PID = "org.argeo.node.deployment"; + String NODE_INSTANCE_PID = "org.argeo.node.instance"; + + String NODE_REPO_PID = "org.argeo.node.repo"; + String NODE_USER_ADMIN_PID = "org.argeo.node.userAdmin"; + + /* + * FACTORY PIDs + */ + String JACKRABBIT_FACTORY_PID = "org.argeo.jackrabbit.config"; + + /* + * FRAMEWORK PROPERTIES + */ + String NODE_INIT = "argeo.node.init"; + String I18N_DEFAULT_LOCALE = "argeo.i18n.defaultLocale"; + String I18N_LOCALES = "argeo.i18n.locales"; + // Node Security + String ROLES_URI = "argeo.node.roles.uri"; + /** URI to an LDIF file or LDAP server used as initialization or backend */ + String USERADMIN_URIS = "argeo.node.useradmin.uris"; + // Node + /** Properties configuring the node repository */ + String NODE_REPO_PROP_PREFIX = "argeo.node.repo."; + +} diff --git a/org.argeo.cms.api/src/org/argeo/node/NodeDeployment.java b/org.argeo.cms.api/src/org/argeo/node/NodeDeployment.java new file mode 100644 index 000000000..5889e5054 --- /dev/null +++ b/org.argeo.cms.api/src/org/argeo/node/NodeDeployment.java @@ -0,0 +1,5 @@ +package org.argeo.node; + +public interface NodeDeployment { + +} diff --git a/org.argeo.cms.api/src/org/argeo/node/NodeInstance.java b/org.argeo.cms.api/src/org/argeo/node/NodeInstance.java new file mode 100644 index 000000000..e1e4bcdfa --- /dev/null +++ b/org.argeo.cms.api/src/org/argeo/node/NodeInstance.java @@ -0,0 +1,5 @@ +package org.argeo.node; + +public interface NodeInstance { + +} diff --git a/org.argeo.cms.api/src/org/argeo/node/NodeState.java b/org.argeo.cms.api/src/org/argeo/node/NodeState.java new file mode 100644 index 000000000..f7d7042a8 --- /dev/null +++ b/org.argeo.cms.api/src/org/argeo/node/NodeState.java @@ -0,0 +1,10 @@ +package org.argeo.node; + +import java.util.List; +import java.util.Locale; + +public interface NodeState { + public Locale getDefaultLocale(); + + public List getLocales(); +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/RepoConf.java b/org.argeo.cms.api/src/org/argeo/node/RepoConf.java similarity index 89% rename from org.argeo.server.jcr/src/org/argeo/jcr/RepoConf.java rename to org.argeo.cms.api/src/org/argeo/node/RepoConf.java index 28275d631..39f72c035 100644 --- a/org.argeo.server.jcr/src/org/argeo/jcr/RepoConf.java +++ b/org.argeo.cms.api/src/org/argeo/node/RepoConf.java @@ -1,4 +1,4 @@ -package org.argeo.jcr; +package org.argeo.node; /** JCR repository configuration */ public enum RepoConf { @@ -13,6 +13,9 @@ public enum RepoConf { /** Database password */ dbpassword(null), + /** The identifier (can be an URL locating the repo) */ + uri(null), + // // JACKRABBIT SPECIFIC // diff --git a/org.argeo.cms.api/src/org/argeo/node/node.cnd b/org.argeo.cms.api/src/org/argeo/node/node.cnd new file mode 100644 index 000000000..390bc6cd6 --- /dev/null +++ b/org.argeo.cms.api/src/org/argeo/node/node.cnd @@ -0,0 +1,49 @@ + + +// USER NODES +[argeo:userHome] > mix:created, mix:lastModified +mixin +- argeo:userID (STRING) m ++ argeo:keyring (argeo:pbeSpec) +//+ argeo:preferences (argeo:preferenceNode) + +[argeo:userProfile] > mix:created, mix:lastModified, mix:title, mix:versionable +mixin +- argeo:userID (STRING) m + +//[argeo:preferenceNode] > mix:lastModified, mix:versionable +//mixin +//+ * (argeo:preferenceNode) * version + +[argeo:remoteRepository] > nt:unstructured +- argeo:uri (STRING) +- argeo:userID (STRING) ++ argeo:password (argeo:encrypted) + +// TABULAR CONTENT +[argeo:table] > nt:file ++ * (argeo:column) * + +[argeo:column] > mix:title +- jcr:requiredType (STRING) = 'STRING' + +[argeo:csv] > nt:resource + +// CRYPTO +[argeo:encrypted] > nt:base +mixin +// initialization vector used by some algorithms +- argeo:iv (BINARY) + +[argeo:pbeKeySpec] > nt:base +mixin +- argeo:secretKeyFactory (STRING) +- argeo:salt (BINARY) +- argeo:iterationCount (LONG) +- argeo:keyLength (LONG) +- argeo:secretKeyEncryption (STRING) + +[argeo:pbeSpec] > argeo:pbeKeySpec +mixin +- argeo:cipher (STRING) + diff --git a/org.argeo.cms.api/src/org/argeo/node/package-info.java b/org.argeo.cms.api/src/org/argeo/node/package-info.java new file mode 100644 index 000000000..fda3baec5 --- /dev/null +++ b/org.argeo.cms.api/src/org/argeo/node/package-info.java @@ -0,0 +1,5 @@ +/** + * Abstractions or constants related to an Argeo Node, an active repository of + * linked data. + */ +package org.argeo.node; \ No newline at end of file diff --git a/org.argeo.cms.api/src/org/argeo/node/packageinfo b/org.argeo.cms.api/src/org/argeo/node/packageinfo new file mode 100644 index 000000000..2c9afe82e --- /dev/null +++ b/org.argeo.cms.api/src/org/argeo/node/packageinfo @@ -0,0 +1 @@ +version 2.1.0 \ No newline at end of file diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index b941d3bb4..2add4fa53 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -1,3 +1,4 @@ + Bundle-SymbolicName: org.argeo.cms;singleton:=true Bundle-Activator: org.argeo.cms.internal.kernel.Activator Import-Package: javax.jcr.security,\ @@ -12,7 +13,6 @@ org.eclipse.*;resolution:=optional,\ org.eclipse.core.commands;resolution:=optional,\ org.eclipse.swt;resolution:=optional,\ org.eclipse.jface.window;resolution:=optional,\ -org.eclipse.swt.widgets;resolution:=optional,\ org.h2;resolution:=optional,\ org.postgresql;resolution:=optional,\ org.apache.commons.vfs2.*;resolution:=optional,\ diff --git a/org.argeo.cms/build.properties b/org.argeo.cms/build.properties index 2e5f72120..4f7e63bdb 100644 --- a/org.argeo.cms/build.properties +++ b/org.argeo.cms/build.properties @@ -1,7 +1,8 @@ -source.. = src/ output.. = bin/ bin.includes = META-INF/,\ .,\ OSGI-INF/,\ - bin/ + bin/,\ + OSGI-INF/org.argeo.cms.internal.kernel.KernelInitOld.xml +source.. = src/ additional.bundles = org.apache.jackrabbit.data diff --git a/org.argeo.cms/pom.xml b/org.argeo.cms/pom.xml index eee721389..b60a1280e 100644 --- a/org.argeo.cms/pom.xml +++ b/org.argeo.cms/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 org.argeo.commons @@ -11,6 +12,11 @@ Commons CMS jar + + org.argeo.commons + org.argeo.cms.api + 2.1.45-SNAPSHOT + org.argeo.commons org.argeo.server.jcr diff --git a/org.argeo.cms/src/org/argeo/cms/auth/AuthConstants.java b/org.argeo.cms/src/org/argeo/cms/auth/AuthConstants.java index d58d7421c..baf093b8a 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/AuthConstants.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/AuthConstants.java @@ -7,7 +7,7 @@ public interface AuthConstants { // LOGIN CONTEXTS final static String LOGIN_CONTEXT_USER = "USER"; final static String LOGIN_CONTEXT_ANONYMOUS = "ANONYMOUS"; - final static String LOGIN_CONTEXT_SYSTEM = "SYSTEM"; + final static String LOGIN_CONTEXT_DATA_ADMIN = "DATA_ADMIN"; final static String LOGIN_CONTEXT_SINGLE_USER = "SINGLE_USER"; // RESERVED ROLES diff --git a/org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java new file mode 100644 index 000000000..5c7b64377 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java @@ -0,0 +1,45 @@ +package org.argeo.cms.auth; + +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; + +import org.argeo.node.DataAdminPrincipal; + +public class DataAdminLoginModule implements LoginModule { + private Subject subject; + + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, + Map sharedState, Map options) { + this.subject = subject; + } + + @Override + public boolean login() throws LoginException { + // TODO check permission? + return true; + } + + @Override + public boolean commit() throws LoginException { + subject.getPrincipals().add(new DataAdminPrincipal()); + return true; + } + + @Override + public boolean abort() throws LoginException { + return true; + } + + @Override + public boolean logout() throws LoginException { + // remove ALL credentials (e.g. additional Jackrabbit credentials) + subject.getPrincipals().clear(); + return true; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpLoginModule.java index 91a2d09aa..e99e26d13 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/HttpLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/HttpLoginModule.java @@ -18,9 +18,9 @@ import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.cms.CmsException; -import org.argeo.cms.internal.kernel.Activator; import org.argeo.cms.internal.kernel.WebCmsSessionImpl; import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; @@ -42,7 +42,8 @@ public class HttpLoginModule implements LoginModule, AuthConstants { @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { - bc = Activator.getBundleContext(); + bc = FrameworkUtil.getBundle(HttpLoginModule.class).getBundleContext(); + assert bc != null; this.subject = subject; this.callbackHandler = callbackHandler; this.sharedState = (Map) sharedState; diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SimpleRoleRegistration.java b/org.argeo.cms/src/org/argeo/cms/auth/SimpleRoleRegistration.java new file mode 100644 index 000000000..0efda3fa8 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/SimpleRoleRegistration.java @@ -0,0 +1,89 @@ +package org.argeo.cms.auth; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.transaction.UserTransaction; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.UserAdmin; + +/** + * Register one or many roles via a user admin service. Does nothing if the role + * is already registered. + */ +public class SimpleRoleRegistration implements Runnable { + private final static Log log = LogFactory + .getLog(SimpleRoleRegistration.class); + + private String role; + private List roles = new ArrayList(); + private UserAdmin userAdmin; + private UserTransaction userTransaction; + + @Override + public void run() { + try { + userTransaction.begin(); + if (role != null && !roleExists(role)) + newRole(toDn(role)); + + for (String r : roles) + if (!roleExists(r)) + newRole(toDn(r)); + userTransaction.commit(); + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + log.error("Cannot rollback", e1); + } + throw new ArgeoException("Cannot add roles", e); + } + } + + private boolean roleExists(String role) { + return userAdmin.getRole(toDn(role).toString()) != null; + } + + protected void newRole(LdapName r) { + userAdmin.createRole(r.toString(), Role.GROUP); + log.info("Added role " + r + " required by application."); + } + + public void register(UserAdmin userAdminService, Map properties) { + this.userAdmin = userAdminService; + run(); + } + + protected LdapName toDn(String name) { + try { + return new LdapName("cn=" + name + ",ou=roles,ou=node"); + } catch (InvalidNameException e) { + throw new ArgeoException("Badly formatted role name " + name, e); + } + } + + public void setRole(String role) { + this.role = role; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public void setUserAdmin(UserAdmin userAdminService) { + this.userAdmin = userAdminService; + } + + public void setUserTransaction(UserTransaction userTransaction) { + this.userTransaction = userTransaction; + } + +} 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 29eb8bf49..492ab0fba 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java @@ -17,9 +17,9 @@ import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import org.argeo.ArgeoException; -import org.argeo.cms.internal.kernel.Activator; import org.argeo.eclipse.ui.specific.UiContext; import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; import org.osgi.service.useradmin.Authorization; import org.osgi.service.useradmin.User; import org.osgi.service.useradmin.UserAdmin; @@ -42,7 +42,8 @@ public class UserAdminLoginModule implements LoginModule, AuthConstants { public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { try { - bc = Activator.getBundleContext(); + bc = FrameworkUtil.getBundle(UserAdminLoginModule.class).getBundleContext(); + assert bc != null; // this.subject = subject; this.callbackHandler = callbackHandler; this.sharedState = (Map) sharedState; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java index 1d40c9946..1997b73ce 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java @@ -1,69 +1,161 @@ package org.argeo.cms.internal.kernel; -import java.util.UUID; +import java.io.IOException; +import java.net.URL; +import java.util.Dictionary; +import java.util.Hashtable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoLogger; +import org.argeo.cms.CmsException; +import org.argeo.node.NodeConstants; +import org.argeo.node.NodeState; +import org.argeo.node.RepoConf; +import org.argeo.util.LangUtils; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.cm.Configuration; +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.cm.ManagedService; +import org.osgi.service.condpermadmin.ConditionalPermissionAdmin; +import org.osgi.service.log.LogReaderService; /** * Activates the {@link Kernel} from the provided {@link BundleContext}. Gives * access to kernel information for the rest of the bundle (and only it) */ public class Activator implements BundleActivator { - public final static String SYSTEM_KEY_PROPERTY = "argeo.security.systemKey"; + // public final static String SYSTEM_KEY_PROPERTY = + // "argeo.security.systemKey"; private final Log log = LogFactory.getLog(Activator.class); - private final static String systemKey; - static { - systemKey = UUID.randomUUID().toString(); - System.setProperty(SYSTEM_KEY_PROPERTY, systemKey); - } + // private final static String systemKey; + // static { + // System.setProperty(SYSTEM_KEY_PROPERTY, systemKey); + // } + +// private static Kernel kernel; + private static Activator instance; + + private BundleContext bc; + private ConditionalPermissionAdmin permissionAdmin; + private LogReaderService logReaderService; + private ConfigurationAdmin configurationAdmin; - private static BundleContext bundleContext; - private static Kernel kernel; + private NodeLogger logger; + private CmsState nodeState; @Override - public void start(BundleContext context) throws Exception { - assert bundleContext == null; - assert kernel == null; - bundleContext = context; - try { - kernel = new Kernel(); - kernel.init(); - } catch (Exception e) { - log.error("Cannot boot kernel", e); + public void start(BundleContext bundleContext) throws Exception { + // try { + // kernel = new Kernel(); + // kernel.init(); + // } catch (Exception e) { + // log.error("Cannot boot kernel", e); + // } + + instance = this; + this.bc = bundleContext; + this.permissionAdmin = getService(ConditionalPermissionAdmin.class); + this.logReaderService = getService(LogReaderService.class); + this.configurationAdmin = getService(ConfigurationAdmin.class); + + initSecurity();// must be first + initArgeoLogger(); + initNodeState(); + } + + private void initSecurity() { + URL url = getClass().getClassLoader().getResource(KernelConstants.JAAS_CONFIG); + System.setProperty("java.security.auth.login.config", url.toExternalForm()); + } + + private void initArgeoLogger() { + logger = new NodeLogger(logReaderService); + + // register + bc.registerService(ArgeoLogger.class, logger, null); + } + + private void initNodeState() throws IOException { + nodeState = new CmsState(); + bc.registerService(LangUtils.names(NodeState.class, ManagedService.class), nodeState, + LangUtils.init(Constants.SERVICE_PID, NodeConstants.NODE_STATE_PID)); + + Configuration nodeConf = configurationAdmin.getConfiguration(NodeConstants.NODE_STATE_PID); + Dictionary props = nodeConf.getProperties(); + if (props == null) { + if (log.isDebugEnabled()) + log.debug("Clean node state"); + Dictionary envProps = getStatePropertiesFromEnvironment(); + nodeConf.update(envProps); + } else { + // Check id state is in line with environment + Dictionary envProps = getStatePropertiesFromEnvironment(); + for (String key : LangUtils.keys(envProps)) { + Object envValue = envProps.get(key); + Object storedValue = props.get(key); + if (storedValue == null) + throw new CmsException("No state value for env " + key + "=" + envValue + + ", please clean the OSGi configuration."); + if (!storedValue.equals(envValue)) + throw new CmsException("State value for " + key + "=" + storedValue + + " is different from env value =" + envValue + ", please clean the OSGi configuration."); + } } + } @Override - public void stop(BundleContext context) throws Exception { - kernel.destroy(); - kernel = null; - bundleContext = null; + public void stop(BundleContext bundleContext) throws Exception { + nodeState.shutdown(); + + instance = null; + this.bc = null; + this.permissionAdmin = null; + this.logReaderService = null; + this.configurationAdmin = null; + +// if (kernel != null) { +// kernel.destroy(); +// kernel = null; +// } + + } + + private T getService(Class clazz) { + ServiceReference sr = bc.getServiceReference(clazz); + if (sr == null) + throw new CmsException("No service available for " + clazz); + return bc.getService(sr); } - /** - * Singleton interface to the {@link BundleContext} related to the calling - * thread. - * - * @BundleScope - */ - public static BundleContext getBundleContext() { - return bundleContext; + protected Dictionary getStatePropertiesFromEnvironment() { + Hashtable props = new Hashtable<>(); + // i18n + copyFrameworkProp(NodeConstants.I18N_DEFAULT_LOCALE, props); + copyFrameworkProp(NodeConstants.I18N_LOCALES, props); + // user admin + copyFrameworkProp(NodeConstants.ROLES_URI, props); + copyFrameworkProp(NodeConstants.USERADMIN_URIS, props); + // data + for (RepoConf repoConf : RepoConf.values()) + copyFrameworkProp(NodeConstants.NODE_REPO_PROP_PREFIX + repoConf.name(), props); + // TODO add other environment sources + return props; } - public static KernelHeader getKernelHeader() { - return kernel; + private void copyFrameworkProp(String key, Dictionary props) { + String value = bc.getProperty(key); + if (value != null) + props.put(key, value); } - /** - * @return a String which is guaranteed to be unique between and constant - * within a Java static context (typically a VM launch) - * @BundleScope - */ - public final static String getSystemKey() { - return systemKey; + public static NodeState getNodeState() { + return instance.nodeState; } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java new file mode 100644 index 000000000..6fbe7a95c --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java @@ -0,0 +1,117 @@ +package org.argeo.cms.internal.kernel; + +import static org.argeo.node.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; + +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.util.Dictionary; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.jcr.Repository; +import javax.jcr.Session; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.commons.cnd.CndImporter; +import org.argeo.cms.CmsException; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.JcrUtils; +import org.argeo.node.DataModelNamespace; +import org.argeo.node.NodeDeployment; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleWire; +import org.osgi.framework.wiring.BundleWiring; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedService; + +public class CmsDeployment implements NodeDeployment, ManagedService { + private final Log log = LogFactory.getLog(getClass()); + private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + private Repository deployedNodeRepository; + private HomeRepository homeRepository; + + @Override + public void updated(Dictionary properties) throws ConfigurationException { + if (properties == null) + return; + + prepareDataModel(KernelUtils.openAdminSession(deployedNodeRepository)); + Hashtable regProps = new Hashtable(); + regProps.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, ArgeoJcrConstants.ALIAS_HOME); + homeRepository = new HomeRepository(deployedNodeRepository); + // register + bc.registerService(Repository.class, homeRepository, regProps); +} + + /** Session is logged out. */ + private void prepareDataModel(Session adminSession) { + try { + Set processed = new HashSet(); + bundles: for (Bundle bundle : bc.getBundles()) { + BundleWiring wiring = bundle.adapt(BundleWiring.class); + if (wiring == null) { + if (log.isTraceEnabled()) + log.error("No wiring for " + bundle.getSymbolicName()); + continue bundles; + } + processWiring(adminSession, wiring, processed); + } + } finally { + JcrUtils.logoutQuietly(adminSession); + } + } + + private void processWiring(Session adminSession, BundleWiring wiring, Set processed) { + // recursively process requirements first + List requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE); + for (BundleWire wire : requiredWires) { + processWiring(adminSession, wire.getProviderWiring(), processed); + // registerCnd(adminSession, wire.getCapability(), processed); + } + List capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); + for (BundleCapability capability : capabilities) { + registerCnd(adminSession, capability, processed); + } + } + + private void registerCnd(Session adminSession, BundleCapability capability, Set processed) { + Map attrs = capability.getAttributes(); + String name = attrs.get(DataModelNamespace.CAPABILITY_NAME_ATTRIBUTE).toString(); + if (processed.contains(name)) { + if (log.isTraceEnabled()) + log.trace("Data model " + name + " has already been processed"); + return; + } + String path = attrs.get(DataModelNamespace.CAPABILITY_CND_ATTRIBUTE).toString(); + URL url = capability.getRevision().getBundle().getResource(path); + try (Reader reader = new InputStreamReader(url.openStream())) { + CndImporter.registerNodeTypes(reader, adminSession, true); + processed.add(name); + if (log.isDebugEnabled()) + log.debug("Registered CND " + url); + } catch (Exception e) { + throw new CmsException("Cannot import CND " + url, e); + } + + Hashtable properties = new Hashtable<>(); + properties.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, name); + bc.registerService(Repository.class, adminSession.getRepository(), properties); + if (log.isDebugEnabled()) + log.debug("Published data model " + name); + } + + public void setDeployedNodeRepository(Repository deployedNodeRepository) { + this.deployedNodeRepository = deployedNodeRepository; + } + + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java new file mode 100644 index 000000000..ca6341aed --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java @@ -0,0 +1,389 @@ +package org.argeo.cms.internal.kernel; + +import static bitronix.tm.TransactionManagerServices.getTransactionManager; +import static bitronix.tm.TransactionManagerServices.getTransactionSynchronizationRegistry; +import static java.util.Locale.ENGLISH; +import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp; +import static org.argeo.util.LocaleChoice.asLocaleList; +import static org.osgi.framework.Constants.FRAMEWORK_UUID; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; +import java.util.Locale; + +import javax.jcr.RepositoryFactory; +import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; +import javax.transaction.UserTransaction; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.api.JackrabbitRepository; +import org.argeo.cms.CmsException; +import org.argeo.jackrabbit.OsgiJackrabbitRepositoryFactory; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.node.NodeConstants; +import org.argeo.node.NodeDeployment; +import org.argeo.node.NodeState; +import org.argeo.node.RepoConf; +import org.argeo.util.LangUtils; +import org.eclipse.equinox.http.jetty.JettyConfigurator; +import org.eclipse.equinox.http.jetty.JettyConstants; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +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.cm.ConfigurationException; +import org.osgi.service.cm.ManagedService; +import org.osgi.service.cm.ManagedServiceFactory; +import org.osgi.service.http.HttpService; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +import bitronix.tm.BitronixTransactionManager; +import bitronix.tm.BitronixTransactionSynchronizationRegistry; +import bitronix.tm.TransactionManagerServices; + +public class CmsState implements NodeState, ManagedService { + private final Log log = LogFactory.getLog(CmsState.class); + private final BundleContext bc = FrameworkUtil.getBundle(CmsState.class).getBundleContext(); + + // REFERENCES + private ConfigurationAdmin configurationAdmin; + + // i18n + private Locale defaultLocale; + private List locales = null; + + // Standalone services + private BitronixTransactionManager transactionManager; + private BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry; + private OsgiJackrabbitRepositoryFactory repositoryFactory; + + // Security + private NodeUserAdmin userAdmin; + private JackrabbitRepositoryServiceFactory repositoryServiceFactory; + + // Deployment + private final CmsDeployment nodeDeployment = new CmsDeployment(); + + private boolean cleanState = false; + private URI nodeRepoUri = null; + + @Override + public void updated(Dictionary properties) throws ConfigurationException { + if (properties == null) { + this.cleanState = true; + if (log.isTraceEnabled()) + log.trace("Clean state"); + return; + } + + try { + if (log.isDebugEnabled()) + log.debug( + "## CMS STARTED " + (cleanState ? " (clean state) " : "") + LangUtils.toJson(properties, true)); + configurationAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class)); + + initI18n(properties); + initTrackers(); + initTransactionManager(); + initRepositoryFactory(); + initUserAdmin(); + initRepositories(properties); + initWebServer(); + initNodeDeployment(); + } catch (Exception e) { + throw new CmsException("Cannot get configuration", e); + } + } + + private void initTrackers() { + new ServiceTracker(bc, HttpService.class, new PrepareHttpStc()).open(); + new ServiceTracker<>(bc, JackrabbitRepository.class, new JackrabbitrepositoryStc()).open(); + } + + private void initI18n(Dictionary stateProps) { + Object defaultLocaleValue = stateProps.get(NodeConstants.I18N_DEFAULT_LOCALE); + defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString()) + : new Locale(ENGLISH.getLanguage()); + locales = asLocaleList(stateProps.get(NodeConstants.I18N_LOCALES)); + } + + private void initTransactionManager() { + bitronix.tm.Configuration tmConf = TransactionManagerServices.getConfiguration(); + tmConf.setServerId(getFrameworkProp(FRAMEWORK_UUID)); + + 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()); + transactionManager = getTransactionManager(); + transactionSynchronizationRegistry = getTransactionSynchronizationRegistry(); + // register + bc.registerService(TransactionManager.class, transactionManager, null); + bc.registerService(UserTransaction.class, transactionManager, null); + bc.registerService(TransactionSynchronizationRegistry.class, transactionSynchronizationRegistry, null); + } + + private void initRepositoryFactory() { + // TODO rationalise RepositoryFactory + repositoryFactory = new OsgiJackrabbitRepositoryFactory(); + repositoryFactory.setBundleContext(bc); + // register + bc.registerService(RepositoryFactory.class, repositoryFactory, null); + } + + private void initUserAdmin() { + userAdmin = new NodeUserAdmin(); + // register + Dictionary props = userAdmin.currentState(); + props.put(Constants.SERVICE_PID, NodeConstants.NODE_REPO_PID); + // TODO use ManagedService + bc.registerService(UserAdmin.class, userAdmin, props); + } + + private void initRepositories(Dictionary stateProps) throws IOException { + nodeRepoUri = KernelUtils.getOsgiInstanceUri("repos/node"); + // register + repositoryServiceFactory = new JackrabbitRepositoryServiceFactory(); + bc.registerService(ManagedServiceFactory.class, repositoryServiceFactory, + LangUtils.init(Constants.SERVICE_PID, NodeConstants.JACKRABBIT_FACTORY_PID)); + + if (cleanState) { + Configuration newNodeConf = configurationAdmin + .createFactoryConfiguration(NodeConstants.JACKRABBIT_FACTORY_PID); + Dictionary props = getNodeConfig(stateProps); + if (props == null) { + if (log.isDebugEnabled()) + log.debug("No argeo.node.repo.type=localfs|h2|postgresql|memory" + + " property defined, entering interactive mode..."); + // TODO interactive configuration + return; + } + // props.put(Constants.SERVICE_PID, NodeConstants.NODE_REPO_PID); + props.put(RepoConf.uri.name(), nodeRepoUri.toString()); + newNodeConf.update(props); + } + } + + private void initWebServer() { + String httpPort = getFrameworkProp("org.osgi.service.http.port"); + String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure"); + try { + if (httpPort != null || httpsPort != null) { + final Hashtable jettyProps = new Hashtable(); + if (httpPort != null) { + jettyProps.put(JettyConstants.HTTP_PORT, httpPort); + jettyProps.put(JettyConstants.HTTP_ENABLED, true); + } + if (httpsPort != null) { + jettyProps.put(JettyConstants.HTTPS_PORT, httpsPort); + jettyProps.put(JettyConstants.HTTPS_ENABLED, true); + jettyProps.put(JettyConstants.SSL_KEYSTORETYPE, "PKCS12"); + // jettyProps.put(JettyConstants.SSL_KEYSTORE, + // nodeSecurity.getHttpServerKeyStore().getCanonicalPath()); + jettyProps.put(JettyConstants.SSL_PASSWORD, "changeit"); + jettyProps.put(JettyConstants.SSL_WANTCLIENTAUTH, true); + } + if (configurationAdmin != null) { + // TODO make filter more generic + String filter = "(" + JettyConstants.HTTP_PORT + "=" + httpPort + ")"; + if (configurationAdmin.listConfigurations(filter) != null) + return; + Configuration jettyConf = configurationAdmin + .createFactoryConfiguration(KernelConstants.JETTY_FACTORY_PID, null); + jettyConf.update(jettyProps); + + } else { + JettyConfigurator.startServer("default", jettyProps); + } + } + } catch (Exception e) { + throw new CmsException("Cannot initialize web server on " + httpPortsMsg(httpPort, httpsPort), e); + } + } + + private void initNodeDeployment() throws IOException { + Configuration nodeDeploymentConf = configurationAdmin.getConfiguration(NodeConstants.NODE_DEPLOYMENT_PID); + nodeDeploymentConf.update(new Hashtable<>()); + } + + void shutdown() { + if (transactionManager != null) + transactionManager.shutdown(); + if (userAdmin != null) + userAdmin.destroy(); + if (repositoryServiceFactory != null) + repositoryServiceFactory.shutdown(); + + // Clean hanging Gogo shell thread + new GogoShellKiller().start(); + + if (log.isDebugEnabled()) + log.debug("## CMS STOPPED"); + } + + private Dictionary getNodeConfig(Dictionary properties) { + Object repoType = properties.get(NodeConstants.NODE_REPO_PROP_PREFIX + RepoConf.type.name()); + if (repoType == null) + return null; + + Hashtable props = new Hashtable(); + for (RepoConf repoConf : RepoConf.values()) { + Object value = properties.get(NodeConstants.NODE_REPO_PROP_PREFIX + repoConf.name()); + if (value != null) + props.put(repoConf.name(), value); + } + return props; + } + + private class JackrabbitrepositoryStc + implements ServiceTrackerCustomizer { + + @Override + public JackrabbitRepository addingService(ServiceReference reference) { + JackrabbitRepository nodeRepo = bc.getService(reference); + Object repoUri = reference.getProperty(ArgeoJcrConstants.JCR_REPOSITORY_URI); + if (repoUri != null && repoUri.equals(nodeRepoUri.toString())) { + nodeDeployment.setDeployedNodeRepository(nodeRepo); + // register + bc.registerService(LangUtils.names(NodeDeployment.class, ManagedService.class), nodeDeployment, + LangUtils.init(Constants.SERVICE_PID, NodeConstants.NODE_DEPLOYMENT_PID)); + } + + return nodeRepo; + } + + @Override + public void modifiedService(ServiceReference reference, JackrabbitRepository service) { + } + + @Override + public void removedService(ServiceReference reference, JackrabbitRepository service) { + } + + } + + private class PrepareHttpStc implements ServiceTrackerCustomizer { + private DataHttp dataHttp; + private NodeHttp nodeHttp; + + @Override + public HttpService addingService(ServiceReference reference) { + HttpService httpService = addHttpService(reference); + return httpService; + } + + @Override + public void modifiedService(ServiceReference reference, HttpService service) { + } + + @Override + public void removedService(ServiceReference reference, HttpService service) { + dataHttp.destroy(); + dataHttp = null; + } + + private HttpService addHttpService(ServiceReference sr) { + HttpService httpService = bc.getService(sr); + // TODO find constants + Object httpPort = sr.getProperty("http.port"); + Object httpsPort = sr.getProperty("https.port"); + dataHttp = new DataHttp(httpService); + nodeHttp = new NodeHttp(httpService, bc); + if (log.isDebugEnabled()) + log.debug(httpPortsMsg(httpPort, httpsPort)); + return httpService; + } + + } + + /* + * ACCESSORS + */ + public Locale getDefaultLocale() { + return defaultLocale; + } + + public List getLocales() { + return locales; + } + + /* + * STATIC + */ + private static String httpPortsMsg(Object httpPort, Object httpsPort) { + return "HTTP " + httpPort + (httpsPort != null ? " - HTTPS " + httpsPort : ""); + } + + /** Workaround for blocking Gogo shell by system shutdown. */ + private 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; + System.exit(0); + } + } + + private static ThreadGroup getRootThreadGroup(ThreadGroup tg) { + if (tg == null) + tg = Thread.currentThread().getThreadGroup(); + if (tg.getParent() == null) + return tg; + else + return getRootThreadGroup(tg.getParent()); + } + + private static 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 static Thread findGogoShellThread(ThreadGroup rootThreadGroup) { + Thread[] threads = new Thread[rootThreadGroup.activeCount()]; + rootThreadGroup.enumerate(threads, true); + for (Thread thread : threads) { + if (thread.getName().equals("Gogo shell")) + return thread; + } + return null; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java index a1f966152..88f79c5ea 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java @@ -37,10 +37,15 @@ import org.argeo.cms.auth.HttpRequestCallback; import org.argeo.cms.auth.HttpRequestCallbackHandler; import org.argeo.jcr.ArgeoJcrConstants; import org.argeo.jcr.JcrUtils; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; import org.osgi.service.http.HttpContext; import org.osgi.service.http.HttpService; import org.osgi.service.http.NamespaceException; import org.osgi.service.useradmin.Authorization; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; /** * Intercepts and enriches http access, mainly focusing on security and @@ -55,7 +60,9 @@ class DataHttp implements KernelConstants, ArgeoJcrConstants { private final static String DEFAULT_PROTECTED_HANDLERS = "/org/argeo/cms/internal/kernel/protectedHandlers.xml"; + private final BundleContext bc; private final HttpService httpService; + private final ServiceTracker repositories; // FIXME Make it more unique private String httpAuthRealm = "Argeo"; @@ -64,13 +71,15 @@ class DataHttp implements KernelConstants, ArgeoJcrConstants { private OpenInViewSessionProvider sessionProvider; DataHttp(HttpService httpService) { - this.httpService = httpService; + this.bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); sessionProvider = new OpenInViewSessionProvider(); - // registerRepositoryServlets(ALIAS_NODE, node); + this.httpService = httpService; + repositories = new ServiceTracker<>(bc, Repository.class, new RepositoriesStc()); + repositories.open(); } public void destroy() { - // unregisterRepositoryServlets(ALIAS_NODE); + repositories.close(); } void registerRepositoryServlets(String alias, Repository repository) { @@ -79,20 +88,30 @@ class DataHttp implements KernelConstants, ArgeoJcrConstants { registerWebdavServlet(alias, repository, false); registerRemotingServlet(alias, repository, true); registerRemotingServlet(alias, repository, false); + if (log.isDebugEnabled()) + log.debug("Registered servlets for repository '" + alias + "'"); } catch (Exception e) { - throw new CmsException("Could not register servlets for repository " + alias, e); + throw new CmsException("Could not register servlets for repository '" + alias + "'", e); } } void unregisterRepositoryServlets(String alias) { - // FIXME unregister servlets + try { + httpService.unregister(webdavPath(alias, true)); + httpService.unregister(webdavPath(alias, false)); + httpService.unregister(remotingPath(alias, true)); + httpService.unregister(remotingPath(alias, false)); + if (log.isDebugEnabled()) + log.debug("Unregistered servlets for repository '" + alias + "'"); + } catch (Exception e) { + log.error("Could not unregister servlets for repository '" + alias + "'", e); + } } void registerWebdavServlet(String alias, Repository repository, boolean anonymous) throws NamespaceException, ServletException { WebdavServlet webdavServlet = new WebdavServlet(repository, sessionProvider); - String pathPrefix = anonymous ? WEBDAV_PUBLIC : WEBDAV_PRIVATE; - String path = pathPrefix + "/" + alias; + String path = webdavPath(alias, anonymous); Properties ip = new Properties(); ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, WEBDAV_CONFIG); ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); @@ -101,9 +120,8 @@ class DataHttp implements KernelConstants, ArgeoJcrConstants { void registerRemotingServlet(String alias, Repository repository, boolean anonymous) throws NamespaceException, ServletException { - String pathPrefix = anonymous ? REMOTING_PUBLIC : REMOTING_PRIVATE; RemotingServlet remotingServlet = new RemotingServlet(repository, sessionProvider); - String path = pathPrefix + "/" + alias; + String path = remotingPath(alias, anonymous); Properties ip = new Properties(); ip.setProperty(JcrRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); @@ -114,6 +132,16 @@ class DataHttp implements KernelConstants, ArgeoJcrConstants { httpService.registerServlet(path, remotingServlet, ip, new DataHttpContext(anonymous)); } + private String webdavPath(String alias, boolean anonymous) { + String pathPrefix = anonymous ? WEBDAV_PUBLIC : WEBDAV_PRIVATE; + return pathPrefix + "/" + alias; + } + + private String remotingPath(String alias, boolean anonymous) { + String pathPrefix = anonymous ? REMOTING_PUBLIC : REMOTING_PRIVATE; + return pathPrefix + "/" + alias; + } + private Subject subjectFromRequest(HttpServletRequest request) { Authorization authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION); if (authorization == null) @@ -128,6 +156,33 @@ class DataHttp implements KernelConstants, ArgeoJcrConstants { } } + private class RepositoriesStc implements ServiceTrackerCustomizer { + + @Override + public Repository addingService(ServiceReference reference) { + Repository repository = bc.getService(reference); + Object jcrRepoAlias = reference.getProperty(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS); + if (jcrRepoAlias != null) { + String alias = jcrRepoAlias.toString(); + registerRepositoryServlets(alias, repository); + } + return repository; + } + + @Override + public void modifiedService(ServiceReference reference, Repository service) { + } + + @Override + public void removedService(ServiceReference reference, Repository service) { + Object jcrRepoAlias = reference.getProperty(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS); + if (jcrRepoAlias != null) { + String alias = jcrRepoAlias.toString(); + unregisterRepositoryServlets(alias); + } + } + } + private class DataHttpContext implements HttpContext { private final boolean anonymous; @@ -177,7 +232,7 @@ class DataHttp implements KernelConstants, ArgeoJcrConstants { @Override public URL getResource(String name) { - return Activator.getBundleContext().getBundle().getResource(name); + return KernelUtils.getBundleContext(DataHttp.class).getBundle().getResource(name); } @Override diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/HomeRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/HomeRepository.java new file mode 100644 index 000000000..270a54214 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/HomeRepository.java @@ -0,0 +1,181 @@ +package org.argeo.cms.internal.kernel; + +import java.security.PrivilegedAction; +import java.util.HashSet; +import java.util.Set; + +import javax.jcr.LoginException; +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.security.Privilege; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; + +import org.argeo.ArgeoException; +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.AuthConstants; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.ArgeoTypes; +import org.argeo.jcr.JcrRepositoryWrapper; +import org.argeo.jcr.JcrUtils; +import org.argeo.jcr.UserJcrUtils; + +/** + * Make sure each user has a home directory available in the default workspace. + */ +class HomeRepository extends JcrRepositoryWrapper implements KernelConstants, ArgeoJcrConstants { + // private final Kernel kernel; + + /** The home base path. */ + private String homeBasePath = "/home"; + private String peopleBasePath = ArgeoJcrConstants.PEOPLE_BASE_PATH; + + private Set checkedUsers = new HashSet(); + + public HomeRepository(Repository repository) { + // this.kernel = kernel; + setRepository(repository); + LoginContext lc; + try { + lc = new LoginContext(AuthConstants.LOGIN_CONTEXT_DATA_ADMIN); + lc.login(); + } catch (javax.security.auth.login.LoginException e1) { + throw new CmsException("Cannot login as systrem", e1); + } + Subject.doAs(lc.getSubject(), new PrivilegedAction() { + + @Override + public Void run() { + try { + initJcr(getRepository().login()); + } catch (RepositoryException e) { + throw new CmsException("Cannot init JCR home", e); + } + return null; + } + + }); + } + + @Override + public Session login() throws LoginException, RepositoryException { + Session session = super.login(); + String username = session.getUserID(); + if (username == null) + return session; + if (session.getUserID().equals(AuthConstants.ROLE_ANONYMOUS)) + return session; + + if (checkedUsers.contains(username)) + return session; + Session adminSession = KernelUtils.openAdminSession(getRepository(), session.getWorkspace().getName()); + try { + syncJcr(adminSession, username); + checkedUsers.add(username); + } finally { + JcrUtils.logoutQuietly(adminSession); + } + return session; + } + + /* + * JCR + */ + /** Session is logged out. */ + private void initJcr(Session adminSession) { + try { + JcrUtils.mkdirs(adminSession, homeBasePath); + JcrUtils.mkdirs(adminSession, peopleBasePath); + adminSession.save(); + + JcrUtils.addPrivilege(adminSession, homeBasePath, AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_READ); + JcrUtils.addPrivilege(adminSession, peopleBasePath, AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_ALL); + adminSession.save(); + } catch (RepositoryException e) { + throw new CmsException("Cannot initialize node user admin", e); + } finally { + JcrUtils.logoutQuietly(adminSession); + } + } + + private Node syncJcr(Session session, String username) { + try { + Node userHome = UserJcrUtils.getUserHome(session, username); + if (userHome == null) { + String homePath = generateUserPath(homeBasePath, username); + if (session.itemExists(homePath))// duplicate user id + userHome = session.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath)); + else + userHome = JcrUtils.mkdirs(session, homePath); + // userHome = JcrUtils.mkfolders(session, homePath); + userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); + userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); + session.save(); + + JcrUtils.clearAccessControList(session, homePath, username); + JcrUtils.addPrivilege(session, homePath, username, Privilege.JCR_ALL); + } + + Node userProfile = UserJcrUtils.getUserProfile(session, username); + // new user + if (userProfile == null) { + String personPath = generateUserPath(peopleBasePath, username); + Node personBase; + if (session.itemExists(personPath))// duplicate user id + personBase = session.getNode(personPath).getParent().addNode(JcrUtils.lastPathElement(personPath)); + else + personBase = JcrUtils.mkdirs(session, personPath); + userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE); + userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); + userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); +// userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true); +// userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, true); +// userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, true); +// userProfile.setProperty(ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true); + session.save(); + + JcrUtils.clearAccessControList(session, userProfile.getPath(), username); + JcrUtils.addPrivilege(session, userProfile.getPath(), username, Privilege.JCR_READ); + } + + // Remote roles + // if (roles != null) { + // writeRemoteRoles(userProfile, roles); + // } + if (session.hasPendingChanges()) + session.save(); + return userProfile; + } catch (RepositoryException e) { + JcrUtils.discardQuietly(session); + throw new ArgeoException("Cannot sync node security model for " + username, e); + } + } + + /** Generate path for a new user home */ + private String generateUserPath(String base, String username) { + LdapName dn; + try { + dn = new LdapName(username); + } catch (InvalidNameException e) { + throw new ArgeoException("Invalid name " + username, e); + } + String userId = dn.getRdn(dn.size() - 1).getValue().toString(); + int atIndex = userId.indexOf('@'); + if (atIndex > 0) { + String domain = userId.substring(0, atIndex); + String name = userId.substring(atIndex + 1); + return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/' + domain + '/' + + JcrUtils.firstCharsToPath(name, 2) + '/' + name; + } else if (atIndex == 0 || atIndex == (userId.length() - 1)) { + throw new ArgeoException("Unsupported username " + userId); + } else { + return base + '/' + JcrUtils.firstCharsToPath(userId, 2) + '/' + userId; + } + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jackrabbit/ManagedJackrabbitRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitRepositoryServiceFactory.java similarity index 50% rename from org.argeo.server.jcr/src/org/argeo/jackrabbit/ManagedJackrabbitRepository.java rename to org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitRepositoryServiceFactory.java index 85eff49e8..d10b78baf 100644 --- a/org.argeo.server.jcr/src/org/argeo/jackrabbit/ManagedJackrabbitRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitRepositoryServiceFactory.java @@ -1,17 +1,18 @@ -package org.argeo.jackrabbit; +package org.argeo.cms.internal.kernel; import java.io.File; import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Dictionary; -import java.util.Hashtable; +import java.util.Enumeration; +import java.util.HashMap; import java.util.Map; import java.util.Properties; -import javax.jcr.Credentials; -import javax.jcr.LoginException; -import javax.jcr.NoSuchWorkspaceException; import javax.jcr.RepositoryException; -import javax.jcr.Session; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; @@ -22,76 +23,81 @@ import org.apache.jackrabbit.core.RepositoryImpl; import org.apache.jackrabbit.core.cache.CacheManager; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; -import org.apache.jackrabbit.stats.RepositoryStatisticsImpl; import org.argeo.ArgeoException; +import org.argeo.cms.CmsException; +import org.argeo.jackrabbit.JackrabbitNodeType; +import org.argeo.jcr.ArgeoJcrConstants; import org.argeo.jcr.ArgeoJcrException; -import org.argeo.jcr.JcrRepositoryWrapper; -import org.argeo.jcr.RepoConf; +import org.argeo.node.RepoConf; +import org.argeo.util.LangUtils; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.FrameworkUtil; +import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedService; +import org.osgi.service.cm.ManagedServiceFactory; import org.xml.sax.InputSource; -public class ManagedJackrabbitRepository extends JcrRepositoryWrapper implements ManagedService, JackrabbitRepository { - private final static Log log = LogFactory.getLog(ManagedJackrabbitRepository.class); +public class JackrabbitRepositoryServiceFactory implements ManagedServiceFactory { + private final static Log log = LogFactory.getLog(JackrabbitRepositoryServiceFactory.class); + private final BundleContext bc = FrameworkUtil.getBundle(JackrabbitRepositoryServiceFactory.class) + .getBundleContext(); // Node final static String REPO_TYPE = "repoType"; - // final static String REPO_CONFIGURATION = "argeo.node.repo.configuration"; - // final static String REPO_DEFAULT_WORKSPACE = "defaultWorkspace"; - // final static String REPO_DBURL = "dburl"; - // final static String REPO_DBUSER = "dbuser"; - // final static String REPO_DBPASSWORD = "dbpassword"; - // final static String REPO_MAX_POOL_SIZE = "maxPoolSize"; - // final static String REPO_MAX_CACHE_MB = "maxCacheMB"; - // final static String REPO_BUNDLE_CACHE_MB = "bundleCacheMB"; - // final static String REPO_EXTRACTOR_POOL_SIZE = "extractorPoolSize"; - // final static String REPO_SEARCH_CACHE_SIZE = "searchCacheSize"; - // final static String REPO_MAX_VOLATILE_INDEX_SIZE = - // "maxVolatileIndexSize"; - private Dictionary properties; + private Map repositories = new HashMap(); @Override - public void updated(Dictionary properties) throws ConfigurationException { - this.properties = properties; + public String getName() { + return "Jackrabbit repository service factory"; + } + + @Override + public void updated(String pid, Dictionary properties) throws ConfigurationException { + if (repositories.containsKey(pid)) + throw new ArgeoException("Already a repository registered for " + pid); + if (properties == null) return; - JackrabbitNodeType type = JackrabbitNodeType.valueOf(prop(RepoConf.type).toString()); + if (repositories.containsKey(pid)) { + log.warn("Ignore update of Jackrabbit repository " + pid); + return; + } + try { - repositoryContext = createNode(type); + RepositoryContext repositoryContext = createNode(properties); + repositories.put(pid, repositoryContext); + Dictionary props = LangUtils.init(Constants.SERVICE_PID, pid); + props.put(ArgeoJcrConstants.JCR_REPOSITORY_URI, properties.get(RepoConf.uri.name())); + bc.registerService(JackrabbitRepository.class, repositoryContext.getRepository(), props); } catch (Exception e) { - e.printStackTrace(); - throw new ArgeoException("Cannot create Jackrabbit repository of type " + type, e); + throw new ArgeoException("Cannot create Jackrabbit repository " + pid, e); } - } - - private RepositoryContext repositoryContext; - public ManagedJackrabbitRepository() { - // setBundleContext(Activator.getBundleContext()); - // JackrabbitNodeType type = JackrabbitNodeType.valueOf(prop(REPO_TYPE, - // JackrabbitNodeType.h2.name())); - // try { - // repositoryContext = createNode(type); - // cndFiles = Arrays.asList(DEFAULT_CNDS); - // prepareDataModel(); - // } catch (Exception e) { - // throw new ArgeoException("Cannot create Jackrabbit repository of type - // " + type, e); - // } } - public void destroy() { - ((RepositoryImpl) getRepository()).shutdown(); + @Override + public void deleted(String pid) { + RepositoryContext repositoryContext = repositories.remove(pid); + repositoryContext.getRepository().shutdown(); + if (log.isDebugEnabled()) + log.debug("Deleted repository " + pid); } - RepositoryStatisticsImpl getRepositoryStatistics() { - return repositoryContext.getRepositoryStatistics(); + public void shutdown() { + for (String pid : repositories.keySet()) { + try { + repositories.get(pid).getRepository().shutdown(); + } catch (Exception e) { + log.error("Error when shutting down Jackrabbit repository " + pid, e); + } + } } - private RepositoryConfig getConfiguration(JackrabbitNodeType type, Hashtable props) - throws RepositoryException { + private RepositoryConfig getConfiguration(Dictionary properties) throws RepositoryException { + JackrabbitNodeType type = JackrabbitNodeType.valueOf(prop(properties, RepoConf.type).toString()); ClassLoader cl = getClass().getClassLoader(); InputStream in = null; try { @@ -116,10 +122,7 @@ public class ManagedJackrabbitRepository extends JcrRepositoryWrapper implements if (in == null) throw new ArgeoJcrException("Repository configuration not found"); InputSource config = new InputSource(in); - Properties jackrabbitVars = new Properties(); - // convert values to Strings, otherwise they are skipped - for (String key : props.keySet()) - jackrabbitVars.setProperty(key, props.get(key).toString()); + Properties jackrabbitVars = getConfigurationProperties(type, properties); RepositoryConfig repositoryConfig = RepositoryConfig.create(config, jackrabbitVars); return repositoryConfig; } finally { @@ -127,12 +130,28 @@ public class ManagedJackrabbitRepository extends JcrRepositoryWrapper implements } } - private Hashtable getConfigurationProperties(JackrabbitNodeType type) { - Hashtable props = new Hashtable(); + private Properties getConfigurationProperties(JackrabbitNodeType type, Dictionary properties) { + Properties props = new Properties(); + keys: for (Enumeration keys = properties.keys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + if (key.equals(ConfigurationAdmin.SERVICE_FACTORYPID) || key.equals(Constants.SERVICE_PID)) + continue keys; + String value = prop(properties, RepoConf.valueOf(key)); + if (value != null) + props.put(key, value); + } // home - File osgiInstanceDir = getOsgiInstanceDir(); - File homeDir = new File(osgiInstanceDir, "repos/node"); + // File osgiInstanceDir = getOsgiInstanceDir(); + String homeUri = props.getProperty(RepoConf.uri.name()); + Path homePath; + try { + homePath = Paths.get(new URI(homeUri)); + } catch (URISyntaxException e) { + throw new CmsException("Invalid repository home URI", e); + } + // File homeDir = new File(osgiInstanceDir, "repos/node"); + File homeDir = homePath.toFile(); homeDir.mkdirs(); // home cannot be overridden props.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, homeDir.getAbsolutePath()); @@ -172,35 +191,35 @@ public class ManagedJackrabbitRepository extends JcrRepositoryWrapper implements return props; } - private void setProp(Dictionary props, RepoConf key, String def) { - Object value = prop(key); + private void setProp(Properties props, RepoConf key, String def) { + Object value = props.get(key.name()); if (value == null) value = def; - props.put(key.name(), value); + if (value == null) + value = key.getDefault(); + if (value != null) + props.put(key.name(), value.toString()); } - private void setProp(Dictionary props, RepoConf key) { + private void setProp(Properties props, RepoConf key) { setProp(props, key, null); } - private Object prop(RepoConf key) { - if (properties == null) - throw new ArgeoJcrException("Properties are not set"); + private String prop(Dictionary properties, RepoConf key) { Object value = properties.get(key.name()); if (value == null) - return key.getDefault(); + return key.getDefault() != null ? key.getDefault().toString() : null; else - return value; + return value.toString(); } - private RepositoryContext createNode(JackrabbitNodeType type) throws RepositoryException { - Hashtable props = getConfigurationProperties(type); - RepositoryConfig repositoryConfig = getConfiguration(type, props); + private RepositoryContext createNode(Dictionary properties) throws RepositoryException { + RepositoryConfig repositoryConfig = getConfiguration(properties); RepositoryContext repositoryContext = createJackrabbitRepository(repositoryConfig); RepositoryImpl repository = repositoryContext.getRepository(); // cache - Object maxCacheMbStr = prop(RepoConf.maxCacheMB); + Object maxCacheMbStr = prop(properties, RepoConf.maxCacheMB); if (maxCacheMbStr != null) { Integer maxCacheMB = Integer.parseInt(maxCacheMbStr.toString()); CacheManager cacheManager = repository.getCacheManager(); @@ -208,14 +227,12 @@ public class ManagedJackrabbitRepository extends JcrRepositoryWrapper implements cacheManager.setMaxMemoryPerCache((maxCacheMB / 4) * 1024l * 1024l); } - // wrap the repository - setRepository(repository); return repositoryContext; } private RepositoryContext createJackrabbitRepository(RepositoryConfig repositoryConfig) throws RepositoryException { ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(ManagedJackrabbitRepository.class.getClassLoader()); + Thread.currentThread().setContextClassLoader(JackrabbitRepositoryServiceFactory.class.getClassLoader()); try { long begin = System.currentTimeMillis(); // @@ -234,37 +251,4 @@ public class ManagedJackrabbitRepository extends JcrRepositoryWrapper implements } } - /* - * DATA MODEL - */ - - public synchronized void waitForInit() { - while (repositoryContext == null) - try { - wait(100); - } catch (InterruptedException e) { - return; - } - } - - private final static String OSGI_INSTANCE_AREA = "osgi.instance.area"; - - private File getOsgiInstanceDir() { - String instanceArea = System.getProperty(OSGI_INSTANCE_AREA); - return new File(instanceArea.substring("file:".length())).getAbsoluteFile(); - } - - @Override - public Session login(Credentials credentials, String workspaceName, Map attributes) - throws LoginException, NoSuchWorkspaceException, RepositoryException { - // TODO Auto-generated method stub - return null; - } - - @Override - public void shutdown() { - destroy(); - - } - } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java index a1988a218..65e38912c 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java @@ -3,39 +3,40 @@ package org.argeo.cms.internal.kernel; import static bitronix.tm.TransactionManagerServices.getTransactionManager; import static bitronix.tm.TransactionManagerServices.getTransactionSynchronizationRegistry; import static java.util.Locale.ENGLISH; -import static org.argeo.cms.internal.kernel.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp; import static org.argeo.cms.internal.kernel.KernelUtils.getOsgiInstanceDir; -import static org.argeo.jcr.ArgeoJcrConstants.ALIAS_NODE; -import static org.argeo.jcr.ArgeoJcrConstants.JCR_REPOSITORY_ALIAS; +import static org.argeo.node.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; import static org.argeo.util.LocaleChoice.asLocaleList; import static org.osgi.framework.Constants.FRAMEWORK_UUID; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileFilter; +import java.io.FilePermission; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.lang.management.ManagementFactory; +import java.lang.reflect.ReflectPermission; +import java.net.SocketPermission; import java.net.URL; +import java.security.AllPermission; import java.security.PrivilegedAction; import java.util.Dictionary; -import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.PropertyPermission; import java.util.Set; -import javax.jcr.ImportUUIDBehavior; import javax.jcr.Repository; -import javax.jcr.RepositoryException; import javax.jcr.RepositoryFactory; import javax.jcr.Session; -import javax.jcr.SimpleCredentials; +import javax.management.MBeanPermission; +import javax.management.MBeanServerPermission; +import javax.management.MBeanTrustPermission; +import javax.security.auth.AuthPermission; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; @@ -53,22 +54,21 @@ import org.argeo.ArgeoException; import org.argeo.ArgeoLogger; import org.argeo.cms.CmsException; import org.argeo.cms.maintenance.MaintenanceUi; -import org.argeo.jackrabbit.JackrabbitDataModel; -import org.argeo.jackrabbit.ManagedJackrabbitRepository; import org.argeo.jackrabbit.OsgiJackrabbitRepositoryFactory; import org.argeo.jcr.ArgeoJcrConstants; -import org.argeo.jcr.ArgeoJcrUtils; import org.argeo.jcr.JcrUtils; -import org.argeo.jcr.RepoConf; +import org.argeo.node.DataModelNamespace; +import org.argeo.node.NodeConstants; +import org.argeo.node.RepoConf; import org.eclipse.equinox.http.jetty.JettyConfigurator; import org.eclipse.equinox.http.jetty.JettyConstants; -import org.eclipse.equinox.http.servlet.ExtendedHttpService; import org.eclipse.rap.rwt.application.ApplicationConfiguration; +import org.osgi.framework.AdminPermission; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; -import org.osgi.framework.ServiceEvent; -import org.osgi.framework.ServiceListener; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServicePermission; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.framework.startlevel.BundleStartLevel; @@ -77,10 +77,19 @@ import org.osgi.framework.wiring.BundleWire; import org.osgi.framework.wiring.BundleWiring; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; -import org.osgi.service.cm.ManagedService; +import org.osgi.service.cm.ConfigurationPermission; +import org.osgi.service.cm.ManagedServiceFactory; +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.service.useradmin.UserAdmin; import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; import bitronix.tm.BitronixTransactionManager; import bitronix.tm.BitronixTransactionSynchronizationRegistry; @@ -97,11 +106,15 @@ import bitronix.tm.TransactionManagerServices; *
  • OS access
  • * */ -final class Kernel implements KernelHeader, KernelConstants, ServiceListener { +final class Kernel implements KernelHeader, KernelConstants { /* * SERVICE REFERENCES */ - private ServiceReference configurationAdmin; + // private ServiceReference configurationAdmin; + private final ServiceTracker configurationAdmin; + private final ServiceTracker logReaderService; + private final ServiceTracker httpService; + private final ConditionalPermissionAdmin permissionAdmin; /* * REGISTERED SERVICES */ @@ -109,7 +122,7 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { private ServiceRegistration tmReg; private ServiceRegistration utReg; private ServiceRegistration tsrReg; - private ServiceRegistration repositoryReg; + private ServiceRegistration repositoryReg; private ServiceRegistration repositoryFactoryReg; private ServiceRegistration userAdminReg; @@ -120,14 +133,15 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { private BitronixTransactionManager transactionManager; private BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry; private OsgiJackrabbitRepositoryFactory repositoryFactory; - JackrabbitRepository repository; + private Repository repository; private NodeUserAdmin userAdmin; // Members + private final BundleContext bc;// = Activator.getBundleContext(); + private final NodeSecurity nodeSecurity; + private final static Log log = LogFactory.getLog(Kernel.class); ThreadGroup threadGroup = new ThreadGroup(Kernel.class.getSimpleName()); - private final BundleContext bc = Activator.getBundleContext(); - private final NodeSecurity nodeSecurity; private DataHttp dataHttp; private NodeHttp nodeHttp; private KernelThread kernelThread; @@ -138,8 +152,280 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { public Kernel() { // KernelUtils.logFrameworkProperties(log); nodeSecurity = new NodeSecurity(); + bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + configurationAdmin = new ServiceTracker(bc, ConfigurationAdmin.class, + new PrepareStc()); + configurationAdmin.open(); + logReaderService = new ServiceTracker(bc, LogReaderService.class, + new PrepareStc()); + logReaderService.open(); + httpService = new ServiceTracker(bc, HttpService.class, new PrepareHttpStc()); + httpService.open(); + + permissionAdmin = bc.getService(bc.getServiceReference(ConditionalPermissionAdmin.class)); + + applySystemPermissions(); + } + + private void applySystemPermissions() { + ConditionalPermissionUpdate update = permissionAdmin.newConditionalPermissionUpdate(); + // Self + update.getConditionalPermissionInfos() + .add(permissionAdmin.newConditionalPermissionInfo(null, + new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(), + new String[] { locate(Kernel.class) }) }, + 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[] { bc.getBundle(0).getLocation() }) }, + 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)); + + // 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(), "getLoginConfiguration", null), + new PermissionInfo(AuthPermission.class.getName(), "createLoginContext.Jackrabbit", null), }, + 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(), "createLoginContext.Jackrabbit", null), }, + ConditionalPermissionInfo.ALLOW)); + 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"), + 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 */ + private String locate(Class clzz) { + return FrameworkUtil.getBundle(clzz).getLocation(); + } + + /* + * PACKAGE RESTRICTED INTERFACE + */ + Subject getKernelSubject() { + return nodeSecurity.getKernelSubject(); + } + + /* + * INITIALISATION + */ + final void init() { Subject.doAs(nodeSecurity.getKernelSubject(), new PrivilegedAction() { @Override @@ -157,25 +443,27 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { Thread.currentThread().setContextClassLoader(Kernel.class.getClassLoader()); try { // Listen to service publication (also ours) - bc.addServiceListener(Kernel.this); + // bc.addServiceListener(Kernel.this); if (nodeSecurity.isFirstInit()) firstInit(); - defaultLocale = new Locale(getFrameworkProp(I18N_DEFAULT_LOCALE, ENGLISH.getLanguage())); - locales = asLocaleList(getFrameworkProp(I18N_LOCALES)); + defaultLocale = new Locale(getFrameworkProp(NodeConstants.I18N_DEFAULT_LOCALE, ENGLISH.getLanguage())); + locales = asLocaleList(getFrameworkProp(NodeConstants.I18N_LOCALES)); - ServiceTracker logReaderService = new ServiceTracker( - bc, LogReaderService.class, null); - logReaderService.open(); + // ServiceTracker + // logReaderService = new ServiceTracker( + // bc, LogReaderService.class, null); + // logReaderService.open(); logger = new NodeLogger(logReaderService.getService()); - logReaderService.close(); + // logReaderService.close(); if (isMaintenance()) maintenanceInit(); else normalInit(); - } catch (Exception e) { + } catch (Throwable e) { log.error("Cannot initialize Argeo CMS", e); throw new ArgeoException("Cannot initialize", e); } finally { @@ -211,10 +499,18 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { // Initialise services initTransactionManager(); + JackrabbitRepositoryServiceFactory jrsf = new JackrabbitRepositoryServiceFactory(); + String[] clazzes = { ManagedServiceFactory.class.getName() }; + Hashtable serviceProps = new Hashtable(); + serviceProps.put(Constants.SERVICE_PID, ArgeoJcrConstants.JACKRABBIT_REPO_FACTORY_PID); + bc.registerService(clazzes, jrsf, serviceProps); + try { - Configuration nodeConf = conf.getConfiguration(ArgeoJcrConstants.REPO_PID_NODE); + Configuration nodeConf = conf.createFactoryConfiguration(ArgeoJcrConstants.JACKRABBIT_REPO_FACTORY_PID); + // Configuration nodeConf = + // conf.getConfiguration(ArgeoJcrConstants.REPO_PID_NODE); if (nodeConf.getProperties() == null) { - Dictionary props = getNodeConfigFromFrameworkProperties(); + Dictionary props = getNodeConfigFromFrameworkProperties(); if (props == null) { // TODO interactive configuration if (log.isDebugEnabled()) @@ -222,33 +518,84 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { + " property defined, entering interactive mode..."); return; } + // props.put(ConfigurationAdmin.SERVICE_FACTORYPID, + // ArgeoJcrConstants.JACKRABBIT_REPO_FACTORY_PID); + props.put(Constants.SERVICE_PID, ArgeoJcrConstants.REPO_PID_NODE); nodeConf.update(props); } } catch (IOException e) { throw new CmsException("Cannot get configuration", e); } - ManagedJackrabbitRepository nodeRepo = new ManagedJackrabbitRepository(); - String[] clazzes = { ManagedService.class.getName(), Repository.class.getName(), - JackrabbitRepository.class.getName() }; - Hashtable serviceProps = new Hashtable(); - serviceProps.put(Constants.SERVICE_PID, ArgeoJcrConstants.REPO_PID_NODE); - serviceProps.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, ArgeoJcrConstants.ALIAS_NODE); - ServiceRegistration nodeSr = bc.registerService(clazzes, nodeRepo, serviceProps); - nodeRepo.waitForInit(); + // ManagedJackrabbitRepository nodeRepo = new + // ManagedJackrabbitRepository(); + // String[] clazzes = { ManagedService.class.getName(), + // Repository.class.getName(), + // JackrabbitRepository.class.getName() }; + // Hashtable serviceProps = new Hashtable(); + // serviceProps.put(Constants.SERVICE_PID, + // ArgeoJcrConstants.REPO_PID_NODE); + // serviceProps.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, + // ArgeoJcrConstants.ALIAS_NODE); + // repositoryReg = bc.registerService(clazzes, nodeRepo, serviceProps); + // nodeRepo.waitForInit(); - new JackrabbitDataModel(bc).prepareDataModel(nodeRepo); - prepareDataModel(nodeRepo); + ServiceTracker jackrabbitSt = new ServiceTracker<>(bc, + JackrabbitRepository.class, new ServiceTrackerCustomizer() { - repository = (JackrabbitRepository) bc.getService(nodeSr.getReference()); + @Override + public JackrabbitRepository addingService(ServiceReference reference) { + JackrabbitRepository nodeRepo = bc.getService(reference); + // new + // JackrabbitDataModel(bc).prepareDataModel(nodeRepo); + prepareDataModel(KernelUtils.openAdminSession( nodeRepo)); + + // repository = (JackrabbitRepository) + // bc.getService(repositoryReg.getReference()); + repository = new HomeRepository( nodeRepo); + Hashtable regProps = new Hashtable(); + regProps.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, ArgeoJcrConstants.ALIAS_NODE); + repositoryReg = (ServiceRegistration) bc.registerService(Repository.class, + repository, regProps); + + // if (repository == null) + // repository = new NodeRepository(); + if (repositoryFactory == null) { + repositoryFactory = new OsgiJackrabbitRepositoryFactory(); + repositoryFactory.setBundleContext(bc); + repositoryFactoryReg = bc.registerService(RepositoryFactory.class, repositoryFactory, null); + } + userAdmin = new NodeUserAdmin(transactionManager, repository); + userAdminReg = bc.registerService(UserAdmin.class, userAdmin, userAdmin.currentState()); + return nodeRepo; + } - if (repository == null) - repository = new NodeRepository(); - if (repositoryFactory == null) { - repositoryFactory = new OsgiJackrabbitRepositoryFactory(); - repositoryFactory.setBundleContext(bc); - } - userAdmin = new NodeUserAdmin(transactionManager, repository); + @Override + public void modifiedService(ServiceReference reference, + JackrabbitRepository service) { + } + + @Override + public void removedService(ServiceReference reference, + JackrabbitRepository service) { + } + }); + jackrabbitSt.open(); + + // new JackrabbitDataModel(bc).prepareDataModel(nodeRepo); + // prepareDataModel(nodeRepo); + // + // repository = (JackrabbitRepository) + // bc.getService(repositoryReg.getReference()); + // + //// if (repository == null) + //// repository = new NodeRepository(); + // if (repositoryFactory == null) { + // repositoryFactory = new OsgiJackrabbitRepositoryFactory(); + // repositoryFactory.setBundleContext(bc); + // } + // userAdmin = new NodeUserAdmin(transactionManager, repository); // ADMIN UIs UserUi userUi = new UserUi(); @@ -285,25 +632,24 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { publish(); } - private Dictionary getNodeConfigFromFrameworkProperties() { - String repoType = KernelUtils.getFrameworkProp(KernelConstants.NODE_REPO_PROP_PREFIX + RepoConf.type.name()); + private Dictionary getNodeConfigFromFrameworkProperties() { + String repoType = KernelUtils.getFrameworkProp(NodeConstants.NODE_REPO_PROP_PREFIX + RepoConf.type.name()); if (repoType == null) return null; Hashtable props = new Hashtable(); for (RepoConf repoConf : RepoConf.values()) { - String value = KernelUtils.getFrameworkProp(KernelConstants.NODE_REPO_PROP_PREFIX + repoConf.name()); + String value = KernelUtils.getFrameworkProp(NodeConstants.NODE_REPO_PROP_PREFIX + repoConf.name()); if (value != null) props.put(repoConf.name(), value); } return props; } - private void prepareDataModel(ManagedJackrabbitRepository nodeRepo) { - Session adminSession = null; + /** Session is logged out. */ + private void prepareDataModel(Session adminSession) { try { Set processed = new HashSet(); - adminSession = nodeRepo.login(); bundles: for (Bundle bundle : bc.getBundles()) { BundleWiring wiring = bundle.adapt(BundleWiring.class); if (wiring == null) { @@ -313,8 +659,6 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { } processWiring(adminSession, wiring, processed); } - } catch (RepositoryException e) { - throw new CmsException("Cannot prepare data model", e); } finally { JcrUtils.logoutQuietly(adminSession); } @@ -375,7 +719,7 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { private void maintenanceInit() { log.info("## MAINTENANCE ##"); - bc.addServiceListener(Kernel.this); + // bc.addServiceListener(Kernel.this); initWebServer(null); MaintenanceUi maintenanceUi = new MaintenanceUi(); Hashtable props = new Hashtable(); @@ -385,11 +729,11 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { private void firstInit() { log.info("## FIRST INIT ##"); - String nodeInit = getFrameworkProp(NODE_INIT); + String nodeInit = getFrameworkProp(NodeConstants.NODE_INIT); if (nodeInit == null) nodeInit = "../../init"; if (nodeInit.startsWith("http")) { - remoteFirstInit(nodeInit); + // remoteFirstInit(nodeInit); return; } File initDir; @@ -415,34 +759,39 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { } } - private void remoteFirstInit(String uri) { - try { - repository = new NodeRepository(); - repositoryFactory = new OsgiJackrabbitRepositoryFactory(); - Repository remoteRepository = ArgeoJcrUtils.getRepositoryByUri(repositoryFactory, uri); - Session remoteSession = remoteRepository.login(new SimpleCredentials("root", "demo".toCharArray()), "main"); - Session localSession = this.repository.login(); - // FIXME register node type - // if (false) - // CndImporter.registerNodeTypes(null, localSession); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - remoteSession.exportSystemView("/", out, true, false); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - localSession.importXML("/", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW); - // JcrUtils.copy(remoteSession.getRootNode(), - // localSession.getRootNode()); - } catch (Exception e) { - throw new CmsException("Cannot first init from " + uri, e); - } - } + // private void remoteFirstInit(String uri) { + // try { + // repository = new NodeRepository(); + // repositoryFactory = new OsgiJackrabbitRepositoryFactory(); + // Repository remoteRepository = + // ArgeoJcrUtils.getRepositoryByUri(repositoryFactory, uri); + // Session remoteSession = remoteRepository.login(new + // SimpleCredentials("root", "demo".toCharArray()), "main"); + // Session localSession = this.repository.login(); + // // FIXME register node type + // // if (false) + // // CndImporter.registerNodeTypes(null, localSession); + // ByteArrayOutputStream out = new ByteArrayOutputStream(); + // remoteSession.exportSystemView("/", out, true, false); + // ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + // localSession.importXML("/", in, + // ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW); + // // JcrUtils.copy(remoteSession.getRootNode(), + // // localSession.getRootNode()); + // } catch (Exception e) { + // throw new CmsException("Cannot first init from " + uri, e); + // } + // } /** Can be null */ private ConfigurationAdmin findConfigurationAdmin() { - configurationAdmin = bc.getServiceReference(ConfigurationAdmin.class); - if (configurationAdmin == null) { - return null; - } - return bc.getService(configurationAdmin); + // configurationAdmin = + // bc.getServiceReference(ConfigurationAdmin.class); + // if (configurationAdmin == null) { + // return null; + // } + // return bc.getService(configurationAdmin); + return configurationAdmin.getService(); } /** Can be null */ @@ -459,7 +808,9 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { // File tmBaseDir = new File(getFrameworkProp(TRANSACTIONS_HOME, // getOsgiInstancePath(DIR_TRANSACTIONS))); - File tmBaseDir = bc.getDataFile(DIR_TRANSACTIONS); + Bundle bitronixBundle = FrameworkUtil.getBundle(bitronix.tm.Configuration.class); + File tmBaseDir = bitronixBundle.getDataFile(DIR_TRANSACTIONS); + // File tmBaseDir = bc.getDataFile(DIR_TRANSACTIONS); File tmDir1 = new File(tmBaseDir, "btm1"); tmDir1.mkdirs(); tmConf.setLogPart1Filename(new File(tmDir1, tmDir1.getName() + ".tlog").getAbsolutePath()); @@ -470,12 +821,12 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { transactionSynchronizationRegistry = getTransactionSynchronizationRegistry(); } - private void initWebServer(ConfigurationAdmin conf) { + private void initWebServer(final ConfigurationAdmin conf) { String httpPort = getFrameworkProp("org.osgi.service.http.port"); String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure"); try { if (httpPort != null || httpsPort != null) { - Hashtable jettyProps = new Hashtable(); + final Hashtable jettyProps = new Hashtable(); if (httpPort != null) { jettyProps.put(JettyConstants.HTTP_PORT, httpPort); jettyProps.put(JettyConstants.HTTP_ENABLED, true); @@ -496,6 +847,7 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { return; Configuration jettyConf = conf.createFactoryConfiguration(JETTY_FACTORY_PID, null); jettyConf.update(jettyProps); + } else { JettyConfigurator.startServer("default", jettyProps); } @@ -505,7 +857,6 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { } } - @SuppressWarnings("unchecked") private void publish() { // Logging @@ -515,14 +866,17 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { utReg = bc.registerService(UserTransaction.class, transactionManager, null); tsrReg = bc.registerService(TransactionSynchronizationRegistry.class, transactionSynchronizationRegistry, null); // User admin - userAdminReg = bc.registerService(UserAdmin.class, userAdmin, userAdmin.currentState()); + // userAdminReg = bc.registerService(UserAdmin.class, userAdmin, + // userAdmin.currentState()); // JCR - Hashtable regProps = new Hashtable(); - regProps.put(JCR_REPOSITORY_ALIAS, ALIAS_NODE); - repositoryReg = (ServiceRegistration) bc.registerService( - new String[] { Repository.class.getName(), JackrabbitRepository.class.getName() }, repository, - regProps); - repositoryFactoryReg = bc.registerService(RepositoryFactory.class, repositoryFactory, null); + // Hashtable regProps = new Hashtable(); + // regProps.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, + // ArgeoJcrConstants.ALIAS_NODE); + // repositoryReg = (ServiceRegistration) + // bc.registerService(Repository.class, repository, + // regProps); + // repositoryFactoryReg = bc.registerService(RepositoryFactory.class, + // repositoryFactory, null); } void destroy() { @@ -537,12 +891,12 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { nodeHttp.destroy(); if (userAdmin != null) userAdmin.destroy(); - if (repository != null) - repository.shutdown(); + // if (repository != null) + // repository.shutdown(); if (transactionManager != null) transactionManager.shutdown(); - bc.removeServiceListener(this); + // bc.removeServiceListener(this); // Clean hanging threads from Jackrabbit TransientFileFactory.shutdown(); @@ -565,44 +919,46 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { loggerReg.unregister(); } - @Override - public void serviceChanged(ServiceEvent event) { - ServiceReference sr = event.getServiceReference(); - Object service = bc.getService(sr); - if (service instanceof Repository) { - Object jcrRepoAlias = sr.getProperty(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS); - if (jcrRepoAlias != null) {// JCR repository - String alias = jcrRepoAlias.toString(); - Repository repository = (Repository) bc.getService(sr); - Map props = new HashMap(); - for (String key : sr.getPropertyKeys()) - props.put(key, sr.getProperty(key)); - if (ServiceEvent.REGISTERED == event.getType()) { - try { - // repositoryFactory.register(repository, props); - dataHttp.registerRepositoryServlets(alias, repository); - } catch (Exception e) { - throw new CmsException("Could not publish JCR repository " + alias, e); - } - } else if (ServiceEvent.UNREGISTERING == event.getType()) { - // repositoryFactory.unregister(repository, props); - dataHttp.unregisterRepositoryServlets(alias); - } - } - } else if (service instanceof ExtendedHttpService) { - if (ServiceEvent.REGISTERED == event.getType()) { - addHttpService(sr); - } else if (ServiceEvent.UNREGISTERING == event.getType()) { - dataHttp.destroy(); - dataHttp = null; - } - } - } - - private void addHttpService(ServiceReference sr) { + // @Override + // public void serviceChanged(ServiceEvent event) { + // ServiceReference sr = event.getServiceReference(); + // Object service = bc.getService(sr); + // if (service instanceof Repository) { + // Object jcrRepoAlias = + // sr.getProperty(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS); + // if (jcrRepoAlias != null) {// JCR repository + // String alias = jcrRepoAlias.toString(); + // Repository repository = (Repository) bc.getService(sr); + // Map props = new HashMap(); + // for (String key : sr.getPropertyKeys()) + // props.put(key, sr.getProperty(key)); + // if (ServiceEvent.REGISTERED == event.getType()) { + // try { + // // repositoryFactory.register(repository, props); + // dataHttp.registerRepositoryServlets(alias, repository); + // } catch (Exception e) { + // throw new CmsException("Could not publish JCR repository " + alias, e); + // } + // } else if (ServiceEvent.UNREGISTERING == event.getType()) { + // // repositoryFactory.unregister(repository, props); + // dataHttp.unregisterRepositoryServlets(alias); + // } + // } + // } + // // else if (service instanceof ExtendedHttpService) { + // // if (ServiceEvent.REGISTERED == event.getType()) { + // // addHttpService(sr); + // // } else if (ServiceEvent.UNREGISTERING == event.getType()) { + // // dataHttp.destroy(); + // // dataHttp = null; + // // } + // // } + // } + + private HttpService addHttpService(ServiceReference sr) { // for (String key : sr.getPropertyKeys()) // log.debug(key + "=" + sr.getProperty(key)); - ExtendedHttpService httpService = (ExtendedHttpService) bc.getService(sr); + HttpService httpService = bc.getService(sr); // TODO find constants Object httpPort = sr.getProperty("http.port"); Object httpsPort = sr.getProperty("https.port"); @@ -610,6 +966,7 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { nodeHttp = new NodeHttp(httpService, bc); if (log.isDebugEnabled()) log.debug(httpPortsMsg(httpPort, httpsPort)); + return httpService; } private String httpPortsMsg(Object httpPort, Object httpsPort) { @@ -644,6 +1001,47 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { log.debug("Sleep accuracy: " + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100)) + " %"); } + private class PrepareStc implements ServiceTrackerCustomizer { + + @Override + public T addingService(ServiceReference reference) { + T service = bc.getService(reference); + System.out.println("addingService " + service); + return service; + } + + @Override + public void modifiedService(ServiceReference reference, T service) { + System.out.println("modifiedService " + service); + } + + @Override + public void removedService(ServiceReference reference, T service) { + System.out.println("removedService " + service); + } + + } + + private class PrepareHttpStc implements ServiceTrackerCustomizer { + + @Override + public HttpService addingService(ServiceReference reference) { + HttpService httpService = addHttpService(reference); + return httpService; + } + + @Override + public void modifiedService(ServiceReference reference, HttpService service) { + } + + @Override + public void removedService(ServiceReference reference, HttpService service) { + dataHttp.destroy(); + dataHttp = null; + } + + } + /** Workaround for blocking Gogo shell by system shutdown. */ private class GogoShellKiller extends Thread { 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 index b179ec045..fa746521f 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java @@ -1,39 +1,10 @@ package org.argeo.cms.internal.kernel; public interface KernelConstants { - final static String NODE_INIT = "argeo.node.init"; + - // Node - /** Properties configuring the node repository */ - final static String NODE_REPO_PROP_PREFIX = "argeo.node.repo."; - // final static String REPO_HOME = "argeo.node.repo.home"; - // final static String REPO_TYPE = "argeo.node.repo.type"; - // final static String REPO_CONFIGURATION = "argeo.node.repo.configuration"; - // final static String REPO_DEFAULT_WORKSPACE = - // "argeo.node.repo.defaultWorkspace"; - // final static String REPO_DBURL = "argeo.node.repo.dburl"; - // final static String REPO_DBUSER = "argeo.node.repo.dbuser"; - // final static String REPO_DBPASSWORD = "argeo.node.repo.dbpassword"; - // final static String REPO_MAX_POOL_SIZE = "argeo.node.repo.maxPoolSize"; - // final static String REPO_MAX_CACHE_MB = "argeo.node.repo.maxCacheMB"; - // final static String REPO_BUNDLE_CACHE_MB = - // "argeo.node.repo.bundleCacheMB"; - // final static String REPO_EXTRACTOR_POOL_SIZE = - // "argeo.node.repo.extractorPoolSize"; - // final static String REPO_SEARCH_CACHE_SIZE = - // "argeo.node.repo.searchCacheSize"; - // final static String REPO_MAX_VOLATILE_INDEX_SIZE = - // "argeo.node.repo.maxVolatileIndexSize"; + //final static String TRANSACTIONS_HOME = "argeo.node.transactions.home"; - final static String TRANSACTIONS_HOME = "argeo.node.transactions.home"; - - final static String I18N_DEFAULT_LOCALE = "argeo.i18n.defaultLocale"; - final static String I18N_LOCALES = "argeo.i18n.locales"; - - // Node Security - final static String ROLES_URI = "argeo.node.roles.uri"; - /** URI to an LDIF file or LDAP server used as initialization or backend */ - final static String USERADMIN_URIS = "argeo.node.useradmin.uris"; final static String[] DEFAULT_CNDS = { "/org/argeo/jcr/argeo.cnd", "/org/argeo/cms/cms.cnd" }; // Directories 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 index 81c9242b2..9b220f20a 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java @@ -3,12 +3,17 @@ package org.argeo.cms.internal.kernel; import java.io.File; import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; +import java.security.PrivilegedAction; import java.util.Dictionary; import java.util.Enumeration; import java.util.Hashtable; import java.util.Properties; import java.util.TreeSet; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; @@ -17,7 +22,9 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.argeo.cms.CmsException; import org.argeo.cms.auth.AuthConstants; +import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; /** Package utilities */ class KernelUtils implements KernelConstants { @@ -37,8 +44,7 @@ class KernelUtils implements KernelConstants { try { props.load(cl.getResourceAsStream(resource)); } catch (IOException e) { - throw new CmsException("Cannot load " + resource - + " from classpath", e); + throw new CmsException("Cannot load " + resource + " from classpath", e); } return asDictionary(props); } @@ -55,37 +61,41 @@ class KernelUtils implements KernelConstants { } static File getOsgiInstanceDir() { - return new File(Activator.getBundleContext() - .getProperty(OSGI_INSTANCE_AREA).substring("file:".length())) + return new File(getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length())) .getAbsoluteFile(); } + static URI getOsgiInstanceUri(String relativePath) { + String osgiInstanceBaseUri = getFrameworkProp(OSGI_INSTANCE_AREA); + try { + return new URI(osgiInstanceBaseUri + (relativePath != null ? relativePath : "")); + } catch (URISyntaxException e) { + throw new CmsException("Cannot get OSGi instance URI for " + relativePath, e); + } + } + static String getOsgiInstancePath(String relativePath) { try { if (relativePath == null) return getOsgiInstanceDir().getCanonicalPath(); else - return new File(getOsgiInstanceDir(), relativePath) - .getCanonicalPath(); + return new File(getOsgiInstanceDir(), relativePath).getCanonicalPath(); } catch (IOException e) { - throw new CmsException("Cannot get instance path for " - + relativePath, e); + throw new CmsException("Cannot get instance path for " + relativePath, e); } } static File getOsgiConfigurationFile(String relativePath) { try { - return new File(new URI(Activator.getBundleContext().getProperty( - OSGI_CONFIGURATION_AREA) - + relativePath)).getCanonicalFile(); + return new File(new URI(getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath)) + .getCanonicalFile(); } catch (Exception e) { - throw new CmsException("Cannot get configuration file for " - + relativePath, e); + throw new CmsException("Cannot get configuration file for " + relativePath, e); } } static String getFrameworkProp(String key, String def) { - String value = Activator.getBundleContext().getProperty(key); + String value = getBundleContext().getProperty(key); if (value == null) return def; return value; @@ -100,8 +110,7 @@ class KernelUtils implements KernelConstants { Subject subject = new Subject(); LoginContext lc; try { - lc = new LoginContext(AuthConstants.LOGIN_CONTEXT_ANONYMOUS, - subject); + lc = new LoginContext(AuthConstants.LOGIN_CONTEXT_ANONYMOUS, subject); lc.login(); return subject; } catch (LoginException e) { @@ -133,19 +142,17 @@ class KernelUtils implements KernelConstants { static void logRequestHeaders(Log log, HttpServletRequest request) { if (!log.isDebugEnabled()) return; - for (Enumeration headerNames = request.getHeaderNames(); headerNames - .hasMoreElements();) { + for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) { String headerName = headerNames.nextElement(); Object headerValue = request.getHeader(headerName); log.debug(headerName + ": " + headerValue); } - log.debug(request.getRequestURI()+"\n"); + log.debug(request.getRequestURI() + "\n"); } static void logFrameworkProperties(Log log) { - BundleContext bc = Activator.getBundleContext(); - for (Object sysProp : new TreeSet(System.getProperties() - .keySet())) { + BundleContext bc = getBundleContext(); + for (Object sysProp : new TreeSet(System.getProperties().keySet())) { log.debug(sysProp + "=" + bc.getProperty(sysProp.toString())); } // String[] keys = { Constants.FRAMEWORK_STORAGE, @@ -159,6 +166,54 @@ class KernelUtils implements KernelConstants { // log.debug(key + "=" + bc.getProperty(key)); } + static Session openAdminSession(Repository repository) { + return openAdminSession(repository, null); + } + + static Session openAdminSession(final Repository repository, final String workspaceName) { + ClassLoader currentCl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(KernelUtils.class.getClassLoader()); + LoginContext loginContext; + try { + loginContext = new LoginContext(AuthConstants.LOGIN_CONTEXT_DATA_ADMIN); + loginContext.login(); + } catch (LoginException e1) { + throw new CmsException("Could not login as data admin", e1); + } finally { + Thread.currentThread().setContextClassLoader(currentCl); + } + return Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { + + @Override + public Session run() { + try { + return repository.login(workspaceName); + } catch (RepositoryException e) { + throw new CmsException("Cannot open admin session", e); + } + } + + }); + } + + /** + * @return the {@link BundleContext} of the {@link Bundle} which provided + * this class, never null. + * @throws CmsException + * if the related bundle is not active + */ + public static BundleContext getBundleContext(Class clzz) { + Bundle bundle = FrameworkUtil.getBundle(clzz); + BundleContext bc = bundle.getBundleContext(); + if (bc == null) + throw new CmsException("Bundle " + bundle.getSymbolicName() + " is not active"); + return bc; + } + + private static BundleContext getBundleContext() { + return getBundleContext(KernelUtils.class); + } + private KernelUtils() { } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java index eee6dcd51..97e912edd 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java @@ -32,9 +32,9 @@ import org.argeo.cms.CmsException; import org.argeo.cms.util.CmsUtils; import org.argeo.jcr.ArgeoJcrConstants; import org.argeo.jcr.JcrUtils; -import org.eclipse.equinox.http.servlet.ExtendedHttpService; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; +import org.osgi.service.http.HttpService; /** * Intercepts and enriches http access, mainly focusing on security and @@ -51,7 +51,7 @@ class NodeHttp implements KernelConstants, ArgeoJcrConstants { private BundleContext bc; - NodeHttp(ExtendedHttpService httpService, BundleContext bc) { + NodeHttp(HttpService httpService, BundleContext bc) { this.bc = bc; // rootFilter = new RootFilter(); // dosFilter = new CustomDosFilter(); 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 index ac0c8469f..de28ac118 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java @@ -139,7 +139,9 @@ class NodeLogger implements ArgeoLogger, LogListener { } else if (severity == LogService.LOG_WARNING) pluginLog.warn(status.getMessage(), status.getException()); else if (severity == LogService.LOG_INFO && pluginLog.isDebugEnabled()) - pluginLog.debug(status.getMessage(), status.getException()); + pluginLog.debug( + status.getMessage() + (status.getServiceReference() != null ?" "+ status.getServiceReference() : ""), + status.getException()); else if (severity == LogService.LOG_DEBUG && pluginLog.isTraceEnabled()) pluginLog.trace(status.getMessage(), status.getException()); } @@ -148,33 +150,28 @@ class NodeLogger implements ArgeoLogger, LogListener { // ARGEO LOGGER // - public synchronized void register(ArgeoLogListener listener, - Integer numberOfPreviousEvents) { + public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) { String username = CurrentUser.getUsername(); if (username == null) - throw new ArgeoException( - "Only authenticated users can register a log listener"); + throw new ArgeoException("Only authenticated users can register a log listener"); if (!userListeners.containsKey(username)) { - List lst = Collections - .synchronizedList(new ArrayList()); + List lst = Collections.synchronizedList(new ArrayList()); userListeners.put(username, lst); } userListeners.get(username).add(listener); - List lastEvents = logDispatcherThread.getLastEvents(username, - numberOfPreviousEvents); + List lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents); for (LogEvent evt : lastEvents) dispatchEvent(listener, evt); } - public synchronized void registerForAll(ArgeoLogListener listener, - Integer numberOfPreviousEvents, boolean everything) { + 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); + List lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents); for (LogEvent evt : lastEvents) if (everything || evt.getUsername() != null) dispatchEvent(listener, evt); @@ -185,11 +182,9 @@ class NodeLogger implements ArgeoLogger, LogListener { if (username == null)// FIXME return; if (!userListeners.containsKey(username)) - throw new ArgeoException("No user listeners " + listener - + " registered for user " + username); + throw new ArgeoException("No user listeners " + listener + " registered for user " + username); if (!userListeners.get(username).contains(listener)) - throw new ArgeoException("No user listeners " + listener - + " registered for user " + username); + throw new ArgeoException("No user listeners " + listener + " registered for user " + username); userListeners.get(username).remove(listener); if (userListeners.get(username).isEmpty()) userListeners.remove(username); @@ -231,7 +226,9 @@ class NodeLogger implements ArgeoLogger, LogListener { return configuration; } - /** Reloads configuration (if the configuration {@link Properties} is set) */ + /** + * Reloads configuration (if the configuration {@link Properties} is set) + */ protected void reloadConfiguration() { if (configuration != null) { LogManager.resetConfiguration(); @@ -251,36 +248,29 @@ class NodeLogger implements ArgeoLogger, LogListener { try { log4jLevel = Level.toLevel(level); } catch (Exception e) { - System.err - .println("Log4j level could not be set for level '" - + level + "', resetting it to null."); + 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)) { + if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) { return; } } try { // admin listeners - Iterator everythingIt = everythingListeners - .iterator(); + Iterator everythingIt = everythingListeners.iterator(); while (everythingIt.hasNext()) dispatchEvent(everythingIt.next(), event); if (event.getUsername() != null) { - Iterator allUsersIt = allUsersListeners - .iterator(); + Iterator allUsersIt = allUsersListeners.iterator(); while (allUsersIt.hasNext()) dispatchEvent(allUsersIt.next(), event); if (userListeners.containsKey(event.getUsername())) { - Iterator userIt = userListeners.get( - event.getUsername()).iterator(); + Iterator userIt = userListeners.get(event.getUsername()).iterator(); while (userIt.hasNext()) dispatchEvent(userIt.next(), event); } @@ -293,10 +283,8 @@ class NodeLogger implements ArgeoLogger, LogListener { 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()); + logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(), + event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep()); } private class AppenderImpl extends AppenderSkeleton { @@ -348,11 +336,9 @@ class NodeLogger implements ArgeoLogger, LogListener { lastEvents.add(loggingEvent); } - public synchronized List getLastEvents(String username, - Integer maxCount) { + public synchronized List getLastEvents(String username, Integer maxCount) { LinkedList evts = new LinkedList(); - ListIterator it = lastEvents.listIterator(lastEvents - .size()); + ListIterator it = lastEvents.listIterator(lastEvents.size()); int count = 0; while (it.hasPrevious() && (count < maxCount)) { LogEvent evt = it.previous(); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeRepository.java deleted file mode 100644 index 0bdb9e3a4..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeRepository.java +++ /dev/null @@ -1,166 +0,0 @@ -package org.argeo.cms.internal.kernel; - -import org.argeo.jackrabbit.JackrabbitWrapper; -import org.argeo.jcr.ArgeoJcrConstants; - -/** Jacrabbit based data layer */ -class NodeRepository extends JackrabbitWrapper implements KernelConstants, - ArgeoJcrConstants { -// private static Log log = LogFactory.getLog(NodeRepository.class); -// -// private RepositoryContext repositoryContext; -// -// public NodeRepository() { -// setBundleContext(Activator.getBundleContext()); -// JackrabbitNodeType type = JackrabbitNodeType.valueOf(prop(REPO_TYPE, -// h2.name())); -// try { -// repositoryContext = createNode(type); -// setCndFiles(Arrays.asList(DEFAULT_CNDS)); -// prepareDataModel(); -// } catch (Exception e) { -// throw new ArgeoException( -// "Cannot create Jackrabbit repository of type " + type, e); -// } -// } -// -// public void destroy() { -// ((RepositoryImpl) getRepository()).shutdown(); -// } -// -// RepositoryStatisticsImpl getRepositoryStatistics() { -// return repositoryContext.getRepositoryStatistics(); -// } -// -// private RepositoryConfig getConfiguration(JackrabbitNodeType type, -// Hashtable vars) throws RepositoryException { -// ClassLoader cl = getClass().getClassLoader(); -// InputStream in = null; -// try { -// final String base = "/org/argeo/cms/internal/kernel"; -// switch (type) { -// case h2: -// in = cl.getResourceAsStream(base + "/repository-h2.xml"); -// break; -// case postgresql: -// in = cl.getResourceAsStream(base + "/repository-postgresql.xml"); -// break; -// case memory: -// in = cl.getResourceAsStream(base + "/repository-memory.xml"); -// break; -// case localfs: -// in = cl.getResourceAsStream(base + "/repository-localfs.xml"); -// break; -// default: -// throw new CmsException("Unsupported node type " + type); -// } -// -// if (in == null) -// throw new CmsException("Repository configuration not found"); -// InputSource config = new InputSource(in); -// Properties jackrabbitProps = new Properties(); -// jackrabbitProps.putAll(vars); -// RepositoryConfig repositoryConfig = RepositoryConfig.create(config, -// jackrabbitProps); -// return repositoryConfig; -// } finally { -// IOUtils.closeQuietly(in); -// } -// } -// -// private Hashtable getConfigurationProperties( -// JackrabbitNodeType type) { -// // use Hashtable to ease integration with Properties -// Hashtable defaults = new Hashtable(); -// -// // home -// File osgiInstanceDir = KernelUtils.getOsgiInstanceDir(); -// File homeDir = new File(osgiInstanceDir, DIR_NODE); -// // home cannot be overridden -// defaults.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, -// homeDir.getAbsolutePath()); -// -// // common -// setProp(defaults, REPO_DEFAULT_WORKSPACE, "main"); -// setProp(defaults, REPO_MAX_POOL_SIZE, "10"); -// // Jackrabbit defaults -// setProp(defaults, REPO_BUNDLE_CACHE_MB, "8"); -// // See http://wiki.apache.org/jackrabbit/Search -// setProp(defaults, REPO_EXTRACTOR_POOL_SIZE, "0"); -// setProp(defaults, REPO_SEARCH_CACHE_SIZE, "1000"); -// setProp(defaults, REPO_MAX_VOLATILE_INDEX_SIZE, "1048576"); -// -// // specific -// String dburl; -// switch (type) { -// case h2: -// dburl = "jdbc:h2:" + homeDir.getPath() + "/h2/repository"; -// setProp(defaults, REPO_DBURL, dburl); -// setProp(defaults, REPO_DBUSER, "sa"); -// setProp(defaults, REPO_DBPASSWORD, ""); -// break; -// case postgresql: -// dburl = "jdbc:postgresql://localhost/demo"; -// setProp(defaults, REPO_DBURL, dburl); -// setProp(defaults, REPO_DBUSER, "argeo"); -// setProp(defaults, REPO_DBPASSWORD, "argeo"); -// break; -// case memory: -// break; -// case localfs: -// break; -// default: -// throw new CmsException("Unsupported node type " + type); -// } -// return defaults; -// } -// -// private void setProp(Dictionary props, String key, -// String defaultValue) { -// String value = prop(key, defaultValue); -// props.put(key, value); -// } -// -// private String prop(String key, String defaultValue) { -// // TODO use OSGi CM instead of Framework/System properties -// return KernelUtils.getFrameworkProp(key, defaultValue); -// } -// -// private RepositoryContext createNode(JackrabbitNodeType type) -// throws RepositoryException { -// Hashtable vars = getConfigurationProperties(type); -// RepositoryConfig repositoryConfig = getConfiguration(type, vars); -// RepositoryContext repositoryContext = createJackrabbitRepository(repositoryConfig); -// RepositoryImpl repository = repositoryContext.getRepository(); -// -// // cache -// String maxCacheMbStr = prop(REPO_MAX_CACHE_MB, null); -// if (maxCacheMbStr != null) { -// Integer maxCacheMB = Integer.parseInt(maxCacheMbStr); -// CacheManager cacheManager = repository.getCacheManager(); -// cacheManager.setMaxMemory(maxCacheMB * 1024l * 1024l); -// cacheManager.setMaxMemoryPerCache((maxCacheMB / 4) * 1024l * 1024l); -// } -// -// // wrap the repository -// setRepository(repository); -// return repositoryContext; -// } -// -// private RepositoryContext createJackrabbitRepository( -// RepositoryConfig repositoryConfig) throws RepositoryException { -// long begin = System.currentTimeMillis(); -// // -// // Actual repository creation -// // -// RepositoryContext repositoryContext = RepositoryContext -// .create(repositoryConfig); -// -// double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; -// if (log.isTraceEnabled()) -// log.trace("Created Jackrabbit repository in " + duration -// + " s, home: " + repositoryConfig.getHomeDir()); -// -// return repositoryContext; -// } -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java index d4daef11f..3cc3dbfb3 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java @@ -16,11 +16,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.jcr.Node; import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.security.Privilege; import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; import javax.transaction.TransactionManager; @@ -28,24 +24,24 @@ import javax.transaction.TransactionManager; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.argeo.ArgeoException; import org.argeo.cms.CmsException; import org.argeo.cms.auth.AuthConstants; -import org.argeo.jcr.ArgeoJcrConstants; -import org.argeo.jcr.ArgeoNames; -import org.argeo.jcr.ArgeoTypes; -import org.argeo.jcr.JcrUtils; -import org.argeo.jcr.UserJcrUtils; +import org.argeo.node.NodeConstants; import org.argeo.osgi.useradmin.LdapUserAdmin; import org.argeo.osgi.useradmin.LdifUserAdmin; import org.argeo.osgi.useradmin.UserAdminConf; import org.argeo.osgi.useradmin.UserDirectory; import org.argeo.osgi.useradmin.UserDirectoryException; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; import org.osgi.service.useradmin.Authorization; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.User; import org.osgi.service.useradmin.UserAdmin; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; import bitronix.tm.resource.ehcache.EhCacheXAResourceProducer; @@ -64,33 +60,77 @@ public class NodeUserAdmin implements UserAdmin, KernelConstants { } } + private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + // DAOs private UserAdmin nodeRoles = null; private Map userAdmins = new HashMap(); // JCR - /** The home base path. */ - private String homeBasePath = "/home"; - private String peopleBasePath = ArgeoJcrConstants.PEOPLE_BASE_PATH; - private Repository repository; - private Session adminSession; + // private String homeBasePath = "/home"; + // private String peopleBasePath = ArgeoJcrConstants.PEOPLE_BASE_PATH; + // private Repository repository; + // private Session adminSession; private final String cacheName = UserDirectory.class.getName(); - public NodeUserAdmin(TransactionManager transactionManager, Repository repository) { - this.repository = repository; - try { - this.adminSession = this.repository.login(); - } catch (RepositoryException e) { - throw new CmsException("Cannot log-in", e); + public NodeUserAdmin() { + // DAOs + File nodeBaseDir = new File(getOsgiInstanceDir(), DIR_NODE); + nodeBaseDir.mkdirs(); + String userAdminUri = getFrameworkProp(NodeConstants.USERADMIN_URIS); + initUserAdmins(userAdminUri, nodeBaseDir); + String nodeRolesUri = getFrameworkProp(NodeConstants.ROLES_URI); + initNodeRoles(nodeRolesUri, nodeBaseDir); + + new ServiceTracker<>(bc, TransactionManager.class, new TransactionManagerStc()).open(); + } + + private class TransactionManagerStc implements ServiceTrackerCustomizer { + + @Override + public TransactionManager addingService(ServiceReference reference) { + TransactionManager transactionManager = bc.getService(reference); + ((UserDirectory) nodeRoles).setTransactionManager(transactionManager); + for (UserAdmin userAdmin : userAdmins.values()) { + if (userAdmin instanceof UserDirectory) + ((UserDirectory) userAdmin).setTransactionManager(transactionManager); + } + if (log.isDebugEnabled()) + log.debug("Set transaction manager"); + return transactionManager; } + @Override + public void modifiedService(ServiceReference reference, TransactionManager service) { + } + + @Override + public void removedService(ServiceReference reference, TransactionManager service) { + ((UserDirectory) nodeRoles).setTransactionManager(null); + for (UserAdmin userAdmin : userAdmins.values()) { + if (userAdmin instanceof UserDirectory) + ((UserDirectory) userAdmin).setTransactionManager(null); + } + } + + } + + @Deprecated + public NodeUserAdmin(TransactionManager transactionManager, Repository repository) { + // this.repository = repository; + // try { + // this.adminSession = this.repository.login(); + // } catch (RepositoryException e) { + // throw new CmsException("Cannot log-in", e); + // } + // DAOs File nodeBaseDir = new File(getOsgiInstanceDir(), DIR_NODE); nodeBaseDir.mkdirs(); - String userAdminUri = getFrameworkProp(USERADMIN_URIS); + String userAdminUri = getFrameworkProp(NodeConstants.USERADMIN_URIS); initUserAdmins(userAdminUri, nodeBaseDir); - String nodeRolesUri = getFrameworkProp(ROLES_URI); + String nodeRolesUri = getFrameworkProp(NodeConstants.ROLES_URI); initNodeRoles(nodeRolesUri, nodeBaseDir); // Transaction manager @@ -101,10 +141,10 @@ public class NodeUserAdmin implements UserAdmin, KernelConstants { } // JCR - initJcr(adminSession); + // initJcr(adminSession); } - Dictionary currentState() { + Dictionary currentState() { Dictionary res = new Hashtable(); for (LdapName name : userAdmins.keySet()) { StringBuilder buf = new StringBuilder(); @@ -189,7 +229,7 @@ public class NodeUserAdmin implements UserAdmin, KernelConstants { } Authorization authorization = new NodeAuthorization(rawAuthorization.getName(), rawAuthorization.toString(), systemRoles, rawAuthorization.getRoles()); - syncJcr(adminSession, authorization); + // syncJcr(adminSession, authorization); return authorization; } @@ -339,97 +379,104 @@ public class NodeUserAdmin implements UserAdmin, KernelConstants { /* * JCR */ - private void initJcr(Session adminSession) { - try { - JcrUtils.mkdirs(adminSession, homeBasePath); - JcrUtils.mkdirs(adminSession, peopleBasePath); - adminSession.save(); - - JcrUtils.addPrivilege(adminSession, homeBasePath, AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_READ); - JcrUtils.addPrivilege(adminSession, peopleBasePath, AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_ALL); - adminSession.save(); - } catch (RepositoryException e) { - throw new CmsException("Cannot initialize node user admin", e); - } - } - - private Node syncJcr(Session session, Authorization authorization) { - // TODO check user name validity (e.g. should not start by ROLE_) - String username = authorization.getName(); - // String[] roles = authorization.getRoles(); - try { - Node userHome = UserJcrUtils.getUserHome(session, username); - if (userHome == null) { - String homePath = generateUserPath(homeBasePath, username); - if (session.itemExists(homePath))// duplicate user id - userHome = session.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath)); - else - userHome = JcrUtils.mkdirs(session, homePath); - // userHome = JcrUtils.mkfolders(session, homePath); - userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); - userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); - session.save(); - - JcrUtils.clearAccessControList(session, homePath, username); - JcrUtils.addPrivilege(session, homePath, username, Privilege.JCR_ALL); - } - - Node userProfile = UserJcrUtils.getUserProfile(session, username); - // new user - if (userProfile == null) { - String personPath = generateUserPath(peopleBasePath, username); - Node personBase; - if (session.itemExists(personPath))// duplicate user id - personBase = session.getNode(personPath).getParent().addNode(JcrUtils.lastPathElement(personPath)); - else - personBase = JcrUtils.mkdirs(session, personPath); - userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE); - userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); - userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); - userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true); - userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, true); - userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, true); - userProfile.setProperty(ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true); - session.save(); - - JcrUtils.clearAccessControList(session, userProfile.getPath(), username); - JcrUtils.addPrivilege(session, userProfile.getPath(), username, Privilege.JCR_READ); - } - - // Remote roles - // if (roles != null) { - // writeRemoteRoles(userProfile, roles); - // } - if (adminSession.hasPendingChanges()) - adminSession.save(); - return userProfile; - } catch (RepositoryException e) { - JcrUtils.discardQuietly(session); - throw new ArgeoException("Cannot sync node security model for " + username, e); - } - } - - /** Generate path for a new user home */ - private String generateUserPath(String base, String username) { - LdapName dn; - try { - dn = new LdapName(username); - } catch (InvalidNameException e) { - throw new ArgeoException("Invalid name " + username, e); - } - String userId = dn.getRdn(dn.size() - 1).getValue().toString(); - int atIndex = userId.indexOf('@'); - if (atIndex > 0) { - String domain = userId.substring(0, atIndex); - String name = userId.substring(atIndex + 1); - return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/' + domain + '/' - + JcrUtils.firstCharsToPath(name, 2) + '/' + name; - } else if (atIndex == 0 || atIndex == (userId.length() - 1)) { - throw new ArgeoException("Unsupported username " + userId); - } else { - return base + '/' + JcrUtils.firstCharsToPath(userId, 2) + '/' + userId; - } - } + // private void initJcr(Session adminSession) { + // try { + // JcrUtils.mkdirs(adminSession, homeBasePath); + // JcrUtils.mkdirs(adminSession, peopleBasePath); + // adminSession.save(); + // + // JcrUtils.addPrivilege(adminSession, homeBasePath, + // AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_READ); + // JcrUtils.addPrivilege(adminSession, peopleBasePath, + // AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_ALL); + // adminSession.save(); + // } catch (RepositoryException e) { + // throw new CmsException("Cannot initialize node user admin", e); + // } + // } + // + // private Node syncJcr(Session session, Authorization authorization) { + // // TODO check user name validity (e.g. should not start by ROLE_) + // String username = authorization.getName(); + // // String[] roles = authorization.getRoles(); + // try { + // Node userHome = UserJcrUtils.getUserHome(session, username); + // if (userHome == null) { + // String homePath = generateUserPath(homeBasePath, username); + // if (session.itemExists(homePath))// duplicate user id + // userHome = + // session.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath)); + // else + // userHome = JcrUtils.mkdirs(session, homePath); + // // userHome = JcrUtils.mkfolders(session, homePath); + // userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); + // userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); + // session.save(); + // + // JcrUtils.clearAccessControList(session, homePath, username); + // JcrUtils.addPrivilege(session, homePath, username, Privilege.JCR_ALL); + // } + // + // Node userProfile = UserJcrUtils.getUserProfile(session, username); + // // new user + // if (userProfile == null) { + // String personPath = generateUserPath(peopleBasePath, username); + // Node personBase; + // if (session.itemExists(personPath))// duplicate user id + // personBase = + // session.getNode(personPath).getParent().addNode(JcrUtils.lastPathElement(personPath)); + // else + // personBase = JcrUtils.mkdirs(session, personPath); + // userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE); + // userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); + // userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); + // userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true); + // userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, true); + // userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, true); + // userProfile.setProperty(ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true); + // session.save(); + // + // JcrUtils.clearAccessControList(session, userProfile.getPath(), username); + // JcrUtils.addPrivilege(session, userProfile.getPath(), username, + // Privilege.JCR_READ); + // } + // + // // Remote roles + // // if (roles != null) { + // // writeRemoteRoles(userProfile, roles); + // // } + // if (adminSession.hasPendingChanges()) + // adminSession.save(); + // return userProfile; + // } catch (RepositoryException e) { + // JcrUtils.discardQuietly(session); + // throw new ArgeoException("Cannot sync node security model for " + + // username, e); + // } + // } + // + // /** Generate path for a new user home */ + // private String generateUserPath(String base, String username) { + // LdapName dn; + // try { + // dn = new LdapName(username); + // } catch (InvalidNameException e) { + // throw new ArgeoException("Invalid name " + username, e); + // } + // String userId = dn.getRdn(dn.size() - 1).getValue().toString(); + // int atIndex = userId.indexOf('@'); + // if (atIndex > 0) { + // String domain = userId.substring(0, atIndex); + // String name = userId.substring(atIndex + 1); + // return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/' + domain + + // '/' + // + JcrUtils.firstCharsToPath(name, 2) + '/' + name; + // } else if (atIndex == 0 || atIndex == (userId.length() - 1)) { + // throw new ArgeoException("Unsupported username " + userId); + // } else { + // return base + '/' + JcrUtils.firstCharsToPath(userId, 2) + '/' + userId; + // } + // } // /** Write remote roles used by remote access in the home directory */ // private void writeRemoteRoles(Node userHome, String[] roles) 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 index 2e63d52e1..2bb1ab481 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg @@ -9,8 +9,8 @@ ANONYMOUS { org.argeo.cms.auth.NodeUserLoginModule requisite; }; -SYSTEM { - org.argeo.security.core.SystemLoginModule requisite; +DATA_ADMIN { + org.argeo.cms.auth.DataAdminLoginModule requisite; }; KERNEL { diff --git a/org.argeo.eclipse.ui.workbench/META-INF/spring/osgi.xml b/org.argeo.eclipse.ui.workbench/META-INF/spring/osgi.xml index 5be2a197e..feede4d6e 100644 --- a/org.argeo.eclipse.ui.workbench/META-INF/spring/osgi.xml +++ b/org.argeo.eclipse.ui.workbench/META-INF/spring/osgi.xml @@ -15,7 +15,7 @@ + filter="(argeo.jcr.repository.alias=home)" /> diff --git a/org.argeo.security.jackrabbit/pom.xml b/org.argeo.security.jackrabbit/pom.xml index 9fc1718ac..8bcac0621 100644 --- a/org.argeo.security.jackrabbit/pom.xml +++ b/org.argeo.security.jackrabbit/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 org.argeo.commons @@ -12,8 +13,16 @@ org.argeo.commons - org.argeo.security.core + org.argeo.cms.api 2.1.45-SNAPSHOT + + + + org.argeo.commons + org.argeo.server.jcr + 2.1.45-SNAPSHOT + test + \ No newline at end of file diff --git a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java index 9977938ec..c041d276d 100644 --- a/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java +++ b/org.argeo.security.jackrabbit/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java @@ -11,7 +11,7 @@ import javax.security.auth.x500.X500Principal; import org.apache.jackrabbit.core.security.SecurityConstants; import org.apache.jackrabbit.core.security.principal.AdminPrincipal; -import org.argeo.security.SystemAuth; +import org.argeo.node.DataAdminPrincipal; public class SystemJackrabbitLoginModule implements LoginModule { @@ -30,8 +30,8 @@ public class SystemJackrabbitLoginModule implements LoginModule { @Override public boolean commit() throws LoginException { - Set initPrincipal = subject - .getPrincipals(SystemAuth.class); + Set initPrincipal = subject + .getPrincipals(DataAdminPrincipal.class); if (!initPrincipal.isEmpty()) { subject.getPrincipals().add( new AdminPrincipal(SecurityConstants.ADMIN_ID)); @@ -47,34 +47,6 @@ public class SystemJackrabbitLoginModule implements LoginModule { + userPrincipal); return true; - - // Set principals = subject.getPrincipals(); - // if (principals.isEmpty()) {// system - // throw new LoginException("Subject must be pre-authenticated"); - // // subject.getPrincipals().add(new AdminPrincipal("admin")); - // // return true; - // } - // boolean isAdmin = false; - // boolean isAnonymous = false; - // // FIXME make it more generic - // for (Principal principal : principals) { - // if (principal.getName().equalsIgnoreCase( - // "cn=admin,ou=roles,ou=node")) - // isAdmin = true; - // else if (principal.getName().equalsIgnoreCase( - // "cn=anonymous,ou=roles,ou=node")) - // isAnonymous = true; - // } - // - // if (isAnonymous && isAdmin) - // throw new LoginException("Cannot be admin and anonymous"); - // - // // Add special Jackrabbit roles - // if (isAdmin) - // principals.add(new AdminPrincipal(SecurityConstants.ADMIN_ID)); - // if (isAnonymous)// anonymous - // principals.add(new AnonymousPrincipal()); - // return true; } @Override @@ -84,14 +56,12 @@ public class SystemJackrabbitLoginModule implements LoginModule { @Override public boolean logout() throws LoginException { - Set initPrincipal = subject - .getPrincipals(SystemAuth.class); + Set initPrincipal = subject + .getPrincipals(DataAdminPrincipal.class); if (!initPrincipal.isEmpty()) { subject.getPrincipals(AdminPrincipal.class); return true; } - // subject.getPrincipals().removeAll( - // subject.getPrincipals(AdminPrincipal.class)); return true; } } diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoJcrConstants.java b/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoJcrConstants.java index 53e494cff..26979e927 100644 --- a/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoJcrConstants.java +++ b/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoJcrConstants.java @@ -35,7 +35,9 @@ public interface ArgeoJcrConstants { * JCR repository. */ public final static String ALIAS_NODE = "node"; + public final static String ALIAS_HOME = "home"; public final static String BASE_REPO_PID = "argeo.repo."; public final static String REPO_PID_NODE = BASE_REPO_PID + ALIAS_NODE; + public final static String JACKRABBIT_REPO_FACTORY_PID = "argeo.repo.factory.jackrabbit"; } diff --git a/org.argeo.util/src/org/argeo/util/DictionaryKeys.java b/org.argeo.util/src/org/argeo/util/DictionaryKeys.java new file mode 100644 index 000000000..d17c86f96 --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/DictionaryKeys.java @@ -0,0 +1,42 @@ +package org.argeo.util; + +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Iterator; + +/** + * Access the keys of a {@link String}-keyed {@link Dictionary} (common throughout + * the OSGi APIs) as an {@link Iterable} so that they are easily usable in + * for-each loops. + */ +class DictionaryKeys implements Iterable { + private final Dictionary dictionary; + + public DictionaryKeys(Dictionary dictionary) { + this.dictionary = dictionary; + } + + @Override + public Iterator iterator() { + return new KeyIterator(dictionary.keys()); + } + + private static class KeyIterator implements Iterator { + private final Enumeration keys; + + KeyIterator(Enumeration keys) { + this.keys = keys; + } + + @Override + public boolean hasNext() { + return keys.hasMoreElements(); + } + + @Override + public String next() { + return keys.nextElement(); + } + + } +} diff --git a/org.argeo.util/src/org/argeo/util/LangUtils.java b/org.argeo.util/src/org/argeo/util/LangUtils.java new file mode 100644 index 000000000..c94db9d30 --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/LangUtils.java @@ -0,0 +1,80 @@ +package org.argeo.util; + +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Hashtable; + +public class LangUtils { + /* + * NON-API OSGi + */ + /** + * Returns an array with the names of the provided classes. Useful when + * registering services with multiple interfaces in OSGi. + */ + public static String[] names(Class... clzz) { + String[] res = new String[clzz.length]; + for (int i = 0; i < clzz.length; i++) + res[i] = clzz[i].getName(); + return res; + } + + /* + * DICTIONARY + */ + + /** + * Creates a new {@link Dictionary} with one key-value pair (neith key not + * value should be null) + */ + public static Dictionary init(String key, Object value) { + assert key != null; + assert value != null; + Hashtable props = new Hashtable<>(); + props.put(key, value); + return props; + } + + /** + * Wraps the keys of the provided {@link Dictionary} as an {@link Iterable}. + */ + public static Iterable keys(Dictionary props) { + assert props != null; + return new DictionaryKeys(props); + } + + public static String toJson(Dictionary props) { + return toJson(props, false); + } + + public static String toJson(Dictionary props, boolean pretty) { + StringBuilder sb = new StringBuilder(); + sb.append('{'); + if (pretty) + sb.append('\n'); + Enumeration keys = props.keys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + if (pretty) + sb.append(' '); + sb.append('\"').append(key).append('\"'); + if (pretty) + sb.append(" : "); + else + sb.append(':'); + sb.append('\"').append(props.get(key)).append('\"'); + if (keys.hasMoreElements()) + sb.append(", "); + if (pretty) + sb.append('\n'); + } + sb.append('}'); + return sb.toString(); + } + + /** Singleton constructor. */ + private LangUtils() { + + } + +} diff --git a/org.argeo.util/src/org/argeo/util/LocaleChoice.java b/org.argeo.util/src/org/argeo/util/LocaleChoice.java index e9d9acac4..6609f4c94 100644 --- a/org.argeo.util/src/org/argeo/util/LocaleChoice.java +++ b/org.argeo.util/src/org/argeo/util/LocaleChoice.java @@ -41,13 +41,11 @@ public class LocaleChoice { // based on language only if (defaultIndex == null) for (int i = 0; i < locales.size(); i++) - if (locales.get(i).getLanguage() - .equals(defaultLocale.getLanguage())) + if (locales.get(i).getLanguage().equals(defaultLocale.getLanguage())) defaultIndex = i; if (defaultIndex == null) - throw new ArgeoException("Default locale " + defaultLocale - + " is not in available locales " + locales); + throw new ArgeoException("Default locale " + defaultLocale + " is not in available locales " + locales); this.defaultIndex = defaultIndex; this.selectedIndex = defaultIndex; @@ -66,13 +64,10 @@ public class LocaleChoice { for (int i = 0; i < locales.size(); i++) { Locale locale = locales.get(i); if (locale.getCountry().equals("")) - labels[i] = locale.getDisplayLanguage(locale) + " [" - + locale.getLanguage() + "]"; + labels[i] = locale.getDisplayLanguage(locale) + " [" + locale.getLanguage() + "]"; else - labels[i] = locale.getDisplayLanguage(locale) + " (" - + locale.getDisplayCountry(locale) + ") [" - + locale.getLanguage() + "_" + locale.getCountry() - + "]"; + labels[i] = locale.getDisplayLanguage(locale) + " (" + locale.getDisplayCountry(locale) + ") [" + + locale.getLanguage() + "_" + locale.getCountry() + "]"; } return labels; @@ -105,11 +100,11 @@ public class LocaleChoice { } /** Returns null if argument is null. */ - public static List asLocaleList(String locales) { + public static List asLocaleList(Object locales) { if (locales == null) return null; ArrayList availableLocales = new ArrayList(); - String[] codes = locales.split(","); + String[] codes = locales.toString().split(","); for (int i = 0; i < codes.length; i++) { String code = codes[i]; // variant not supported @@ -130,8 +125,7 @@ public class LocaleChoice { public static void main(String[] args) { for (String isoL : Locale.getISOLanguages()) { Locale locale = new Locale(isoL); - System.out.println(isoL + "\t" + locale.getDisplayLanguage() + "\t" - + locale.getDisplayLanguage(locale)); + System.out.println(isoL + "\t" + locale.getDisplayLanguage() + "\t" + locale.getDisplayLanguage(locale)); } } diff --git a/pom.xml b/pom.xml index 7c1fa7493..35cbabaa9 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 org.argeo.commons argeo-commons @@ -25,12 +26,13 @@ org.argeo.server.jcr org.argeo.security.core - org.argeo.security.jackrabbit org.argeo.eclipse.ui org.argeo.eclipse.ui.rap + org.argeo.cms.api org.argeo.cms + org.argeo.security.jackrabbit org.argeo.eclipse.ui.workbench org.argeo.eclipse.ui.workbench.rap @@ -220,7 +222,7 @@ limitations under the License. org.apache.felix maven-bundle-plugin - 2.3.7 + 3.0.1 true META-INF @@ -501,8 +503,10 @@ limitations under the License. - - + + @@ -527,8 +531,10 @@ limitations under the License. - - + + -- 2.30.2