From: Mathieu Baudier Date: Wed, 10 Aug 2016 14:35:37 +0000 (+0000) Subject: Continue framework clean up. X-Git-Tag: argeo-commons-2.1.45~33 X-Git-Url: http://git.argeo.org/?a=commitdiff_plain;h=ba8f8a6fb8ad9649dd03b2ac4670d194f0e1be79;p=lgpl%2Fargeo-commons.git Continue framework clean up. git-svn-id: https://svn.argeo.org/commons/trunk@9078 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- diff --git a/org.argeo.cms.api/src/org/argeo/node/ArgeoLogListener.java b/org.argeo.cms.api/src/org/argeo/node/ArgeoLogListener.java new file mode 100644 index 000000000..698dfe1be --- /dev/null +++ b/org.argeo.cms.api/src/org/argeo/node/ArgeoLogListener.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.node; + +/** Framework agnostic interface for log notifications */ +public interface ArgeoLogListener { + /** + * Appends a log + * + * @param username + * authentified user, null for anonymous + * @param level + * INFO, DEBUG, WARN, etc. (logging framework specific) + * @param category + * hierarchy (logging framework specific) + * @param thread + * name of the thread which logged this message + * @param msg + * any object as long as its toString() method returns the + * message + * @param the + * exception in log4j ThrowableStrRep format + */ + public void appendLog(String username, Long timestamp, String level, + String category, String thread, Object msg, String[] exception); +} diff --git a/org.argeo.cms.api/src/org/argeo/node/ArgeoLogger.java b/org.argeo.cms.api/src/org/argeo/node/ArgeoLogger.java new file mode 100644 index 000000000..213286d44 --- /dev/null +++ b/org.argeo.cms.api/src/org/argeo/node/ArgeoLogger.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.node; + +/** + * Logging framework agnostic identifying a logging service, to which one can + * register + */ +public interface ArgeoLogger { + /** + * Register for events by threads with the same authentication (or all + * threads if admin) + */ + public void register(ArgeoLogListener listener, + Integer numberOfPreviousEvents); + + /** + * For admin use only: register for all users + * + * @param listener + * the log listener + * @param numberOfPreviousEvents + * the number of previous events to notify + * @param everything + * if true even anonymous is logged + */ + public void registerForAll(ArgeoLogListener listener, + Integer numberOfPreviousEvents, boolean everything); + + public void unregister(ArgeoLogListener listener); + + public void unregisterForAll(ArgeoLogListener listener); +} 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.cms/src/org/argeo/cms/internal/auth/LocaleChoice.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/LocaleChoice.java new file mode 100644 index 000000000..9ec9f7a0b --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/LocaleChoice.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.cms.internal.auth; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import javax.security.auth.callback.LanguageCallback; + +import org.argeo.ArgeoException; + +/** Choose in a list of locales. TODO: replace with {@link LanguageCallback} */ +public class LocaleChoice { + private final List locales; + + private Integer selectedIndex = null; + private final Integer defaultIndex; + + public LocaleChoice(List locales, Locale defaultLocale) { + Integer defaultIndex = null; + this.locales = Collections.unmodifiableList(locales); + for (int i = 0; i < locales.size(); i++) + if (locales.get(i).equals(defaultLocale)) + defaultIndex = i; + + // based on language only + if (defaultIndex == null) + for (int i = 0; i < locales.size(); i++) + 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); + this.defaultIndex = defaultIndex; + + this.selectedIndex = defaultIndex; + } + + /** + * Convenience constructor based on a comma separated list of iso codes (en, + * en_US, fr_CA, etc.). Default selection is default locale. + */ + public LocaleChoice(String locales, Locale defaultLocale) { + this(asLocaleList(locales), defaultLocale); + } + + public String[] getSupportedLocalesLabels() { + String[] labels = new String[locales.size()]; + for (int i = 0; i < locales.size(); i++) { + Locale locale = locales.get(i); + if (locale.getCountry().equals("")) + labels[i] = locale.getDisplayLanguage(locale) + " [" + locale.getLanguage() + "]"; + else + labels[i] = locale.getDisplayLanguage(locale) + " (" + locale.getDisplayCountry(locale) + ") [" + + locale.getLanguage() + "_" + locale.getCountry() + "]"; + + } + return labels; + } + + public Locale getSelectedLocale() { + if (selectedIndex == null) + return null; + return locales.get(selectedIndex); + } + + public void setSelectedIndex(Integer selectedIndex) { + this.selectedIndex = selectedIndex; + } + + public Integer getSelectedIndex() { + return selectedIndex; + } + + public Integer getDefaultIndex() { + return defaultIndex; + } + + public List getLocales() { + return locales; + } + + public Locale getDefaultLocale() { + return locales.get(getDefaultIndex()); + } + + /** Returns null if argument is null. */ + public static List asLocaleList(Object locales) { + if (locales == null) + return null; + ArrayList availableLocales = new ArrayList(); + String[] codes = locales.toString().split(","); + for (int i = 0; i < codes.length; i++) { + String code = codes[i]; + // variant not supported + int indexUnd = code.indexOf("_"); + Locale locale; + if (indexUnd > 0) { + String language = code.substring(0, indexUnd); + String country = code.substring(indexUnd + 1); + locale = new Locale(language, country); + } else { + locale = new Locale(code); + } + availableLocales.add(locale); + } + return availableLocales; + } + + 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)); + } + } + +} 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/LdifParser.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifParser.java deleted file mode 100644 index da793adce..000000000 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifParser.java +++ /dev/null @@ -1,147 +0,0 @@ -package org.argeo.osgi.useradmin; - -import static org.argeo.osgi.useradmin.LdifName.dn; - -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.SortedMap; -import java.util.TreeMap; - -import javax.naming.InvalidNameException; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttribute; -import javax.naming.directory.BasicAttributes; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; - -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; - -/** Basic LDIF parser. */ -class LdifParser { - private final static Log log = LogFactory.getLog(LdifParser.class); - - protected Attributes addAttributes(SortedMap res, - int lineNumber, LdapName currentDn, Attributes currentAttributes) { - try { - Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1); - Attribute nameAttr = currentAttributes.get(nameRdn.getType()); - if (nameAttr == null) - currentAttributes.put(nameRdn.getType(), nameRdn.getValue()); - else if (!nameAttr.get().equals(nameRdn.getValue())) - throw new UserDirectoryException("Attribute " - + nameAttr.getID() + "=" + nameAttr.get() - + " not consistent with DN " + currentDn - + " (shortly before line " + lineNumber - + " in LDIF file)"); - Attributes previous = res.put(currentDn, currentAttributes); - if (log.isTraceEnabled()) - log.trace("Added " + currentDn); - return previous; - } catch (NamingException e) { - throw new UserDirectoryException("Cannot add " + currentDn, e); - } - } - - static void checkDnConsistency() { - - } - - SortedMap read(InputStream in) throws IOException { - SortedMap res = new TreeMap(); - try { - List lines = IOUtils.readLines(in); - // add an empty new line since the last line is not checked - if (!lines.get(lines.size() - 1).equals("")) - lines.add(""); - - LdapName currentDn = null; - Attributes currentAttributes = null; - StringBuilder currentEntry = new StringBuilder(); - - readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { - String line = lines.get(lineNumber); - boolean isLastLine = false; - if (lineNumber == lines.size() - 1) - isLastLine = true; - if (line.startsWith(" ")) { - currentEntry.append(line.substring(1)); - if (!isLastLine) - continue readLines; - } - - if (currentEntry.length() != 0 || isLastLine) { - // read previous attribute - StringBuilder attrId = new StringBuilder(8); - boolean isBase64 = false; - readAttrId: for (int i = 0; i < currentEntry.length(); i++) { - char c = currentEntry.charAt(i); - if (c == ':') { - if (i + 1 < currentEntry.length() - && currentEntry.charAt(i + 1) == ':') - isBase64 = true; - currentEntry.delete(0, i + (isBase64 ? 2 : 1)); - break readAttrId; - } else { - attrId.append(c); - } - } - - String attributeId = attrId.toString(); - String cleanValueStr = currentEntry.toString().trim(); - Object attributeValue = isBase64 ? Base64 - .decodeBase64(cleanValueStr) : cleanValueStr; - - // manage DN attributes - if (attributeId.equals(dn.name()) || isLastLine) { - if (currentDn != null) { - // - // ADD - // - Attributes previous = addAttributes(res, - lineNumber, currentDn, currentAttributes); - if (previous != null) { - log.warn("There was already an entry with DN " - + currentDn - + ", which has been discarded by a subsequent one."); - } - } - - if (attributeId.equals(dn.name())) - try { - currentDn = new LdapName( - attributeValue.toString()); - currentAttributes = new BasicAttributes(true); - } catch (InvalidNameException e) { - log.error(attributeValue - + " not a valid DN, skipping the entry."); - currentDn = null; - currentAttributes = null; - } - } - - // store attribute - if (currentAttributes != null) { - Attribute attribute = currentAttributes - .get(attributeId); - if (attribute == null) { - attribute = new BasicAttribute(attributeId); - currentAttributes.put(attribute); - } - attribute.add(attributeValue); - } - currentEntry = new StringBuilder(); - } - currentEntry.append(line); - } - } finally { - IOUtils.closeQuietly(in); - } - return res; - } -} \ No newline at end of file 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/osgi/useradmin/LdifWriter.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifWriter.java deleted file mode 100644 index ba393cad1..000000000 --- a/org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifWriter.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.argeo.osgi.useradmin; - -import static org.argeo.osgi.useradmin.LdifName.dn; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; - -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; - -import org.apache.commons.codec.binary.Base64; - -/** Basic LDIF writer */ -class LdifWriter { - private final Writer writer; - - LdifWriter(OutputStream out) { - this.writer = new OutputStreamWriter(out); - } - - 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); - - 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();) { - Attribute attribute = attrs.next(); - if (attribute.getID().equals(dn.name()) - || attribute.getID().equals("objectClass")) - continue;// skip DN attribute - writeAttribute(attribute); - } - writer.append('\n'); - writer.flush(); - } catch (NamingException e) { - throw new UserDirectoryException("Cannot write LDIF", e); - } - } - - private 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'); - } else { - writer.append(attribute.getID()).append(':') - .append(value.toString()).append('\n'); - } - } - } -} 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/util/naming/LdifParser.java b/org.argeo.security.core/src/org/argeo/util/naming/LdifParser.java new file mode 100644 index 000000000..66e529e5e --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/util/naming/LdifParser.java @@ -0,0 +1,144 @@ +package org.argeo.util.naming; + +import static org.argeo.osgi.useradmin.LdifName.dn; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.naming.InvalidNameException; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +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. */ +public class LdifParser { + private final static Log log = LogFactory.getLog(LdifParser.class); + + protected Attributes addAttributes(SortedMap res, + int lineNumber, LdapName currentDn, Attributes currentAttributes) { + try { + Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1); + Attribute nameAttr = currentAttributes.get(nameRdn.getType()); + if (nameAttr == null) + currentAttributes.put(nameRdn.getType(), nameRdn.getValue()); + else if (!nameAttr.get().equals(nameRdn.getValue())) + throw new UserDirectoryException("Attribute " + + nameAttr.getID() + "=" + nameAttr.get() + + " not consistent with DN " + currentDn + + " (shortly before line " + lineNumber + + " in LDIF file)"); + Attributes previous = res.put(currentDn, currentAttributes); + if (log.isTraceEnabled()) + log.trace("Added " + currentDn); + return previous; + } catch (NamingException e) { + throw new UserDirectoryException("Cannot add " + currentDn, e); + } + } + + public SortedMap read(InputStream in) throws IOException { + SortedMap res = new TreeMap(); + try { + List lines = IOUtils.readLines(in); + // add an empty new line since the last line is not checked + if (!lines.get(lines.size() - 1).equals("")) + lines.add(""); + + LdapName currentDn = null; + Attributes currentAttributes = null; + StringBuilder currentEntry = new StringBuilder(); + + readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { + String line = lines.get(lineNumber); + boolean isLastLine = false; + if (lineNumber == lines.size() - 1) + isLastLine = true; + if (line.startsWith(" ")) { + currentEntry.append(line.substring(1)); + if (!isLastLine) + continue readLines; + } + + if (currentEntry.length() != 0 || isLastLine) { + // read previous attribute + StringBuilder attrId = new StringBuilder(8); + boolean isBase64 = false; + readAttrId: for (int i = 0; i < currentEntry.length(); i++) { + char c = currentEntry.charAt(i); + if (c == ':') { + if (i + 1 < currentEntry.length() + && currentEntry.charAt(i + 1) == ':') + isBase64 = true; + currentEntry.delete(0, i + (isBase64 ? 2 : 1)); + break readAttrId; + } else { + attrId.append(c); + } + } + + String attributeId = attrId.toString(); + String cleanValueStr = currentEntry.toString().trim(); + Object attributeValue = isBase64 ? Base64 + .decodeBase64(cleanValueStr) : cleanValueStr; + + // manage DN attributes + if (attributeId.equals(dn.name()) || isLastLine) { + if (currentDn != null) { + // + // ADD + // + Attributes previous = addAttributes(res, + lineNumber, currentDn, currentAttributes); + if (previous != null) { + log.warn("There was already an entry with DN " + + currentDn + + ", which has been discarded by a subsequent one."); + } + } + + if (attributeId.equals(dn.name())) + try { + currentDn = new LdapName( + attributeValue.toString()); + currentAttributes = new BasicAttributes(true); + } catch (InvalidNameException e) { + log.error(attributeValue + + " not a valid DN, skipping the entry."); + currentDn = null; + currentAttributes = null; + } + } + + // store attribute + if (currentAttributes != null) { + Attribute attribute = currentAttributes + .get(attributeId); + if (attribute == null) { + attribute = new BasicAttribute(attributeId); + currentAttributes.put(attribute); + } + attribute.add(attributeValue); + } + currentEntry = new StringBuilder(); + } + currentEntry.append(line); + } + } finally { + IOUtils.closeQuietly(in); + } + return res; + } +} \ No newline at end of file diff --git a/org.argeo.security.core/src/org/argeo/util/naming/LdifWriter.java b/org.argeo.security.core/src/org/argeo/util/naming/LdifWriter.java new file mode 100644 index 000000000..76586c3b3 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/util/naming/LdifWriter.java @@ -0,0 +1,71 @@ +package org.argeo.util.naming; + +import static org.argeo.osgi.useradmin.LdifName.dn; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +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 */ +public class LdifWriter { + private final Writer writer; + + /** Writer must be closed by caller */ + public LdifWriter(Writer writer) { + this.writer = writer; + } + + /** 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); + + 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();) { + Attribute attribute = attrs.next(); + if (attribute.getID().equals(dn.name()) || attribute.getID().equals("objectClass")) + continue;// skip DN attribute + writeAttribute(attribute); + } + writer.append('\n'); + writer.flush(); + } catch (NamingException e) { + throw new UserDirectoryException("Cannot write LDIF", e); + } + } + + 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'); + } else { + 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"> - locales; - - private Integer selectedIndex = null; - private final Integer defaultIndex; - - public LocaleChoice(List locales, Locale defaultLocale) { - Integer defaultIndex = null; - this.locales = Collections.unmodifiableList(locales); - for (int i = 0; i < locales.size(); i++) - if (locales.get(i).equals(defaultLocale)) - defaultIndex = i; - - // based on language only - if (defaultIndex == null) - for (int i = 0; i < locales.size(); i++) - 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); - this.defaultIndex = defaultIndex; - - this.selectedIndex = defaultIndex; - } - - /** - * Convenience constructor based on a comma separated list of iso codes (en, - * en_US, fr_CA, etc.). Default selection is default locale. - */ - public LocaleChoice(String locales, Locale defaultLocale) { - this(asLocaleList(locales), defaultLocale); - } - - public String[] getSupportedLocalesLabels() { - String[] labels = new String[locales.size()]; - for (int i = 0; i < locales.size(); i++) { - Locale locale = locales.get(i); - if (locale.getCountry().equals("")) - labels[i] = locale.getDisplayLanguage(locale) + " [" + locale.getLanguage() + "]"; - else - labels[i] = locale.getDisplayLanguage(locale) + " (" + locale.getDisplayCountry(locale) + ") [" - + locale.getLanguage() + "_" + locale.getCountry() + "]"; - - } - return labels; - } - - public Locale getSelectedLocale() { - if (selectedIndex == null) - return null; - return locales.get(selectedIndex); - } - - public void setSelectedIndex(Integer selectedIndex) { - this.selectedIndex = selectedIndex; - } - - public Integer getSelectedIndex() { - return selectedIndex; - } - - public Integer getDefaultIndex() { - return defaultIndex; - } - - public List getLocales() { - return locales; - } - - public Locale getDefaultLocale() { - return locales.get(getDefaultIndex()); - } - - /** Returns null if argument is null. */ - public static List asLocaleList(Object locales) { - if (locales == null) - return null; - ArrayList availableLocales = new ArrayList(); - String[] codes = locales.toString().split(","); - for (int i = 0; i < codes.length; i++) { - String code = codes[i]; - // variant not supported - int indexUnd = code.indexOf("_"); - Locale locale; - if (indexUnd > 0) { - String language = code.substring(0, indexUnd); - String country = code.substring(indexUnd + 1); - locale = new Locale(language, country); - } else { - locale = new Locale(code); - } - availableLocales.add(locale); - } - return availableLocales; - } - - 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)); - } - } - -}