/*
* Copyright (c) 2014, 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.application.authentication.endpoint.util;
import org.apache.axiom.om.util.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.securevault.SecretResolver;
import org.wso2.securevault.SecretResolverFactory;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public class TenantDataManager {
private static final Log log = LogFactory.getLog(TenantDataManager.class);
private static final String SECRET_ALIAS = "secretAlias:";
private static Properties prop;
private static String carbonLogin = "";
private static String serviceURL;
private static String usernameHeaderName = "";
private static List<String> tenantDomainList = new ArrayList<String>();
private static boolean initialized = false;
private static boolean initAttempted = false;
private static boolean identityAvailable;
private TenantDataManager() {
}
/**
* Initialize Tenant data manager
*/
public static synchronized void init() {
InputStream inputStream = null;
initAttempted = true;
try {
if (!initialized) {
prop = new Properties();
String configFilePath = buildFilePath(Constants.TenantConstants.CONFIG_RELATIVE_PATH);
File configFile = new File(configFilePath);
if (configFile.exists()) {
identityAvailable = true;
log.info(Constants.TenantConstants.CONFIG_FILE_NAME + " file loaded from " + Constants
.TenantConstants.CONFIG_RELATIVE_PATH);
inputStream = new FileInputStream(configFile);
prop.load(inputStream);
if (isSecuredPropertyAvailable(prop)) {
// Resolve encrypted properties with secure vault
resolveSecrets(prop);
}
} else {
identityAvailable = false;
log.info(Constants.TenantConstants.CONFIG_FILE_NAME + " file loaded from authentication endpoint " +
"webapp");
inputStream = TenantDataManager.class.getClassLoader().getResourceAsStream(Constants
.TenantConstants.CONFIG_FILE_NAME);
prop.load(inputStream);
}
usernameHeaderName = getPropertyValue(Constants.TenantConstants.USERNAME_HEADER);
carbonLogin = getPropertyValue(Constants.TenantConstants.USERNAME);
// Base64 encoded username
carbonLogin = Base64.encode(carbonLogin.getBytes(Constants.TenantConstants.CHARACTER_ENCODING));
String clientKeyStorePath = buildFilePath(getPropertyValue(Constants.TenantConstants.CLIENT_KEY_STORE));
String clientTrustStorePath = buildFilePath(getPropertyValue(Constants.TenantConstants
.CLIENT_TRUST_STORE));
if (StringUtils.isNotEmpty(getPropertyValue(Constants.TenantConstants.KEY_MANAGER_TYPE))) {
TenantMgtAdminServiceClient.setKeyManagerType(getPropertyValue(Constants.TenantConstants
.KEY_MANAGER_TYPE));
}
if (StringUtils.isNotEmpty(getPropertyValue(Constants.TenantConstants.TRUST_MANAGER_TYPE))) {
TenantMgtAdminServiceClient.setTrustManagerType(getPropertyValue(Constants.TenantConstants
.TRUST_MANAGER_TYPE));
}
TenantMgtAdminServiceClient
.loadKeyStore(clientKeyStorePath, getPropertyValue(Constants.TenantConstants
.CLIENT_KEY_STORE_PASSWORD));
TenantMgtAdminServiceClient
.loadTrustStore(clientTrustStorePath, getPropertyValue(Constants.TenantConstants
.CLIENT_TRUST_STORE_PASSWORD));
TenantMgtAdminServiceClient.initMutualSSLConnection(Boolean.parseBoolean(
getPropertyValue(Constants.TenantConstants.HOSTNAME_VERIFICATION_ENABLED)));
// Build the service URL of tenant management admin service
StringBuilder builder = new StringBuilder();
serviceURL = builder.append(getPropertyValue(Constants.SERVICES_URL)).append(Constants.TenantConstants
.TENANT_MGT_ADMIN_SERVICE_URL).toString();
initialized = true;
}
} catch (AuthenticationException | IOException e) {
log.error("Initialization failed : ", e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error("Failed to close the FileInputStream, file : " + Constants.TenantConstants
.CONFIG_FILE_NAME, e);
}
}
}
}
/**
* Build the absolute path of a give file path
*
* @param path File path
* @return Absolute file path
* @throws java.io.IOException
*/
private static String buildFilePath(String path) throws IOException {
if (StringUtils.isNotEmpty(path) && path.startsWith(Constants.TenantConstants.RELATIVE_PATH_START_CHAR)) {
// Relative file path is given
File currentDirectory = new File(new File(Constants.TenantConstants.RELATIVE_PATH_START_CHAR)
.getAbsolutePath());
path = currentDirectory.getCanonicalPath() + File.separator + path;
}
if (log.isDebugEnabled()) {
log.debug("File path for KeyStore/TrustStore : " + path);
}
return path;
}
/**
* Get property value by key
*
* @param key Property key
* @return Property value
*/
protected static String getPropertyValue(String key) {
if (key == Constants.SERVICES_URL && identityAvailable) {
return IdentityUtil.getServerURL(prop.getProperty(key),true, true);
} else {
return prop.getProperty(key);
}
}
/**
* Call service and return response
*
* @param url Service URL
* @return Response from service
*/
private static String getServiceResponse(String url) {
String serviceResponse = null;
Map<String, String> headerParams = new HashMap<String, String>();
// Set the username in HTTP header for mutual ssl authentication
headerParams.put(usernameHeaderName, carbonLogin);
serviceResponse = TenantMgtAdminServiceClient.sendPostRequest(url, null, headerParams);
return serviceResponse;
}
/**
* Get active tenants list
*
* @return List of tenant domains
*/
public static List<String> getAllActiveTenantDomains() {
if (initialized && tenantDomainList.isEmpty()) {
refreshActiveTenantDomainsList();
}
return tenantDomainList;
}
/**
* Reset the tenant domains list
*
*/
public static void resetTenantDataList() {
if (!initialized) {
if (log.isDebugEnabled()) {
log.debug("Tenant domains list not set as TenantDataManager is not initialized.");
}
return;
}
synchronized (tenantDomainList) {
tenantDomainList.clear();
refreshActiveTenantDomainsList();
}
}
/**
* Retrieve latest active tenant domains list
*/
private static void refreshActiveTenantDomainsList() {
try {
String xmlString = getServiceResponse(serviceURL);
if (StringUtils.isNotEmpty(xmlString)) {
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
InputSource inputSource = new InputSource(new StringReader(xmlString));
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(inputSource);
String xPathExpression = "/*[local-name() = '" + Constants.TenantConstants.RETRIEVE_TENANTS_RESPONSE
+ "']/*[local-name() = '" +
Constants.TenantConstants.RETURN + "']";
XPathExpression expr = xpath.compile(xPathExpression);
NodeList nodeList = null;
nodeList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
// Reset existing tenant domains list
tenantDomainList.clear();
// For each loop is not supported for NodeList
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node != null && node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
NodeList tenantData = element.getChildNodes();
boolean activeChecked = false;
boolean domainChecked = false;
boolean isActive = false;
String tenantDomain = null;
// For each loop is not supported for NodeList
for (int j = 0; j < tenantData.getLength(); j++) {
Node dataItem = tenantData.item(j);
String localName = dataItem.getLocalName();
if (Constants.TenantConstants.ACTIVE.equals(localName)) {
// Current element has domain status active or inactive
activeChecked = true;
if (Boolean.parseBoolean(dataItem.getTextContent())) {
isActive = true;
}
}
if (Constants.TenantConstants.TENANT_DOMAIN.equals(localName)) {
// Current element has domain name of the tenant
domainChecked = true;
tenantDomain = dataItem.getTextContent();
}
if (activeChecked && domainChecked) {
if (isActive) {
tenantDomainList.add(tenantDomain);
if (log.isDebugEnabled()) {
log.debug(tenantDomain + " is active and added to the dropdown list");
}
} else {
if (log.isDebugEnabled()) {
log.debug(tenantDomain + " is inactive and not added to the dropdown list");
}
}
break;
}
}
}
}
// Sort the list of tenant domains alphabetically
Collections.sort(tenantDomainList);
}
} catch (Exception e) {
// Catching the general exception as if no tenants are available it should stop processing
log.error("Retrieving list of active tenant domains failed. Ignore this if there are no tenants : ", e);
}
}
/**
* Get status of the tenant list dropdown enabled or disabled
*
* @return Tenant list enabled or disabled status
*/
public static boolean isTenantListEnabled() {
if (!initAttempted && !initialized) {
init();
}
return Boolean.parseBoolean(getPropertyValue(Constants.TenantConstants.TENANT_LIST_ENABLED));
}
/**
* There can be sensitive information like passwords in configuration file. If they are encrypted using secure
* vault, this method will resolve them and replace with original values.
*/
private static void resolveSecrets(Properties properties) {
SecretResolver secretResolver = SecretResolverFactory.create(properties);
Enumeration propertyNames = properties.propertyNames();
if (secretResolver != null && secretResolver.isInitialized()) {
// Iterate through whole config file and find encrypted properties and resolve them
while (propertyNames.hasMoreElements()) {
String key = (String) propertyNames.nextElement();
if (secretResolver.isTokenProtected(key)) {
if (log.isDebugEnabled()) {
log.debug("Resolving and replacing secret for " + key);
}
// Resolving the secret password.
String value = secretResolver.resolve(key);
// Replaces the original encrypted property with resolved property
properties.put(key, value);
} else {
if (log.isDebugEnabled()) {
log.debug("No encryption done for value with key :" + key);
}
}
}
} else {
log.warn("Secret Resolver is not present. Will not resolve encryptions in " + Constants.TenantConstants
.CONFIG_RELATIVE_PATH + " file");
}
}
/**
* Get status of the availability of secured (with secure vault) properties
*
* @return availability of secured properties
*/
private static boolean isSecuredPropertyAvailable(Properties properties) {
Enumeration propertyNames = properties.propertyNames();
while (propertyNames.hasMoreElements()) {
String key = (String) propertyNames.nextElement();
if (StringUtils.startsWith(properties.getProperty(key), SECRET_ALIAS)) {
return true;
}
}
return false;
}
}