From ba8f8a6fb8ad9649dd03b2ac4670d194f0e1be79 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Wed, 10 Aug 2016 14:35:37 +0000 Subject: [PATCH] Continue framework clean up. git-svn-id: https://svn.argeo.org/commons/trunk@9078 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../src/org/argeo/node}/ArgeoLogListener.java | 2 +- .../src/org/argeo/node}/ArgeoLogger.java | 2 +- .../src/org/argeo/node/RepoConf.java | 4 +- .../internal/auth/ConsoleCallbackHandler.java | 1 - .../cms/internal/auth}/LocaleChoice.java | 2 +- .../argeo/cms/internal/kernel/Activator.java | 4 +- .../argeo/cms/internal/kernel/CmsState.java | 190 +++++++----- .../cms/internal/kernel/KernelHeader.java | 10 - .../cms/internal/kernel/KernelThread.java | 4 +- .../argeo/cms/internal/kernel/NodeLogger.java | 4 +- .../cms/internal/kernel/NodeUserAdmin.java | 8 +- .../internal/kernel/RepositoryService.java | 4 +- .../cms/internal/kernel/SecurityProfile.java | 288 ++++++++++++++++++ .../org/argeo/cms/widgets/auth/CmsLogin.java | 2 +- .../auth/CompositeCallbackHandler.java | 2 +- .../argeo/osgi/useradmin/LdifParserTest.java | 1 + .../argeo/osgi/useradmin/LdifUserAdmin.java | 2 + .../util/naming/AttributesDictionary.java | 162 ++++++++++ .../useradmin => util/naming}/LdifParser.java | 11 +- .../useradmin => util/naming}/LdifWriter.java | 40 +-- .../META-INF/spring/osgi.xml | 2 +- .../argeo/security/ui/views/AdminLogView.java | 2 +- .../security/ui/views/LogContentProvider.java | 2 +- .../org/argeo/security/ui/views/LogView.java | 4 +- 24 files changed, 623 insertions(+), 130 deletions(-) rename {org.argeo.util/src/org/argeo => org.argeo.cms.api/src/org/argeo/node}/ArgeoLogListener.java (98%) rename {org.argeo.util/src/org/argeo => org.argeo.cms.api/src/org/argeo/node}/ArgeoLogger.java (98%) rename {org.argeo.util/src/org/argeo/util => org.argeo.cms/src/org/argeo/cms/internal/auth}/LocaleChoice.java (99%) delete mode 100644 org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelHeader.java create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/kernel/SecurityProfile.java create mode 100644 org.argeo.security.core/src/org/argeo/util/naming/AttributesDictionary.java rename org.argeo.security.core/src/org/argeo/{osgi/useradmin => util/naming}/LdifParser.java (95%) rename org.argeo.security.core/src/org/argeo/{osgi/useradmin => util/naming}/LdifWriter.java (62%) diff --git a/org.argeo.util/src/org/argeo/ArgeoLogListener.java b/org.argeo.cms.api/src/org/argeo/node/ArgeoLogListener.java similarity index 98% rename from org.argeo.util/src/org/argeo/ArgeoLogListener.java rename to org.argeo.cms.api/src/org/argeo/node/ArgeoLogListener.java index bac8a9820..698dfe1be 100644 --- a/org.argeo.util/src/org/argeo/ArgeoLogListener.java +++ b/org.argeo.cms.api/src/org/argeo/node/ArgeoLogListener.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.argeo; +package org.argeo.node; /** Framework agnostic interface for log notifications */ public interface ArgeoLogListener { diff --git a/org.argeo.util/src/org/argeo/ArgeoLogger.java b/org.argeo.cms.api/src/org/argeo/node/ArgeoLogger.java similarity index 98% rename from org.argeo.util/src/org/argeo/ArgeoLogger.java rename to org.argeo.cms.api/src/org/argeo/node/ArgeoLogger.java index 0657c29dd..213286d44 100644 --- a/org.argeo.util/src/org/argeo/ArgeoLogger.java +++ b/org.argeo.cms.api/src/org/argeo/node/ArgeoLogger.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.argeo; +package org.argeo.node; /** * Logging framework agnostic identifying a logging service, to which one can diff --git a/org.argeo.cms.api/src/org/argeo/node/RepoConf.java b/org.argeo.cms.api/src/org/argeo/node/RepoConf.java index 90d33322f..be4f6f7f7 100644 --- a/org.argeo.cms.api/src/org/argeo/node/RepoConf.java +++ b/org.argeo.cms.api/src/org/argeo/node/RepoConf.java @@ -5,6 +5,7 @@ public enum RepoConf implements EnumAD { /** Repository type */ type("localfs"), /** Default workspace */ + @Deprecated defaultWorkspace("main"), /** Database URL */ dburl(null), @@ -15,14 +16,13 @@ public enum RepoConf implements EnumAD { /** The identifier (can be an URL locating the repo) */ labeledUri(null), - - httpPort(8080), // // JACKRABBIT SPECIFIC // /** Maximum database pool size */ maxPoolSize(10), /** Maximum cache size in MB */ + @Deprecated maxCacheMB(null), /** Bundle cache size in MB */ bundleCacheMB(8), diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java index acb75d1e5..4edcb6341 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java @@ -14,7 +14,6 @@ import javax.security.auth.callback.TextOutputCallback; import javax.security.auth.callback.UnsupportedCallbackException; import org.argeo.ArgeoException; -import org.argeo.util.LocaleChoice; /** Callback handler to be used with a command line UI. */ public class ConsoleCallbackHandler implements CallbackHandler { diff --git a/org.argeo.util/src/org/argeo/util/LocaleChoice.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/LocaleChoice.java similarity index 99% rename from org.argeo.util/src/org/argeo/util/LocaleChoice.java rename to org.argeo.cms/src/org/argeo/cms/internal/auth/LocaleChoice.java index 6609f4c94..9ec9f7a0b 100644 --- a/org.argeo.util/src/org/argeo/util/LocaleChoice.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/LocaleChoice.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.argeo.util; +package org.argeo.cms.internal.auth; import java.util.ArrayList; import java.util.Collections; 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 3546647dd..03cbbd90d 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 @@ -9,8 +9,8 @@ import java.util.Locale; 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.ArgeoLogger; import org.argeo.node.NodeConstants; import org.argeo.node.NodeState; import org.argeo.node.RepoConf; @@ -93,7 +93,7 @@ public class Activator implements BundleActivator { log.debug("Clean node state"); Dictionary envProps = getStatePropertiesFromEnvironment(); // Use the UUID of the first framework run as state UUID - cn = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_UUID); + cn = bc.getProperty(Constants.FRAMEWORK_UUID); envProps.put(NodeConstants.CN, cn); nodeConf.update(envProps); } else { 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 index 7ad61e50f..64a9d170b 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java @@ -3,9 +3,8 @@ 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.auth.LocaleChoice.asLocaleList; 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; @@ -15,10 +14,12 @@ import java.net.UnknownHostException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Dictionary; import java.util.Hashtable; import java.util.List; import java.util.Locale; +import java.util.UUID; import javax.jcr.RepositoryFactory; import javax.transaction.TransactionManager; @@ -73,15 +74,13 @@ public class CmsState implements NodeState, ManagedService { private Locale defaultLocale; private List locales = null; - // Standalone services - private BitronixTransactionManager transactionManager; - private BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry; - private NodeRepositoryFactory repositoryFactory; - - // Security - private NodeUserAdmin userAdmin; - private RepositoryServiceFactory repositoryServiceFactory; - private RepositoryService repositoryService; + // private BitronixTransactionManager transactionManager; + // private BitronixTransactionSynchronizationRegistry + // transactionSynchronizationRegistry; + // private NodeRepositoryFactory repositoryFactory; + // private NodeUserAdmin userAdmin; + // private RepositoryServiceFactory repositoryServiceFactory; + // private RepositoryService repositoryService; // Deployment private final CmsDeployment nodeDeployment = new CmsDeployment(); @@ -89,8 +88,9 @@ public class CmsState implements NodeState, ManagedService { private boolean cleanState = false; private URI nodeRepoUri = null; - ThreadGroup threadGroup = new ThreadGroup("CMS State"); + private ThreadGroup threadGroup = new ThreadGroup("CMS"); private KernelThread kernelThread; + private List shutdownHooks = new ArrayList<>(); private String hostname; @@ -123,23 +123,14 @@ public class CmsState implements NodeState, ManagedService { nodeRepoUri = KernelUtils.getOsgiInstanceUri("repos/node"); - // pre-requisite initI18n(properties); - initTrackers(); - // standalone services - initTransactionManager(); - initRepositoryFactory(); - // UI - initUi(); - // Deployment + initServices(); initDeployConfigs(properties); - initUserAdmin(); - initRepositories(properties); initWebServer(); initNodeDeployment(); // kernel thread - kernelThread = new KernelThread(this); + kernelThread = new KernelThread(threadGroup, "Kernel Thread"); kernelThread.setContextClassLoader(getClass().getClassLoader()); kernelThread.start(); } catch (Exception e) { @@ -147,11 +138,6 @@ public class CmsState implements NodeState, ManagedService { } } - private void initTrackers() { - new ServiceTracker(bc, HttpService.class, new PrepareHttpStc()).open(); - new ServiceTracker<>(bc, RepositoryContext.class, new RepositoryContextStc()).open(); - } - private void initI18n(Dictionary stateProps) { Object defaultLocaleValue = stateProps.get(NodeConstants.I18N_DEFAULT_LOCALE); defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString()) @@ -159,9 +145,57 @@ public class CmsState implements NodeState, ManagedService { locales = asLocaleList(stateProps.get(NodeConstants.I18N_LOCALES)); } + private void initServices() { + // trackers + new ServiceTracker(bc, HttpService.class, new PrepareHttpStc()).open(); + new ServiceTracker<>(bc, RepositoryContext.class, new RepositoryContextStc()).open(); + + initTransactionManager(); + + // JCR + RepositoryServiceFactory repositoryServiceFactory = new RepositoryServiceFactory(); + shutdownHooks.add(() -> repositoryServiceFactory.shutdown()); + bc.registerService(ManagedServiceFactory.class, repositoryServiceFactory, + LangUtils.init(Constants.SERVICE_PID, NodeConstants.JACKRABBIT_FACTORY_PID)); + + NodeRepositoryFactory repositoryFactory = new NodeRepositoryFactory(); + bc.registerService(RepositoryFactory.class, repositoryFactory, null); + + RepositoryService repositoryService = new RepositoryService(); + shutdownHooks.add(() -> repositoryService.shutdown()); + bc.registerService(LangUtils.names(ManagedService.class, MetaTypeProvider.class), repositoryService, + LangUtils.init(Constants.SERVICE_PID, NodeConstants.NODE_REPO_PID)); + + // Security + NodeUserAdmin userAdmin = new NodeUserAdmin(); + shutdownHooks.add(() -> userAdmin.destroy()); + Dictionary props = userAdmin.currentState(); + props.put(Constants.SERVICE_PID, NodeConstants.NODE_USER_ADMIN_PID); + bc.registerService(UserAdmin.class, userAdmin, props); + + // UI + bc.registerService(ApplicationConfiguration.class, new MaintenanceUi(), + LangUtils.init(PROPERTY_CONTEXT_NAME, "system")); + bc.registerService(ApplicationConfiguration.class, new UserUi(), LangUtils.init(PROPERTY_CONTEXT_NAME, "user")); + } + // private void initUserAdmin() { + // userAdmin = new NodeUserAdmin(); + // // register + // Dictionary props = userAdmin.currentState(); + // props.put(Constants.SERVICE_PID, NodeConstants.NODE_USER_ADMIN_PID); + // // TODO use ManagedService + // bc.registerService(UserAdmin.class, userAdmin, props); + // } + private void initTransactionManager() { + // TODO manage it in a managed service, as startup could be long + ServiceReference existingTm = bc.getServiceReference(TransactionManager.class); + if (existingTm != null) { + if (log.isDebugEnabled()) + log.debug("Using provided transaction manager " + existingTm); + } bitronix.tm.Configuration tmConf = TransactionManagerServices.getConfiguration(); - tmConf.setServerId(getFrameworkProp(FRAMEWORK_UUID)); + tmConf.setServerId(UUID.randomUUID().toString()); Bundle bitronixBundle = FrameworkUtil.getBundle(bitronix.tm.Configuration.class); File tmBaseDir = bitronixBundle.getDataFile(KernelConstants.DIR_TRANSACTIONS); @@ -171,26 +205,31 @@ public class CmsState implements NodeState, ManagedService { File tmDir2 = new File(tmBaseDir, "btm2"); tmDir2.mkdirs(); tmConf.setLogPart2Filename(new File(tmDir2, tmDir2.getName() + ".tlog").getAbsolutePath()); - transactionManager = getTransactionManager(); - transactionSynchronizationRegistry = getTransactionSynchronizationRegistry(); + + BitronixTransactionManager transactionManager = getTransactionManager(); + shutdownHooks.add(() -> transactionManager.shutdown()); + BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry = getTransactionSynchronizationRegistry(); // register bc.registerService(TransactionManager.class, transactionManager, null); bc.registerService(UserTransaction.class, transactionManager, null); bc.registerService(TransactionSynchronizationRegistry.class, transactionSynchronizationRegistry, null); + if (log.isDebugEnabled()) + log.debug("Initialised default Bitronix transaction manager"); } - private void initRepositoryFactory() { - // TODO rationalise RepositoryFactory - repositoryFactory = new NodeRepositoryFactory(); - // register - bc.registerService(RepositoryFactory.class, repositoryFactory, null); - } + // private void initRepositoryFactory() { + // // TODO rationalise RepositoryFactory + // repositoryFactory = new NodeRepositoryFactory(); + // // register + // bc.registerService(RepositoryFactory.class, repositoryFactory, null); + // } - private void initUi() { - bc.registerService(ApplicationConfiguration.class, new MaintenanceUi(), - LangUtils.init(PROPERTY_CONTEXT_NAME, "system")); - bc.registerService(ApplicationConfiguration.class, new UserUi(), LangUtils.init(PROPERTY_CONTEXT_NAME, "user")); - } + // private void initUi() { + // bc.registerService(ApplicationConfiguration.class, new MaintenanceUi(), + // LangUtils.init(PROPERTY_CONTEXT_NAME, "system")); + // bc.registerService(ApplicationConfiguration.class, new UserUi(), + // LangUtils.init(PROPERTY_CONTEXT_NAME, "user")); + // } private void initDeployConfigs(Dictionary stateProps) throws IOException { Path deployPath = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE + '/' + KernelConstants.DIR_DEPLOY); @@ -228,25 +267,20 @@ public class CmsState implements NodeState, ManagedService { } } - private void initUserAdmin() { - userAdmin = new NodeUserAdmin(); - // register - Dictionary props = userAdmin.currentState(); - props.put(Constants.SERVICE_PID, NodeConstants.NODE_USER_ADMIN_PID); - // TODO use ManagedService - bc.registerService(UserAdmin.class, userAdmin, props); - } - - private void initRepositories(Dictionary stateProps) throws IOException { - // register - repositoryServiceFactory = new RepositoryServiceFactory(); - bc.registerService(ManagedServiceFactory.class, repositoryServiceFactory, - LangUtils.init(Constants.SERVICE_PID, NodeConstants.JACKRABBIT_FACTORY_PID)); - - repositoryService = new RepositoryService(); - Dictionary regProps = LangUtils.init(Constants.SERVICE_PID, NodeConstants.NODE_REPO_PID); - bc.registerService(LangUtils.names(ManagedService.class, MetaTypeProvider.class), repositoryService, regProps); - } + // private void initRepositories(Dictionary stateProps) throws + // IOException { + // // register + // repositoryServiceFactory = new RepositoryServiceFactory(); + // bc.registerService(ManagedServiceFactory.class, repositoryServiceFactory, + // LangUtils.init(Constants.SERVICE_PID, + // NodeConstants.JACKRABBIT_FACTORY_PID)); + // + // repositoryService = new RepositoryService(); + // Dictionary regProps = + // LangUtils.init(Constants.SERVICE_PID, NodeConstants.NODE_REPO_PID); + // bc.registerService(LangUtils.names(ManagedService.class, + // MetaTypeProvider.class), repositoryService, regProps); + // } private void initWebServer() { String httpPort = getFrameworkProp("org.osgi.service.http.port"); @@ -291,21 +325,35 @@ public class CmsState implements NodeState, ManagedService { } void shutdown() { + // if (transactionManager != null) + // transactionManager.shutdown(); + // if (userAdmin != null) + // userAdmin.destroy(); + // if (repositoryServiceFactory != null) + // repositoryServiceFactory.shutdown(); + + applyShutdownHooks(); + if (kernelThread != null) kernelThread.destroyAndJoin(); - if (transactionManager != null) - transactionManager.shutdown(); - if (userAdmin != null) - userAdmin.destroy(); - if (repositoryServiceFactory != null) - repositoryServiceFactory.shutdown(); + if (log.isDebugEnabled()) + log.debug("## CMS STOPPED"); + } + /** Apply shutdown hoos in reverse order. */ + private void applyShutdownHooks() { + for (int i = shutdownHooks.size() - 1; i >= 0; i--) { + try { + // new Thread(shutdownHooks.get(i), "CMS Shutdown Hook #" + + // i).start(); + shutdownHooks.get(i).run(); + } catch (Exception e) { + log.error("Could not run shutdown hook #" + i); + } + } // Clean hanging Gogo shell thread new GogoShellKiller().start(); - - if (log.isDebugEnabled()) - log.debug("## CMS STOPPED"); } private Dictionary getNodeConfig(Dictionary properties) { @@ -415,7 +463,7 @@ public class CmsState implements NodeState, ManagedService { private class GogoShellKiller extends Thread { public GogoShellKiller() { - super("Gogo shell killer"); + super("Gogo Shell Killer"); setDaemon(true); } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelHeader.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelHeader.java deleted file mode 100644 index f82f6b1d1..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelHeader.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.argeo.cms.internal.kernel; - -import java.util.List; -import java.util.Locale; - -public interface KernelHeader { - public Locale getDefaultLocale(); - - public List getLocales(); -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelThread.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelThread.java index bd5828602..944a7cd3d 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelThread.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelThread.java @@ -30,8 +30,8 @@ class KernelThread extends Thread { @SuppressWarnings("unused") private long cycle = 0l; - public KernelThread(CmsState cmState) { - super(cmState.threadGroup, cmState.getClass().getSimpleName()); + public KernelThread(ThreadGroup threadGroup, String name) { + super(threadGroup, name); } private void doSmallestPeriod() { 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 de28ac118..b9475446e 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 @@ -38,9 +38,9 @@ import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import org.apache.log4j.spi.LoggingEvent; import org.argeo.ArgeoException; -import org.argeo.ArgeoLogListener; -import org.argeo.ArgeoLogger; import org.argeo.cms.auth.CurrentUser; +import org.argeo.node.ArgeoLogListener; +import org.argeo.node.ArgeoLogger; import org.osgi.service.log.LogEntry; import org.osgi.service.log.LogListener; import org.osgi.service.log.LogReaderService; 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 3cc3dbfb3..8f1c36860 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 @@ -36,6 +36,8 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedService; import org.osgi.service.useradmin.Authorization; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.User; @@ -49,7 +51,7 @@ import bitronix.tm.resource.ehcache.EhCacheXAResourceProducer; * Aggregates multiple {@link UserDirectory} and integrates them with this node * system roles. */ -public class NodeUserAdmin implements UserAdmin, KernelConstants { +class NodeUserAdmin implements UserAdmin, ManagedService, KernelConstants { private final static Log log = LogFactory.getLog(NodeUserAdmin.class); final static LdapName ROLES_BASE; static { @@ -86,6 +88,10 @@ public class NodeUserAdmin implements UserAdmin, KernelConstants { new ServiceTracker<>(bc, TransactionManager.class, new TransactionManagerStc()).open(); } + @Override + public void updated(Dictionary properties) throws ConfigurationException { + } + private class TransactionManagerStc implements ServiceTrackerCustomizer { @Override diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/RepositoryService.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/RepositoryService.java index ed8be17ea..8525174e2 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/RepositoryService.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/RepositoryService.java @@ -23,7 +23,7 @@ public class RepositoryService implements ManagedService, MetaTypeProvider { private ServiceRegistration repositoryContextReg; @Override - public synchronized void updated(Dictionary properties) throws ConfigurationException { + public void updated(Dictionary properties) throws ConfigurationException { if (properties == null) return; @@ -52,7 +52,7 @@ public class RepositoryService implements ManagedService, MetaTypeProvider { } - public synchronized void shutdown() { + public void shutdown() { if (repositoryContextReg == null) return; RepositoryContext repositoryContext = bc.getService(repositoryContextReg.getReference()); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/SecurityProfile.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/SecurityProfile.java new file mode 100644 index 000000000..358b212b1 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/SecurityProfile.java @@ -0,0 +1,288 @@ +package org.argeo.cms.internal.kernel; + +import java.io.FilePermission; +import java.lang.reflect.ReflectPermission; +import java.net.SocketPermission; +import java.security.AllPermission; +import java.util.PropertyPermission; + +import javax.management.MBeanPermission; +import javax.management.MBeanServerPermission; +import javax.management.MBeanTrustPermission; +import javax.security.auth.AuthPermission; + +import org.osgi.framework.AdminPermission; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServicePermission; +import org.osgi.service.cm.ConfigurationPermission; +import org.osgi.service.condpermadmin.BundleLocationCondition; +import org.osgi.service.condpermadmin.ConditionInfo; +import org.osgi.service.condpermadmin.ConditionalPermissionAdmin; +import org.osgi.service.condpermadmin.ConditionalPermissionInfo; +import org.osgi.service.condpermadmin.ConditionalPermissionUpdate; +import org.osgi.service.permissionadmin.PermissionInfo; + +import bitronix.tm.BitronixTransactionManager; + +public interface SecurityProfile { + BundleContext bc = FrameworkUtil.getBundle(SecurityProfile.class).getBundleContext(); + + default void applySystemPermissions(ConditionalPermissionAdmin permissionAdmin) { + ConditionalPermissionUpdate update = permissionAdmin.newConditionalPermissionUpdate(); + // Self + update.getConditionalPermissionInfos() + .add(permissionAdmin.newConditionalPermissionInfo(null, + new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(), + new String[] { locate(SecurityProfile.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 */ + default String locate(Class clzz) { + return FrameworkUtil.getBundle(clzz).getLocation(); + } + + /** Can be null */ + default Bundle findBundle(String symbolicName) { + for (Bundle b : bc.getBundles()) + if (b.getSymbolicName().equals(symbolicName)) + return b; + return null; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/auth/CmsLogin.java b/org.argeo.cms/src/org/argeo/cms/widgets/auth/CmsLogin.java index 3e5e25d04..8f00c4577 100644 --- a/org.argeo.cms/src/org/argeo/cms/widgets/auth/CmsLogin.java +++ b/org.argeo.cms/src/org/argeo/cms/widgets/auth/CmsLogin.java @@ -29,9 +29,9 @@ import org.argeo.cms.CmsView; import org.argeo.cms.auth.CurrentUser; import org.argeo.cms.auth.HttpRequestCallback; import org.argeo.cms.i18n.LocaleUtils; +import org.argeo.cms.internal.auth.LocaleChoice; import org.argeo.cms.util.CmsUtils; import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.util.LocaleChoice; import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseAdapter; diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java index bc99bc48b..55190d39c 100644 --- a/org.argeo.cms/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java +++ b/org.argeo.cms/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java @@ -10,7 +10,7 @@ import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.TextOutputCallback; import javax.security.auth.callback.UnsupportedCallbackException; -import org.argeo.util.LocaleChoice; +import org.argeo.cms.internal.auth.LocaleChoice; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; diff --git a/org.argeo.security.core/ext/test/org/argeo/osgi/useradmin/LdifParserTest.java b/org.argeo.security.core/ext/test/org/argeo/osgi/useradmin/LdifParserTest.java index 34ac98b1c..aabc9a0e7 100644 --- a/org.argeo.security.core/ext/test/org/argeo/osgi/useradmin/LdifParserTest.java +++ b/org.argeo.security.core/ext/test/org/argeo/osgi/useradmin/LdifParserTest.java @@ -13,6 +13,7 @@ import junit.framework.TestCase; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.digest.DigestUtils; +import org.argeo.util.naming.LdifParser; public class LdifParserTest extends TestCase implements BasicTestConstants { public void testBasicLdif() throws Exception { diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java index 7b87a4b6e..fbedeb79c 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java +++ b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java @@ -23,6 +23,8 @@ import javax.naming.ldap.LdapName; import javax.transaction.TransactionManager; import org.apache.commons.io.IOUtils; +import org.argeo.util.naming.LdifParser; +import org.argeo.util.naming.LdifWriter; import org.osgi.framework.Filter; import org.osgi.service.useradmin.Role; diff --git a/org.argeo.security.core/src/org/argeo/util/naming/AttributesDictionary.java b/org.argeo.security.core/src/org/argeo/util/naming/AttributesDictionary.java new file mode 100644 index 000000000..a6fddb440 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/util/naming/AttributesDictionary.java @@ -0,0 +1,162 @@ +package org.argeo.util.naming; + +import java.util.Dictionary; +import java.util.Enumeration; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; + +public class AttributesDictionary extends Dictionary { + private final Attributes attributes; + + /** The provided attributes is wrapped, not copied. */ + public AttributesDictionary(Attributes attributes) { + if (attributes == null) + throw new IllegalArgumentException("Attributes cannot be null"); + this.attributes = attributes; + } + + @Override + public int size() { + return attributes.size(); + } + + @Override + public boolean isEmpty() { + return attributes.size() == 0; + } + + @Override + public Enumeration keys() { + NamingEnumeration namingEnumeration = attributes.getIDs(); + return new Enumeration() { + + @Override + public boolean hasMoreElements() { + return namingEnumeration.hasMoreElements(); + } + + @Override + public String nextElement() { + return namingEnumeration.nextElement(); + } + + }; + } + + @Override + public Enumeration elements() { + NamingEnumeration namingEnumeration = attributes.getIDs(); + return new Enumeration() { + + @Override + public boolean hasMoreElements() { + return namingEnumeration.hasMoreElements(); + } + + @Override + public Object nextElement() { + String key = namingEnumeration.nextElement(); + return get(key); + } + + }; + } + + @Override + /** @returns a String or String[] */ + public Object get(Object key) { + try { + if (key == null) + throw new IllegalArgumentException("Key cannot be null"); + Attribute attr = attributes.get(key.toString()); + if (attr == null) + return null; + if (attr.size() == 0) + throw new IllegalStateException("There must be at least one value"); + else if (attr.size() == 1) { + return attr.get().toString(); + } else {// multiple + String[] res = new String[attr.size()]; + for (int i = 0; i < attr.size(); i++) { + Object value = attr.get(); + if (value == null) + throw new RuntimeException("Values cannot be null"); + res[i] = attr.get(i).toString(); + } + return res; + } + } catch (NamingException e) { + throw new RuntimeException("Cannot get value for " + key, e); + } + } + + @Override + public Object put(String key, Object value) { + if (key == null) + throw new IllegalArgumentException("Key cannot be null"); + if (value == null) + throw new IllegalArgumentException("Value cannot be null"); + + Object oldValue = get(key); + Attribute attr = attributes.get(key); + if (attr == null) { + attr = new BasicAttribute(key); + attributes.put(attr); + } + + if (value instanceof String[]) { + String[] values = (String[]) value; + // clean additional values + for (int i = values.length; i < attr.size(); i++) + attr.remove(i); + // set values + for (int i = 0; i < values.length; i++) { + attr.set(i, values[i]); + } + } else { + if (attr.size() != 1) + throw new IllegalArgumentException("Attribute " + key + " is multi-valued"); + attr.set(0, value.toString()); + } + return oldValue; + } + + @Override + public Object remove(Object key) { + if (key == null) + throw new IllegalArgumentException("Key cannot be null"); + Object oldValue = get(key); + if (oldValue == null) + return null; + return attributes.remove(key.toString()); + } + + /** + * Copy the content of an {@link javax.naming.Attributes} to the + * provided {@link Dictionary}. + */ + public static void copy(Attributes attributes, Dictionary dictionary) { + AttributesDictionary ad = new AttributesDictionary(attributes); + Enumeration keys = ad.keys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + dictionary.put(key, ad.get(key)); + } + } + + /** + * Copy a {@link Dictionary} into an {@link javax.naming.Attributes}. + */ + public static void copy(Dictionary dictionary, Attributes attributes) { + AttributesDictionary ad = new AttributesDictionary(attributes); + Enumeration keys = dictionary.keys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + ad.put(key, dictionary.get(key)); + } + } +} diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifParser.java b/org.argeo.security.core/src/org/argeo/util/naming/LdifParser.java similarity index 95% rename from org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifParser.java rename to org.argeo.security.core/src/org/argeo/util/naming/LdifParser.java index da793adce..66e529e5e 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifParser.java +++ b/org.argeo.security.core/src/org/argeo/util/naming/LdifParser.java @@ -1,4 +1,4 @@ -package org.argeo.osgi.useradmin; +package org.argeo.util.naming; import static org.argeo.osgi.useradmin.LdifName.dn; @@ -21,9 +21,10 @@ import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.argeo.osgi.useradmin.UserDirectoryException; /** Basic LDIF parser. */ -class LdifParser { +public class LdifParser { private final static Log log = LogFactory.getLog(LdifParser.class); protected Attributes addAttributes(SortedMap res, @@ -48,11 +49,7 @@ class LdifParser { } } - static void checkDnConsistency() { - - } - - SortedMap read(InputStream in) throws IOException { + public SortedMap read(InputStream in) throws IOException { SortedMap res = new TreeMap(); try { List lines = IOUtils.readLines(in); diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifWriter.java b/org.argeo.security.core/src/org/argeo/util/naming/LdifWriter.java similarity index 62% rename from org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifWriter.java rename to org.argeo.security.core/src/org/argeo/util/naming/LdifWriter.java index ba393cad1..76586c3b3 100644 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifWriter.java +++ b/org.argeo.security.core/src/org/argeo/util/naming/LdifWriter.java @@ -1,4 +1,4 @@ -package org.argeo.osgi.useradmin; +package org.argeo.util.naming; import static org.argeo.osgi.useradmin.LdifName.dn; @@ -15,34 +15,38 @@ import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; import org.apache.commons.codec.binary.Base64; +import org.argeo.osgi.useradmin.UserDirectoryException; /** Basic LDIF writer */ -class LdifWriter { +public class LdifWriter { private final Writer writer; - LdifWriter(OutputStream out) { - this.writer = new OutputStreamWriter(out); + /** Writer must be closed by caller */ + public LdifWriter(Writer writer) { + this.writer = writer; } - void writeEntry(LdapName name, Attributes attributes) throws IOException { + /** Stream must be closed by caller */ + public LdifWriter(OutputStream out) { + this(new OutputStreamWriter(out)); + } + + public void writeEntry(LdapName name, Attributes attributes) throws IOException { try { // check consistency Rdn nameRdn = name.getRdn(name.size() - 1); Attribute nameAttr = attributes.get(nameRdn.getType()); if (!nameAttr.get().equals(nameRdn.getValue())) - throw new UserDirectoryException("Attribute " - + nameAttr.getID() + "=" + nameAttr.get() - + " not consistent with DN " + name); + throw new UserDirectoryException( + "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name); writer.append(dn.name() + ":").append(name.toString()).append('\n'); Attribute objectClassAttr = attributes.get("objectClass"); if (objectClassAttr != null) writeAttribute(objectClassAttr); - for (NamingEnumeration attrs = attributes - .getAll(); attrs.hasMore();) { + for (NamingEnumeration attrs = attributes.getAll(); attrs.hasMore();) { Attribute attribute = attrs.next(); - if (attribute.getID().equals(dn.name()) - || attribute.getID().equals("objectClass")) + if (attribute.getID().equals(dn.name()) || attribute.getID().equals("objectClass")) continue;// skip DN attribute writeAttribute(attribute); } @@ -53,18 +57,14 @@ class LdifWriter { } } - private void writeAttribute(Attribute attribute) throws NamingException, - IOException { - for (NamingEnumeration attrValues = attribute.getAll(); attrValues - .hasMore();) { + protected void writeAttribute(Attribute attribute) throws NamingException, IOException { + for (NamingEnumeration attrValues = attribute.getAll(); attrValues.hasMore();) { Object value = attrValues.next(); if (value instanceof byte[]) { String encoded = Base64.encodeBase64String((byte[]) value); - writer.append(attribute.getID()).append("::").append(encoded) - .append('\n'); + writer.append(attribute.getID()).append("::").append(encoded).append('\n'); } else { - writer.append(attribute.getID()).append(':') - .append(value.toString()).append('\n'); + writer.append(attribute.getID()).append(':').append(value.toString()).append('\n'); } } } diff --git a/org.argeo.security.ui/META-INF/spring/osgi.xml b/org.argeo.security.ui/META-INF/spring/osgi.xml index cb2cbfb2f..a3b8e5fdf 100644 --- a/org.argeo.security.ui/META-INF/spring/osgi.xml +++ b/org.argeo.security.ui/META-INF/spring/osgi.xml @@ -8,7 +8,7 @@ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> -