/* * Copyright (C) 2003-2017 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.management.service.impl; import org.chromattic.api.Chromattic; import org.chromattic.api.ChromatticBuilder; import org.chromattic.api.ChromatticSession; import org.chromattic.api.query.QueryResult; import org.exoplatform.commons.chromattic.ChromatticManager; import org.exoplatform.commons.utils.PropertyManager; import org.exoplatform.management.common.AbstractOperationHandler; import org.exoplatform.management.service.api.ChromatticService; import org.exoplatform.management.service.api.TargetServer; import org.exoplatform.management.service.api.model.TargetServerChromattic; import org.exoplatform.management.service.integration.CurrentRepositoryLifeCycle; import org.exoplatform.services.jcr.RepositoryService; import org.exoplatform.services.jcr.core.ExtendedNode; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.web.security.codec.AbstractCodec; import org.exoplatform.web.security.codec.AbstractCodecBuilder; import org.exoplatform.web.security.security.TokenServiceInitializationException; import org.gatein.common.io.IOTools; import org.picocontainer.Startable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.KeyStore; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.jcr.RepositoryException; import javax.jcr.Session; /** * The Class ChromatticServiceImpl. * * @author Thomas Delhoménie */ public class ChromatticServiceImpl implements ChromatticService, Startable { /** The Constant EXO_PRIVILEGEABLE_MIXIN. */ private static final String EXO_PRIVILEGEABLE_MIXIN = "exo:privilegeable"; /** The Constant LOG. */ private static final Log LOG = ExoLogger.getLogger(ChromatticServiceImpl.class); /** The default permissions. */ private static Map<String, String[]> DEFAULT_PERMISSIONS = new HashMap<String, String[]>(); static { try { DEFAULT_PERMISSIONS.put("*:/platform/administrators", new String[] { "read", "set_property", "add_node", "remove" }); DEFAULT_PERMISSIONS.put("*:/platform/web-contributors", new String[] { "read", "set_property", "add_node", "remove" }); } catch (Exception e) { LOG.error(e); DEFAULT_PERMISSIONS = null; } } /** The Constant STAGING_SERVERS_ROOT_PATH. */ public final static String STAGING_SERVERS_ROOT_PATH = "/exo:applications/staging/servers"; /** The workspace name. */ private String workspaceName = null; /** The repository service. */ private RepositoryService repositoryService; /** The chromattic. */ Chromattic chromattic; /** The codec. */ private AbstractCodec codec; /** * Instantiates a new chromattic service impl. * * @param chromatticManager the chromattic manager * @param repositoryService the repository service */ public ChromatticServiceImpl(ChromatticManager chromatticManager, RepositoryService repositoryService) { // Nothing to do with chromatticManager, it's only to ensure that // ChromatticManager is started before this service this.repositoryService = repositoryService; } /** * {@inheritDoc} */ @Override public void start() { try { workspaceName = repositoryService.getDefaultRepository().getConfiguration().getDefaultWorkspaceName(); } catch (Exception e) { workspaceName = "collaboration"; } // Init Chromattic ChromatticBuilder builder = ChromatticBuilder.create(); builder.add(TargetServerChromattic.class); builder.setOptionValue(ChromatticBuilder.SESSION_LIFECYCLE_CLASSNAME, CurrentRepositoryLifeCycle.class.getName()); builder.setOptionValue(ChromatticBuilder.CREATE_ROOT_NODE, true); builder.setOptionValue(ChromatticBuilder.ROOT_NODE_PATH, STAGING_SERVERS_ROOT_PATH); chromattic = builder.build(); setPermissions(STAGING_SERVERS_ROOT_PATH, DEFAULT_PERMISSIONS); try { initCodec(); } catch (Exception e) { this.codec = null; } } /** * {@inheritDoc} */ @Override public List<TargetServer> getSynchonizationServers() { List<TargetServer> targetServers = new ArrayList<TargetServer>(); ChromatticSession session = null; try { session = openSession(); QueryResult<TargetServerChromattic> servers = session.createQueryBuilder(TargetServerChromattic.class).where("jcr:path like '" + STAGING_SERVERS_ROOT_PATH + "/%'").get().objects(); while (servers.hasNext()) { TargetServerChromattic server = servers.next(); String password = decodePassword(server.getPassword()); targetServers.add(new TargetServer(server.getId(), server.getName(), server.getHost(), server.getPort(), server.getUsername(), password, server.isSsl())); } session.save(); } finally { if (session != null) { session.close(); } } return targetServers; } /** * {@inheritDoc} */ @Override public TargetServer getServerByName(String name) { TargetServer targetServer = null; ChromatticSession session = null; try { session = openSession(); if (!session.getJCRSession().itemExists(STAGING_SERVERS_ROOT_PATH)) { return null; } TargetServerChromattic server = null; QueryResult<TargetServerChromattic> servers = session.createQueryBuilder(TargetServerChromattic.class).where("jcr:path = '" + STAGING_SERVERS_ROOT_PATH + "/" + name + "'").get().objects(); if (servers.size() == 0) { return null; } else if (servers.size() > 1) { throw new IllegalStateException("found more than one server with name: " + name); } else if (servers.hasNext()) { server = servers.next(); } if (server != null) { String password = decodePassword(server.getPassword()); targetServer = new TargetServer(server.getId(), server.getName(), server.getHost(), server.getPort(), server.getUsername(), password, server.isSsl()); } } catch (RepositoryException e) { throw new IllegalStateException("Error while attempting to access JCR repository", e); } finally { if (session != null) { session.close(); } } return targetServer; } /** * {@inheritDoc} */ @Override public void addSynchonizationServer(TargetServer targetServer) { ChromatticSession session = null; try { session = openSession(); TargetServerChromattic chromatticObject = null; try { chromatticObject = session.findByPath(TargetServerChromattic.class, STAGING_SERVERS_ROOT_PATH + "/" + targetServer.getName()); if (chromatticObject != null) { LOG.warn("Attempt to add server with same name"); return; } } catch (Exception e) { // Nothing to do } TargetServerChromattic server = session.insert(TargetServerChromattic.class, targetServer.getName()); server.setHost(targetServer.getHost()); server.setPort(targetServer.getPort()); server.setUsername(targetServer.getUsername()); String password = encodePassword(targetServer.getPassword()); server.setPassword(password); server.setSsl(targetServer.isSsl()); session.save(); String jcrPath = session.getPath(server); setPermissions(jcrPath, DEFAULT_PERMISSIONS); } catch (Exception e) { LOG.warn("error while adding server details" + targetServer.getName(), e); } finally { if (session != null) { session.close(); } } } /** * {@inheritDoc} */ @Override public void removeSynchonizationServer(TargetServer targetServer) { ChromatticSession session = null; try { session = openSession(); TargetServerChromattic server = session.findById(TargetServerChromattic.class, targetServer.getId()); if (server != null) { session.remove(server); session.save(); } } finally { if (session != null) { session.close(); } } } /** * Open session. * * @return the chromattic session */ private ChromatticSession openSession() { return chromattic.openSession(workspaceName); } /** * Sets the permissions. * * @param jcrPath the jcr path * @param permissions the permissions */ public void setPermissions(String jcrPath, Map<String, String[]> permissions) { Session session = null; try { session = AbstractOperationHandler.getSession(repositoryService, workspaceName); if (!session.itemExists(jcrPath)) { return; } ExtendedNode extendedNode = (ExtendedNode) session.getItem(jcrPath); if (extendedNode.canAddMixin(EXO_PRIVILEGEABLE_MIXIN)) { extendedNode.addMixin(EXO_PRIVILEGEABLE_MIXIN); extendedNode.setPermissions(permissions); session.save(); } } catch (Exception e) { LOG.error(e); } finally { if (session != null) { session.logout(); } } } /** * {@inheritDoc} */ @Override public void stop() {} /** * Inits the codec. * * @throws Exception the exception */ private void initCodec() throws Exception { String builderType = PropertyManager.getProperty("gatein.codec.builderclass"); Map<String, String> config = new HashMap<String, String>(); if (builderType != null) { // If there is config for codec in configuration.properties, we read the // config parameters from config file // referenced in configuration.properties String configFile = PropertyManager.getProperty("gatein.codec.config"); InputStream in = null; try { File f = new File(configFile); in = new FileInputStream(f); Properties properties = new Properties(); properties.load(in); for (Map.Entry<?, ?> entry : properties.entrySet()) { config.put((String) entry.getKey(), (String) entry.getValue()); } config.put("gatein.codec.config.basedir", f.getParentFile().getAbsolutePath()); } catch (IOException e) { throw new TokenServiceInitializationException("Failed to read the config parameters from file '" + configFile + "'.", e); } finally { IOTools.safeClose(in); } } else { // If there is no config for codec in configuration.properties, we // generate key if it does not exist and setup the // default config builderType = "org.exoplatform.web.security.codec.JCASymmetricCodecBuilder"; String gtnConfDir = PropertyManager.getProperty("gatein.conf.dir"); if (gtnConfDir == null || gtnConfDir.length() == 0) { throw new TokenServiceInitializationException("'gatein.conf.dir' property must be set."); } File f = new File(gtnConfDir + "/codec/codeckey.txt"); if (!f.exists()) { File codecDir = f.getParentFile(); if (!codecDir.exists()) { codecDir.mkdir(); } OutputStream out = null; try { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(128); SecretKey key = keyGen.generateKey(); KeyStore store = KeyStore.getInstance("JCEKS"); store.load(null, "gtnStorePass".toCharArray()); store.setEntry("gtnKey", new KeyStore.SecretKeyEntry(key), new KeyStore.PasswordProtection("gtnKeyPass".toCharArray())); out = new FileOutputStream(f); store.store(out, "gtnStorePass".toCharArray()); } catch (Exception e) { throw new TokenServiceInitializationException(e); } finally { IOTools.safeClose(out); } } config.put("gatein.codec.jca.symmetric.keyalg", "AES"); config.put("gatein.codec.jca.symmetric.keystore", "codeckey.txt"); config.put("gatein.codec.jca.symmetric.storetype", "JCEKS"); config.put("gatein.codec.jca.symmetric.alias", "gtnKey"); config.put("gatein.codec.jca.symmetric.keypass", "gtnKeyPass"); config.put("gatein.codec.jca.symmetric.storepass", "gtnStorePass"); config.put("gatein.codec.config.basedir", f.getParentFile().getAbsolutePath()); } try { this.codec = Class.forName(builderType).asSubclass(AbstractCodecBuilder.class).newInstance().build(config); LOG.info("Initialized CookieTokenService.codec using builder " + builderType); } catch (Exception e) { throw new TokenServiceInitializationException("Could not initialize CookieTokenService.codec.", e); } } /** * Decode password. * * @param password the password * @return the string */ private String decodePassword(String password) { if (codec != null) { try { password = codec.decode(password); } catch (Exception e) { LOG.warn("Error while decoding password, it will be used in plain text", e); } } return password; } /** * Encode password. * * @param password the password * @return the string */ private String encodePassword(String password) { if (codec != null) { try { password = codec.encode(password); } catch (Exception e) { LOG.warn("Error while encoding password, it will be used in plain text", e); } } return password; } }