From e61a7a2dbff5e17fbf1c6c8bbd7fa687935d2897 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Tue, 6 Feb 2018 13:21:38 +0100 Subject: [PATCH] Introduce support for multiple JCR repositories. Framework properties override deploy config by clean (and not only by first init). --- .../cms/internal/kernel/CmsDeployment.java | 81 +++++---- .../argeo/cms/internal/kernel/DataModels.java | 163 ++++++++++++++++++ .../cms/internal/kernel/DeployConfig.java | 71 ++++++-- .../kernel/{FirstInit.java => InitUtils.java} | 38 ++-- .../cms/internal/kernel/KernelUtils.java | 19 +- .../cms/internal/kernel/LocalRepository.java | 2 +- .../osgi/useradmin/AbstractUserDirectory.java | 10 ++ .../osgi/useradmin/AggregatingUserAdmin.java | 7 +- .../org/argeo/osgi/useradmin/DigestUtils.java | 23 ++- .../argeo/osgi/useradmin/UserAdminConf.java | 18 ++ .../argeo/osgi/useradmin/UserDirectory.java | 16 +- .../org/argeo/node/DataModelNamespace.java | 11 +- .../src/org/argeo/node/NodeConstants.java | 2 + 13 files changed, 377 insertions(+), 84 deletions(-) create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/kernel/DataModels.java rename org.argeo.cms/src/org/argeo/cms/internal/kernel/{FirstInit.java => InitUtils.java} (84%) 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 index e25ff7747..0a04e0804 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java @@ -45,11 +45,13 @@ import org.osgi.service.useradmin.UserAdmin; import org.osgi.util.tracker.ServiceTracker; public class CmsDeployment implements NodeDeployment { - private final static String LEGACY_JCR_REPOSITORY_ALIAS = "argeo.jcr.repository.alias"; + // private final static String LEGACY_JCR_REPOSITORY_ALIAS = + // "argeo.jcr.repository.alias"; private final Log log = LogFactory.getLog(getClass()); private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + private DataModels dataModels; private DeployConfig deployConfig; private HomeRepository homeRepository; @@ -74,6 +76,7 @@ public class CmsDeployment implements NodeDeployment { cleanState = nodeState.isClean(); nodeHttp = new NodeHttp(); + dataModels = new DataModels(bc); initTrackers(); } @@ -110,7 +113,7 @@ public class CmsDeployment implements NodeDeployment { @Override public ConfigurationAdmin addingService(ServiceReference reference) { ConfigurationAdmin configurationAdmin = bc.getService(reference); - deployConfig = new DeployConfig(configurationAdmin, cleanState); + deployConfig = new DeployConfig(configurationAdmin, dataModels, cleanState); httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null; try { // Configuration[] configs = configurationAdmin @@ -205,13 +208,13 @@ public class CmsDeployment implements NodeDeployment { } // home - prepareDataModel(KernelUtils.openAdminSession(deployedNodeRepository)); + prepareDataModel(NodeConstants.NODE, KernelUtils.openAdminSession(deployedNodeRepository)); } private void prepareHomeRepository(Repository deployedRepository) { Hashtable regProps = new Hashtable(); regProps.put(NodeConstants.CN, NodeConstants.HOME); - regProps.put(LEGACY_JCR_REPOSITORY_ALIAS, NodeConstants.HOME); + // regProps.put(LEGACY_JCR_REPOSITORY_ALIAS, NodeConstants.HOME); homeRepository = new HomeRepository(deployedRepository); // register bc.registerService(Repository.class, homeRepository, regProps); @@ -232,36 +235,45 @@ public class CmsDeployment implements NodeDeployment { } /** Session is logged out. */ - private void prepareDataModel(Session adminSession) { + private void prepareDataModel(String cn, Session adminSession) { try { Set processed = new HashSet(); bundles: for (Bundle bundle : bc.getBundles()) { BundleWiring wiring = bundle.adapt(BundleWiring.class); if (wiring == null) continue bundles; - processWiring(adminSession, wiring, processed); + if (NodeConstants.NODE.equals(cn))// process all data models + processWiring(cn, adminSession, wiring, processed); + else { + List capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); + for (BundleCapability capability : capabilities) { + String dataModelName = (String) capability.getAttributes().get(DataModelNamespace.NAME); + if (dataModelName.equals(cn))// process only own data model + processWiring(cn, adminSession, wiring, processed); + } + } } } finally { JcrUtils.logoutQuietly(adminSession); } } - private void processWiring(Session adminSession, BundleWiring wiring, Set processed) { + private void processWiring(String cn, 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); + processWiring(cn, adminSession, wire.getProviderWiring(), processed); } List capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); for (BundleCapability capability : capabilities) { - registerDataModelCapability(adminSession, capability, processed); + registerDataModelCapability(cn, adminSession, capability, processed); } } - private void registerDataModelCapability(Session adminSession, BundleCapability capability, Set processed) { + private void registerDataModelCapability(String cn, Session adminSession, BundleCapability capability, + Set processed) { Map attrs = capability.getAttributes(); - String name = (String) attrs.get(DataModelNamespace.CAPABILITY_NAME_ATTRIBUTE); + String name = (String) attrs.get(DataModelNamespace.NAME); if (processed.contains(name)) { if (log.isTraceEnabled()) log.trace("Data model " + name + " has already been processed"); @@ -269,7 +281,7 @@ public class CmsDeployment implements NodeDeployment { } // CND - String path = (String) attrs.get(DataModelNamespace.CAPABILITY_CND_ATTRIBUTE); + String path = (String) attrs.get(DataModelNamespace.CND); if (path != null) { File dataModel = bc.getBundle().getDataFile("dataModels/" + path); if (!dataModel.exists()) { @@ -289,9 +301,21 @@ public class CmsDeployment implements NodeDeployment { } } - if (!asBoolean((String) attrs.get(DataModelNamespace.CAPABILITY_ABSTRACT_ATTRIBUTE))) { + if (KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT))) + return; + // Non abstract + boolean isStandalone = deployConfig.isStandalone(name); + boolean publishLocalRepo; + if (isStandalone && name.equals(cn))// includes the node itself + publishLocalRepo = true; + else if (!isStandalone && cn.equals(NodeConstants.NODE)) + publishLocalRepo = true; + else + publishLocalRepo = false; + + if (publishLocalRepo) { Hashtable properties = new Hashtable<>(); - properties.put(LEGACY_JCR_REPOSITORY_ALIAS, name); + // properties.put(LEGACY_JCR_REPOSITORY_ALIAS, name); properties.put(NodeConstants.CN, name); if (name.equals(NodeConstants.NODE)) properties.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); @@ -302,20 +326,6 @@ public class CmsDeployment implements NodeDeployment { } } - private boolean asBoolean(String value) { - if (value == null) - return false; - switch (value) { - case "true": - return true; - case "false": - return false; - default: - throw new CmsException("Unsupported value for attribute " + DataModelNamespace.CAPABILITY_ABSTRACT_ATTRIBUTE - + ": " + value); - } - } - @Override public Long getAvailableSince() { return availableSince; @@ -329,19 +339,20 @@ public class CmsDeployment implements NodeDeployment { @Override public RepositoryContext addingService(ServiceReference reference) { - RepositoryContext nodeRepo = bc.getService(reference); - Object cn = reference.getProperty(NodeConstants.CN); + RepositoryContext repoContext = bc.getService(reference); + String cn = (String) reference.getProperty(NodeConstants.CN); if (cn != null) { if (cn.equals(NodeConstants.NODE)) { - prepareNodeRepository(nodeRepo.getRepository()); - prepareHomeRepository(nodeRepo.getRepository()); + prepareNodeRepository(repoContext.getRepository()); + // TODO separate home repository + prepareHomeRepository(repoContext.getRepository()); nodeAvailable = true; checkReadiness(); } else { - // TODO standalone + prepareDataModel(cn, KernelUtils.openAdminSession(repoContext.getRepository())); } } - return nodeRepo; + return repoContext; } @Override diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataModels.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataModels.java new file mode 100644 index 000000000..b758a3063 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataModels.java @@ -0,0 +1,163 @@ +package org.argeo.cms.internal.kernel; + +import static org.argeo.node.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.argeo.cms.CmsException; +import org.argeo.node.DataModelNamespace; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleListener; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleWire; +import org.osgi.framework.wiring.BundleWiring; + +class DataModels implements BundleListener { + private Map dataModels = new TreeMap<>(); + + public DataModels(BundleContext bc) { + for (Bundle bundle : bc.getBundles()) + processBundle(bundle); + bc.addBundleListener(this); + } + + public List getNonAbstractDataModels() { + List res = new ArrayList<>(); + for (String name : dataModels.keySet()) { + DataModel dataModel = dataModels.get(name); + if (!dataModel.isAbstract()) + res.add(dataModel); + } + // TODO reorder? + return res; + } + + @Override + public void bundleChanged(BundleEvent event) { + if (event.getType() == Bundle.RESOLVED) { + processBundle(event.getBundle()); + } else if (event.getType() == Bundle.UNINSTALLED) { + BundleWiring wiring = event.getBundle().adapt(BundleWiring.class); + List providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); + if (providedDataModels.size() == 0) + return; + for (BundleCapability bundleCapability : providedDataModels) { + dataModels.remove(bundleCapability.getAttributes().get(DataModelNamespace.NAME)); + } + } + + } + + protected void processBundle(Bundle bundle) { + BundleWiring wiring = bundle.adapt(BundleWiring.class); + List providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); + if (providedDataModels.size() == 0) + return; + List requiredDataModels = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE); + for (BundleCapability bundleCapability : providedDataModels) { + DataModel dataModel = new DataModel(bundleCapability, requiredDataModels); + dataModels.put(dataModel.getName(), dataModel); + } + } + + /** Return a negative depth if dataModel is required by ref, 0 otherwise. */ + static int required(DataModel ref, DataModel dataModel, int depth) { + for (DataModel dm : ref.getRequired()) { + if (dm.equals(dataModel))// found here + return depth - 1; + int d = required(dm, dataModel, depth - 1); + if (d != 0)// found deeper + return d; + } + return 0;// not found + } + + class DataModel { + private final String name; + private final boolean abstrct; + // private final boolean standalone; + private final String cnd; + private final List required; + + private DataModel(BundleCapability bundleCapability, List requiredDataModels) { + assert CMS_DATA_MODEL_NAMESPACE.equals(bundleCapability.getNamespace()); + Map attrs = bundleCapability.getAttributes(); + name = (String) attrs.get(DataModelNamespace.NAME); + assert name != null; + abstrct = KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)); + // standalone = KernelUtils.asBoolean((String) + // attrs.get(DataModelNamespace.CAPABILITY_STANDALONE_ATTRIBUTE)); + cnd = (String) attrs.get(DataModelNamespace.CND); + List req = new ArrayList<>(); + for (BundleWire wire : requiredDataModels) { + String requiredDataModelName = (String) wire.getCapability().getAttributes() + .get(DataModelNamespace.NAME); + assert requiredDataModelName != null; + DataModel requiredDataModel = dataModels.get(requiredDataModelName); + if (requiredDataModel == null) + throw new CmsException("No required data model " + requiredDataModelName); + req.add(requiredDataModel); + } + required = Collections.unmodifiableList(req); + } + + public String getName() { + return name; + } + + public boolean isAbstract() { + return abstrct; + } + + // public boolean isStandalone() { + // return !isAbstract(); + // } + + public String getCnd() { + return cnd; + } + + public List getRequired() { + return required; + } + + // @Override + // public int compareTo(DataModel o) { + // if (equals(o)) + // return 0; + // int res = required(this, o, 0); + // if (res != 0) + // return res; + // // the other way round + // res = required(o, this, 0); + // if (res != 0) + // return -res; + // return 0; + // } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof DataModel) + return ((DataModel) obj).name.equals(name); + return false; + } + + @Override + public String toString() { + return "Data model " + name; + } + + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java index a26189222..bff544ab2 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java @@ -5,6 +5,7 @@ import java.io.InputStream; import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Dictionary; import java.util.List; import java.util.SortedMap; @@ -23,6 +24,7 @@ import org.argeo.naming.AttributesDictionary; import org.argeo.naming.LdifParser; import org.argeo.naming.LdifWriter; import org.argeo.node.NodeConstants; +import org.argeo.osgi.useradmin.UserAdminConf; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.service.cm.Configuration; @@ -36,15 +38,19 @@ class DeployConfig implements ConfigurationListener { private static Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH); private SortedMap deployConfigs = new TreeMap<>(); + private final DataModels dataModels; - public DeployConfig(ConfigurationAdmin configurationAdmin, boolean isClean) { + public DeployConfig(ConfigurationAdmin configurationAdmin, DataModels dataModels, boolean isClean) { + this.dataModels = dataModels; // ConfigurationAdmin configurationAdmin = // bc.getService(bc.getServiceReference(ConfigurationAdmin.class)); try { + boolean isFirstInit = false; if (!isInitialized()) { // first init + isFirstInit = true; firstInit(); } - init(configurationAdmin, isClean); + init(configurationAdmin, isClean, isFirstInit); } catch (IOException e) { throw new CmsException("Could not init deploy configs", e); } @@ -53,10 +59,11 @@ class DeployConfig implements ConfigurationListener { } private void firstInit() throws IOException { + log.info("## FIRST INIT ##"); Files.createDirectories(deployConfigPath.getParent()); - FirstInit firstInit = new FirstInit(); - FirstInit.prepareInstanceArea(); + // FirstInit firstInit = new FirstInit(); + InitUtils.prepareFirstInitInstanceArea(); if (!Files.exists(deployConfigPath)) deployConfigs = new TreeMap<>(); @@ -64,25 +71,56 @@ class DeployConfig implements ConfigurationListener { try (InputStream in = Files.newInputStream(deployConfigPath)) { deployConfigs = new LdifParser().read(in); } + save(); + } + private void setFromFrameworkProperties(boolean isFirstInit) { // node repository - Dictionary nodeConfig = firstInit + Dictionary nodeConfig = InitUtils .getNodeRepositoryConfig(getProps(NodeConstants.NODE_REPOS_FACTORY_PID, NodeConstants.NODE)); // node repository is mandatory putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, nodeConfig); - // user admin + // additional repositories + dataModels: for (DataModels.DataModel dataModel : dataModels.getNonAbstractDataModels()) { + if (NodeConstants.NODE.equals(dataModel.getName())) + continue dataModels; + Dictionary config = InitUtils.getRepositoryConfig(dataModel.getName(), + getProps(NodeConstants.NODE_REPOS_FACTORY_PID, NodeConstants.NODE)); + if (config.size() != 0) + putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, config); + } - List> userDirectoryConfigs = firstInit.getUserDirectoryConfigs(); - for (int i = 0; i < userDirectoryConfigs.size(); i++) { - Dictionary userDirectoryConfig = userDirectoryConfigs.get(i); - String cn = Integer.toString(i); - userDirectoryConfig.put(NodeConstants.CN, cn); - putFactoryDeployConfig(NodeConstants.NODE_USER_ADMIN_PID, userDirectoryConfig); + // user admin + List> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs(); + if (userDirectoryConfigs.size() != 0) { + List activeCns = new ArrayList<>(); + for (int i = 0; i < userDirectoryConfigs.size(); i++) { + Dictionary userDirectoryConfig = userDirectoryConfigs.get(i); + String cn = UserAdminConf.baseDnHash(userDirectoryConfig); + activeCns.add(cn); + userDirectoryConfig.put(NodeConstants.CN, cn); + putFactoryDeployConfig(NodeConstants.NODE_USER_ADMIN_PID, userDirectoryConfig); + } + // disable others + LdapName userAdminFactoryName = serviceFactoryDn(NodeConstants.NODE_USER_ADMIN_PID); + for (LdapName name : deployConfigs.keySet()) { + if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) { + try { + Attributes attrs = deployConfigs.get(name); + String cn = name.getRdn(name.size() - 1).getValue().toString(); + if (!activeCns.contains(cn)) { + attrs.put(UserAdminConf.disabled.name(), "true"); + } + } catch (Exception e) { + throw new CmsException("Cannot disable user directory " + name, e); + } + } + } } // http server - Dictionary webServerConfig = firstInit + Dictionary webServerConfig = InitUtils .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT)); if (!webServerConfig.isEmpty()) putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig); @@ -90,12 +128,13 @@ class DeployConfig implements ConfigurationListener { save(); } - private void init(ConfigurationAdmin configurationAdmin, boolean isClean) throws IOException { + private void init(ConfigurationAdmin configurationAdmin, boolean isClean, boolean isFirstInit) throws IOException { try (InputStream in = Files.newInputStream(deployConfigPath)) { deployConfigs = new LdifParser().read(in); } if (isClean) { + setFromFrameworkProperties(isFirstInit); for (LdapName dn : deployConfigs.keySet()) { Rdn lastRdn = dn.getRdn(dn.size() - 1); LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1); @@ -210,6 +249,10 @@ class DeployConfig implements ConfigurationListener { } } + boolean isStandalone(String dataModelName) { + return getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null; + } + /* * UTILITIES */ diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/FirstInit.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java similarity index 84% rename from org.argeo.cms/src/org/argeo/cms/internal/kernel/FirstInit.java rename to org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java index 98c2483cc..8a22b480f 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/FirstInit.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java @@ -31,15 +31,11 @@ import org.argeo.osgi.useradmin.UserAdminConf; * Interprets framework properties in order to generate the initial deploy * configuration. */ -class FirstInit { - private final static Log log = LogFactory.getLog(FirstInit.class); - - public FirstInit() { - log.info("## FIRST INIT ##"); - } +class InitUtils { + private final static Log log = LogFactory.getLog(InitUtils.class); /** Override the provided config with the framework properties */ - Dictionary getNodeRepositoryConfig(Dictionary provided) { + static Dictionary getNodeRepositoryConfig(Dictionary provided) { Dictionary props = provided != null ? provided : new Hashtable(); for (RepoConf repoConf : RepoConf.values()) { Object value = getFrameworkProp(NodeConstants.NODE_REPO_PROP_PREFIX + repoConf.name()); @@ -47,12 +43,26 @@ class FirstInit { props.put(repoConf.name(), value); } props.put(NodeConstants.CN, NodeConstants.NODE); - // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, NodeConstants.NODE); + return props; + } + + static Dictionary getRepositoryConfig(String dataModelName, Dictionary provided) { + if (dataModelName.equals(NodeConstants.NODE) || dataModelName.equals(NodeConstants.HOME)) + throw new IllegalArgumentException("Data model '" + dataModelName + "' is reserved."); + Dictionary props = provided != null ? provided : new Hashtable(); + for (RepoConf repoConf : RepoConf.values()) { + Object value = getFrameworkProp( + NodeConstants.NODE_REPOS_PROP_PREFIX + dataModelName + '.' + repoConf.name()); + if (value != null) + props.put(repoConf.name(), value); + } + if (props.size() != 0) + props.put(NodeConstants.CN, dataModelName); return props; } /** Override the provided config with the framework properties */ - Dictionary getHttpServerConfig(Dictionary provided) { + static Dictionary getHttpServerConfig(Dictionary provided) { String httpPort = getFrameworkProp("org.osgi.service.http.port"); String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure"); /// TODO make it more generic @@ -91,7 +101,7 @@ class FirstInit { return props; } - List> getUserDirectoryConfigs() { + static List> getUserDirectoryConfigs() { List> res = new ArrayList<>(); File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile(); List uris = new ArrayList<>(); @@ -104,7 +114,7 @@ class FirstInit { File nodeRolesFile = new File(nodeBaseDir, nodeRolesUri); if (!nodeRolesFile.exists()) try { - FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(baseNodeRoleDn + ".ldif"), + FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeRoleDn + ".ldif"), nodeRolesFile); } catch (IOException e) { throw new CmsException("Cannot copy demo resource", e); @@ -121,7 +131,7 @@ class FirstInit { File businessRolesFile = new File(nodeBaseDir, userAdminUris); if (!businessRolesFile.exists()) try { - FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(demoBaseDn + ".ldif"), + FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(demoBaseDn + ".ldif"), businessRolesFile); } catch (IOException e) { throw new CmsException("Cannot copy demo resource", e); @@ -165,7 +175,7 @@ class FirstInit { * Called before node initialisation, in order populate OSGi instance are with * some files (typically LDIF, etc). */ - static void prepareInstanceArea() { + static void prepareFirstInitInstanceArea() { String nodeInit = getFrameworkProp(NodeConstants.NODE_INIT); if (nodeInit == null) nodeInit = "../../init"; @@ -198,7 +208,7 @@ class FirstInit { } } - private void createSelfSignedKeyStore(Path keyStorePath, String keyStorePassword) { + private static void createSelfSignedKeyStore(Path keyStorePath, String keyStorePassword) { // for (Provider provider : Security.getProviders()) // System.out.println(provider.getName()); File keyStoreFile = keyStorePath.toFile(); 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 63cb356d3..1d8140911 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 @@ -25,6 +25,7 @@ import javax.security.auth.login.LoginException; import org.apache.commons.logging.Log; import org.argeo.cms.CmsException; +import org.argeo.node.DataModelNamespace; import org.argeo.node.NodeConstants; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -206,7 +207,7 @@ class KernelUtils implements KernelConstants { * @throws CmsException * if the related bundle is not active */ - public static BundleContext getBundleContext(Class clzz) { + static BundleContext getBundleContext(Class clzz) { Bundle bundle = FrameworkUtil.getBundle(clzz); BundleContext bc = bundle.getBundleContext(); if (bc == null) @@ -214,10 +215,24 @@ class KernelUtils implements KernelConstants { return bc; } - private static BundleContext getBundleContext() { + static BundleContext getBundleContext() { return getBundleContext(KernelUtils.class); } + static boolean asBoolean(String value) { + if (value == null) + return false; + switch (value) { + case "true": + return true; + case "false": + return false; + default: + throw new CmsException("Unsupported value for attribute " + DataModelNamespace.ABSTRACT + + ": " + value); + } + } + private static URI safeUri(String uri) { if (uri == null) throw new CmsException("URI cannot be null"); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/LocalRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/LocalRepository.java index 67c66e915..6211ccd37 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/LocalRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/LocalRepository.java @@ -15,7 +15,7 @@ class LocalRepository extends JcrRepositoryWrapper { public LocalRepository(Repository repository, BundleCapability dataModelCapability) { super(repository); Map attrs = dataModelCapability.getAttributes(); - cn = (String) attrs.get(DataModelNamespace.CAPABILITY_NAME_ATTRIBUTE); + cn = (String) attrs.get(DataModelNamespace.NAME); putDescriptor(NodeConstants.CN, cn); } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java index 56f2f5c17..95b1f07ad 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java @@ -54,6 +54,7 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory private final String userObjectClass, userBase, groupObjectClass, groupBase; private final boolean readOnly; + private final boolean disabled; private final URI uri; private UserAdmin externalRoles; @@ -108,6 +109,11 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly)); } else readOnly = new Boolean(readOnlyStr); + String disabledStr = UserAdminConf.disabled.getValue(properties); + if (disabledStr != null) + disabled = new Boolean(disabledStr); + else + disabled = false; } /** Returns the groups this user is a direct member of. */ @@ -438,6 +444,10 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory return readOnly; } + public boolean isDisabled() { + return disabled; + } + protected UserAdmin getExternalRoles() { return externalRoles; } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java index d2054416b..f7a7c6e47 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java @@ -139,8 +139,11 @@ public class AggregatingUserAdmin implements UserAdmin { return systemRoles; List res = new ArrayList(1); for (LdapName baseDn : businessRoles.keySet()) { - if (name.startsWith(baseDn)) - res.add(businessRoles.get(baseDn)); + if (name.startsWith(baseDn)) { + AbstractUserDirectory ud = businessRoles.get(baseDn); + if (!ud.isDisabled()) + res.add(ud); + } } if (res.size() == 0) throw new UserDirectoryException("Cannot find user admin for " + name); diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DigestUtils.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DigestUtils.java index f6a237bcc..51d18349b 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DigestUtils.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DigestUtils.java @@ -42,7 +42,28 @@ class DigestUtils { return bytes; } - private DigestUtils() { + static String sha1str(String str) { + byte[] hash = sha1(str.getBytes(StandardCharsets.UTF_8)); + return encodeHexString(hash); } + final private static char[] hexArray = "0123456789abcdef".toCharArray(); + + /** + * From + * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to + * -a-hex-string-in-java + */ + public static String encodeHexString(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + private DigestUtils() { + } } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java index b3ead140c..37d633920 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java @@ -13,6 +13,7 @@ import java.util.Map; import javax.naming.Context; import javax.naming.NamingException; +import javax.naming.ldap.LdapName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -43,6 +44,9 @@ public enum UserAdminConf { /** Read-only source */ readOnly(null), + /** Disabled source */ + disabled(null), + /** Authentication realm */ realm(null); @@ -144,6 +148,9 @@ public enum UserAdminConf { if (bDn.endsWith(".ldif")) bDn = bDn.substring(0, bDn.length() - ".ldif".length()); + // Normalize base DN as LDAP name + bDn = new LdapName(bDn).toString(); + String principal = null; String credentials = null; if (scheme != null) @@ -254,4 +261,15 @@ public enum UserAdminConf { return "dc=" + hostname; } } + + /** + * Hash the base DN in order to have a deterministic string to be used as a cn + * for the underlying user directory. + */ + public static String baseDnHash(Dictionary properties) { + String bDn = (String) properties.get(baseDn.name()); + if (bDn == null) + throw new UserDirectoryException("No baseDn in " + properties); + return DigestUtils.sha1str(bDn); + } } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectory.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectory.java index e5de73836..ff80c5ac8 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectory.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectory.java @@ -6,18 +6,20 @@ import javax.transaction.xa.XAResource; /** Information about a user directory. */ public interface UserDirectory { /** The base DN of all entries in this user directory */ - public LdapName getBaseDn(); + LdapName getBaseDn(); /** The related {@link XAResource} */ - public XAResource getXaResource(); + XAResource getXaResource(); - public boolean isReadOnly(); + boolean isReadOnly(); - public String getUserObjectClass(); + boolean isDisabled(); - public String getUserBase(); + String getUserObjectClass(); - public String getGroupObjectClass(); + String getUserBase(); - public String getGroupBase(); + String getGroupObjectClass(); + + String getGroupBase(); } diff --git a/org.argeo.node.api/src/org/argeo/node/DataModelNamespace.java b/org.argeo.node.api/src/org/argeo/node/DataModelNamespace.java index 6da250dbf..58e4a645a 100644 --- a/org.argeo.node.api/src/org/argeo/node/DataModelNamespace.java +++ b/org.argeo.node.api/src/org/argeo/node/DataModelNamespace.java @@ -6,15 +6,10 @@ import org.osgi.resource.Namespace; public class DataModelNamespace extends Namespace { public static final String CMS_DATA_MODEL_NAMESPACE = "cms.datamodel"; - public static final String CAPABILITY_NAME_ATTRIBUTE = "name"; - public static final String CAPABILITY_CND_ATTRIBUTE = "cnd"; + public static final String NAME = "name"; + public static final String CND = "cnd"; /** If 'true', indicates that no repository should be published */ - public static final String CAPABILITY_ABSTRACT_ATTRIBUTE = "abstract"; - /** - * If 'true', indicates that code using this data model should be prepared - * to have it stored in a different JCR repository than the node - */ - public static final String CAPABILITY_STANDALONE_ATTRIBUTE = "standalone"; + public static final String ABSTRACT = "abstract"; private DataModelNamespace() { // empty diff --git a/org.argeo.node.api/src/org/argeo/node/NodeConstants.java b/org.argeo.node.api/src/org/argeo/node/NodeConstants.java index 22afe0065..75b7826c9 100644 --- a/org.argeo.node.api/src/org/argeo/node/NodeConstants.java +++ b/org.argeo.node.api/src/org/argeo/node/NodeConstants.java @@ -90,6 +90,8 @@ public interface NodeConstants { // Node /** Properties configuring the node repository */ String NODE_REPO_PROP_PREFIX = "argeo.node.repo."; + /** Additional standalone repositories, related to data models. */ + String NODE_REPOS_PROP_PREFIX = "argeo.node.repos."; // HTTP String HTTP_PORT = "org.osgi.service.http.port"; String HTTP_PORT_SECURE = "org.osgi.service.http.port.secure"; -- 2.30.2