/* * Copyright (c) 2005-2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you 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.wso2.carbon.identity.user.store.configuration.deployer; import org.apache.axiom.om.OMAttribute; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.impl.builder.StAXOMBuilder; import org.apache.axiom.om.util.Base64; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.deployment.AbstractDeployer; import org.apache.axis2.deployment.DeploymentException; import org.apache.axis2.deployment.repository.util.DeploymentFileData; import org.apache.axis2.engine.AxisConfiguration; import org.apache.commons.io.FilenameUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.tools.ant.util.FileUtils; import org.wso2.carbon.base.api.ServerConfigurationService; import org.wso2.carbon.identity.core.util.IdentityIOStreamUtils; import org.wso2.carbon.identity.user.store.configuration.deployer.exception.UserStoreConfigurationDeployerException; import org.wso2.carbon.identity.user.store.configuration.deployer.internal.UserStoreConfigComponent; import org.wso2.carbon.identity.user.store.configuration.deployer.util.UserStoreConfigurationConstants; import org.wso2.carbon.identity.user.store.configuration.deployer.util.UserStoreUtil; import org.wso2.carbon.user.api.Property; import org.wso2.carbon.user.core.UserStoreException; import org.wso2.carbon.user.core.common.UserStoreDeploymentManager; import org.wso2.carbon.user.core.tracker.UserStoreManagerRegistry; import javax.crypto.Cipher; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Iterator; /** * This is to deploy a new User Store Management Configuration file dropped or created at repository/deployment/server/userstores * or repository/tenant/<>tenantId</>/userstores. Whenever a new file with .xml extension is added/deleted or a modification is done to * an existing file, deployer will automatically update the existing realm configuration org.wso2.carbon.identity.user.store.configuration * according to the new file. */ public class UserStoreConfigurationDeployer extends AbstractDeployer { private static Log log = LogFactory.getLog(UserStoreConfigurationDeployer.class); private AxisConfiguration axisConfig; /** * @param propElem Property Element * @return If the element text can be encrypted */ private static boolean isEligibleTobeEncrypted(OMElement propElem) { String secAlias = propElem.getAttributeValue(new QName(UserStoreConfigurationConstants.SECURE_VAULT_NS, UserStoreConfigurationConstants.SECRET_ALIAS)); if (secAlias == null) { String secretPropName = propElem.getAttributeValue(new QName(UserStoreConfigurationConstants.PROPERTY_ENCRYPT)); if (secretPropName != null && secretPropName.equalsIgnoreCase("true")) { String plainText = propElem.getText(); if (plainText != null) { return true; } } } return false; } /** * Get the list of properties from mandatoryProperties list * * @param userStoreClass class name of user store * @return ArrayList consisting of mandatory properties to be encrypted */ private static ArrayList<String> getEncryptPropertyList(String userStoreClass) { //First check for mandatory field with #encrypt Property[] mandatoryProperties = UserStoreManagerRegistry.getUserStoreProperties(userStoreClass). getMandatoryProperties(); ArrayList<String> propertyList = new ArrayList<String>(); for (Property property : mandatoryProperties) { if (property != null) { String propertyName = property.getName(); if (propertyName != null && property.getDescription().contains (UserStoreConfigurationConstants.ENCRYPT_TEXT)) { propertyList.add(propertyName); } } } return propertyList; } public void init(ConfigurationContext configurationContext) { log.info("User Store Configuration Deployer initiated."); this.axisConfig = configurationContext.getAxisConfiguration(); } /** * Trigger deploying of new org.wso2.carbon.identity.user.store.configuration file * * @param deploymentFileData information about the user store org.wso2.carbon.identity.user.store.configuration * @throws org.apache.axis2.deployment.DeploymentException for any errors */ public void deploy(DeploymentFileData deploymentFileData) throws DeploymentException { ServerConfigurationService config = UserStoreConfigComponent.getServerConfigurationService(); if (config != null) { String absolutePath = deploymentFileData.getAbsolutePath(); String ext = FilenameUtils.getExtension(absolutePath); if (UserStoreConfigurationConstants.ENC_EXTENSION.equalsIgnoreCase(ext)) { OutputStream outputStream = null; try { Cipher cipher = UserStoreUtil.getCipherOfSuperTenant(); OMElement secondaryStoreDocument = initializeOMElement(absolutePath); updateSecondaryUserStore(secondaryStoreDocument, cipher); int index = absolutePath.lastIndexOf("."); if (index != 1) { String encFileName = absolutePath.substring(0, index + 1) + UserStoreConfigurationConstants.XML_EXTENSION; outputStream = new FileOutputStream(encFileName); secondaryStoreDocument.serialize(outputStream); File file = new File(absolutePath); if (file.exists()) { FileUtils.delete(file); } } return; } catch (UserStoreConfigurationDeployerException e) { String errMsg = "Secondary user store processing failed while processing " + absolutePath; throw new DeploymentException(errMsg, e); } catch (FileNotFoundException e) { String errMsg = "Secondary user store File path " + absolutePath + " is invalid"; throw new DeploymentException(errMsg, e); } catch (XMLStreamException e) { String errMsg = "Unexpected xml processing errors while trying to update file " + absolutePath; throw new DeploymentException(errMsg, e); } catch (UserStoreException e) { String errMsg = "Error while initializing key store"; throw new DeploymentException(errMsg, e); } finally { IdentityIOStreamUtils.closeOutputStream(outputStream); } } UserStoreDeploymentManager userStoreDeploymentManager = new UserStoreDeploymentManager(); userStoreDeploymentManager.deploy(deploymentFileData.getAbsolutePath()); } } /** * Trigger un-deploying of a deployed file. Removes the deleted user store from chain * * @param fileName: domain name --> file name * @throws org.apache.axis2.deployment.DeploymentException for any errors */ public void undeploy(String fileName) throws DeploymentException { if (fileName != null) { String ext = FilenameUtils.getExtension(fileName); if (!UserStoreConfigurationConstants.ENC_EXTENSION.equalsIgnoreCase(ext)) { UserStoreDeploymentManager userStoreDeploymentManager = new UserStoreDeploymentManager(); userStoreDeploymentManager.undeploy(fileName); } } } public void setDirectory(String s) { } public void setExtension(String s) { } /** * Initializes the XML Document object * * @param absoluteFilePath xml path * @return OMElement object will be returned */ private OMElement initializeOMElement(String absoluteFilePath) throws UserStoreConfigurationDeployerException { StAXOMBuilder builder; InputStream inStream; try { inStream = new FileInputStream(absoluteFilePath); builder = new StAXOMBuilder(inStream); return builder.getDocumentElement(); } catch (FileNotFoundException e) { String errMsg = " Secondary storage file Not found in given repo " + absoluteFilePath; throw new UserStoreConfigurationDeployerException(errMsg, e); } catch (XMLStreamException e) { String errMsg = " Secondary storage file reading for repo = " + absoluteFilePath + " failed "; throw new UserStoreConfigurationDeployerException(errMsg, e); } } /** * Encrypts the secondary user store configuration * * @param secondaryStoreDocument OMElement of respective file path * @param cipher Cipher object read for super-tenant's key store * @throws UserStoreConfigurationDeployerException If update operation failed */ private void updateSecondaryUserStore(OMElement secondaryStoreDocument, Cipher cipher) throws UserStoreConfigurationDeployerException { String className = secondaryStoreDocument.getAttributeValue(new QName(UserStoreConfigurationConstants.PROPERTY_CLASS)); ArrayList<String> encryptList = getEncryptPropertyList(className); Iterator<?> ite = secondaryStoreDocument.getChildrenWithName(new QName(UserStoreConfigurationConstants.PROPERTY)); while (ite.hasNext()) { OMElement propElem = (OMElement) ite.next(); if (propElem != null && (propElem.getText() != null)) { String propertyName = propElem.getAttributeValue(new QName(UserStoreConfigurationConstants.PROPERTY_NAME)); OMAttribute encryptedAttr = propElem.getAttribute(new QName(UserStoreConfigurationConstants .PROPERTY_ENCRYPTED)); if (encryptedAttr == null) { boolean encrypt = encryptList.contains(propertyName) || isEligibleTobeEncrypted(propElem); if (encrypt) { OMAttribute encryptAttr = propElem.getAttribute(new QName(UserStoreConfigurationConstants.PROPERTY_ENCRYPT)); if (encryptAttr != null) { propElem.removeAttribute(encryptAttr); } try { String cipherText = Base64.encode(cipher.doFinal((propElem.getText().getBytes()))); propElem.setText(cipherText); propElem.addAttribute(UserStoreConfigurationConstants.PROPERTY_ENCRYPTED, "true", null); } catch (GeneralSecurityException e) { String errMsg = "Encryption in secondary user store failed"; throw new UserStoreConfigurationDeployerException(errMsg, e); } } } } } } }