/*
* Copyright (c) 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.security.deployment;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.om.util.UUIDGenerator;
import org.apache.axis2.AxisFault;
import org.apache.axis2.description.AxisBinding;
import org.apache.axis2.description.AxisEndpoint;
import org.apache.axis2.description.AxisModule;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.AxisServiceGroup;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.PolicySubject;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.engine.AxisEvent;
import org.apache.axis2.engine.AxisObserver;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.neethi.Policy;
import org.apache.neethi.PolicyComponent;
import org.apache.neethi.PolicyEngine;
import org.apache.neethi.PolicyReference;
import org.apache.neethi.builders.xml.XmlPrimtiveAssertion;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.ComponentContext;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.CarbonException;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.context.RegistryType;
import org.wso2.carbon.core.RegistryResources;
import org.wso2.carbon.registry.core.Collection;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.ResourceImpl;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.jdbc.utils.Transaction;
import org.wso2.carbon.registry.core.service.RegistryService;
import org.wso2.carbon.registry.core.session.UserRegistry;
import org.wso2.carbon.security.SecurityConfigException;
import org.wso2.carbon.security.SecurityConfigParams;
import org.wso2.carbon.security.SecurityConstants;
import org.wso2.carbon.security.SecurityScenario;
import org.wso2.carbon.security.SecurityScenarioDatabase;
import org.wso2.carbon.security.SecurityServiceHolder;
import org.wso2.carbon.security.util.RahasUtil;
import org.wso2.carbon.security.util.SecurityConfigParamBuilder;
import org.wso2.carbon.security.util.ServerCrypto;
import org.wso2.carbon.security.util.ServicePasswordCallbackHandler;
import org.wso2.carbon.security.util.XmlConfiguration;
import org.wso2.carbon.user.api.AuthorizationManager;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.core.UserCoreConstants;
import org.wso2.carbon.user.core.UserRealm;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.utils.Axis2ConfigurationContextObserver;
import org.wso2.carbon.utils.PreAxisConfigurationPopulationObserver;
import org.wso2.carbon.utils.ServerConstants;
import org.wso2.carbon.utils.ServerException;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
/**
* This is a deployment interceptor which handles service specific security configurations on
* service deployment events. It is also published as an OSGi service, so that Carbon core can
* add this to the AxisConfiguration.
* <p/>
* NOTE: This is a special type of AxisObserver, which can be used only within an OSGi framework
* hence should not be added to the axis2.xml directly. If done so, it will throw NPEs, since
* the registry & userRealm references are set through the OSGi decalative service framework.
*
* @scr.component name="org.wso2.carbon.security.deployment.SecurityDeploymentInterceptor"
* immediate="true"
* @scr.reference name="registry.service"
* interface="org.wso2.carbon.registry.core.service.RegistryService"
* cardinality="1..1" policy="dynamic" bind="setRegistryService"
* unbind="unsetRegistryService"
* @scr.reference name="user.realmservice.default" interface="org.wso2.carbon.user.core.service.RealmService"
* cardinality="1..1" policy="dynamic" bind="setRealmService" unbind="unsetRealmService"
*/
public class SecurityDeploymentInterceptor implements AxisObserver {
private static final Log log = LogFactory.getLog(SecurityDeploymentInterceptor.class);
private static final String NO_POLICY_ID = "NoPolicy";
private static final String APPLY_POLICY_TO_BINDINGS = "applyPolicyToBindings";
protected void activate(ComponentContext ctxt) {
BundleContext bundleCtx = ctxt.getBundleContext();
try {
PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
carbonContext.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);
carbonContext.setTenantId(MultitenantConstants.SUPER_TENANT_ID);
loadSecurityScenarios(SecurityServiceHolder.getRegistryService().getConfigSystemRegistry(),
bundleCtx);
} catch (Exception e) {
String msg = "Cannot load security scenarios";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
try {
addKeystores();
} catch (Exception e) {
String msg = "Cannot add keystores";
log.error(msg, e);
throw new RuntimeException(msg, e);
}
// Publish the OSGi service
Dictionary props = new Hashtable();
props.put(CarbonConstants.AXIS2_CONFIG_SERVICE, AxisObserver.class.getName());
bundleCtx.registerService(AxisObserver.class.getName(), this, props);
PreAxisConfigurationPopulationObserver preAxisConfigObserver =
new PreAxisConfigurationPopulationObserver() {
@Override
public void createdAxisConfiguration(AxisConfiguration axisConfiguration) {
init(axisConfiguration);
axisConfiguration.addObservers(SecurityDeploymentInterceptor.this);
}
};
bundleCtx.registerService(PreAxisConfigurationPopulationObserver.class.getName(),
preAxisConfigObserver, null);
// Publish an OSGi service to listen tenant configuration context creation events
Dictionary properties = new Hashtable();
properties.put(CarbonConstants.AXIS2_CONFIG_SERVICE,
Axis2ConfigurationContextObserver.class.getName());
bundleCtx.registerService(Axis2ConfigurationContextObserver.class.getName(),
new SecurityDeploymentListener(), properties);
}
@Override
public void init(AxisConfiguration axisConfig) {
// Do Nothing
}
@Override
public void moduleUpdate(AxisEvent event, AxisModule module) {
// This method will not be used
}
@Override
public void serviceGroupUpdate(AxisEvent event, AxisServiceGroup serviceGroup) {
// This method will not be used
}
@Override
public void serviceUpdate(AxisEvent axisEvent, AxisService axisService) {
if (axisEvent.getEventType() == AxisEvent.SERVICE_DEPLOY) {
Policy policy = null;
if (axisEvent.getEventType() == AxisEvent.SERVICE_DEPLOY
&& ServerConstants.STS_NAME.equals(axisService.getName())) {
try {
applyPolicyToSTS(axisService);
} catch (SecurityConfigException e) {
log.error("Error while applying policy to STS Service", e);
} catch (AxisFault axisFault) {
log.error("Error while applying policy to STS Service", axisFault);
}
}
try {
policy = applyPolicyToBindings(axisService);
if (policy != null) {
processPolicy(axisService, policy.getId(), policy);
}
} catch (Exception e) {
log.error("Error while adding policies to bindings", e);
}
try {
if (axisService.getPolicySubject() != null && axisService.getPolicySubject()
.getAttachedPolicyComponents() != null) {
if (log.isDebugEnabled()) {
log.debug("Policies found on axis service");
}
Iterator iterator;
iterator = axisService.getPolicySubject().getAttachedPolicyComponents().iterator();
String policyId = null;
while (iterator.hasNext()) {
PolicyComponent currentPolicyComponent = (PolicyComponent) iterator.next();
if (currentPolicyComponent instanceof Policy) {
policyId = ((Policy) currentPolicyComponent).getId();
} else if (currentPolicyComponent instanceof PolicyReference) { //TODO: check this scenario
policyId = ((PolicyReference) currentPolicyComponent).getURI().substring(1);
}
processPolicy(axisService, policyId, currentPolicyComponent);
}
} else {
return;
}
} catch (Exception e) {
String msg = "Cannot handle service DEPLOY event for service: " +
axisService.getName();
log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
}
private void processPolicy (AxisService axisService, String policyId,
PolicyComponent currentPolicyComponent) throws UserStoreException,
AxisFault {
// Do not apply anything if no policy
if(StringUtils.isNotEmpty(policyId) && NO_POLICY_ID.equalsIgnoreCase(policyId)){
if(axisService != null){
UserRealm userRealm = (UserRealm)PrivilegedCarbonContext.getThreadLocalCarbonContext()
.getUserRealm();
String serviceGroupId = axisService.getAxisServiceGroup().getServiceGroupName();
String serviceName = axisService.getName();
removeAuthorization(userRealm,serviceGroupId,serviceName);
}
AxisModule module = axisService.getAxisConfiguration().getModule(SecurityConstants
.RAMPART_MODULE_NAME);
// disengage at axis2
axisService.disengageModule(module);
return;
}
if (policyId != null && isSecPolicy(policyId)) {
if (log.isDebugEnabled()) {
log.debug("Policy " + policyId + " is identified as a security " +
"policy and trying to apply security parameters");
}
SecurityScenario scenario = SecurityScenarioDatabase.getByWsuId(policyId);
if (scenario == null) {
// if there is no security scenario id, put default id
if (log.isDebugEnabled()) {
log.debug("Policy " + policyId + " does not belongs to a" +
" pre-defined security scenario. " +
"So treating as a custom policy");
}
SecurityScenario securityScenario = new SecurityScenario();
securityScenario.setScenarioId(
SecurityConstants.CUSTOM_SECURITY_SCENARIO);
securityScenario.setWsuId(policyId);
securityScenario.setGeneralPolicy(false);
securityScenario.setSummary(
SecurityConstants.CUSTOM_SECURITY_SCENARIO_SUMMARY);
SecurityScenarioDatabase.put(policyId, securityScenario);
scenario = securityScenario;
}
applySecurityParameters(axisService, scenario,
(Policy) currentPolicyComponent);
}
}
private void loadSecurityScenarios(Registry registry,
BundleContext bundleContext) throws CarbonException, IOException, RegistryException {
// TODO: Load into all tenant DBs
// Load security scenarios
URL resource = bundleContext.getBundle().getResource("/scenarios/scenario-config.xml");
XmlConfiguration xmlConfiguration = new XmlConfiguration(resource.openStream(),
SecurityConstants.SECURITY_NAMESPACE);
OMElement[] elements = xmlConfiguration.getElements("//ns:Scenario");
try {
boolean transactionStarted = Transaction.isStarted();
if (!transactionStarted) {
registry.beginTransaction();
}
for (OMElement scenarioEle : elements) {
SecurityScenario scenario = new SecurityScenario();
String scenarioId = scenarioEle.getAttribute(SecurityConstants.ID_QN)
.getAttributeValue();
scenario.setScenarioId(scenarioId);
scenario.setSummary(scenarioEle.getFirstChildWithName(SecurityConstants.SUMMARY_QN)
.getText());
scenario.setDescription(scenarioEle.getFirstChildWithName(
SecurityConstants.DESCRIPTION_QN).getText());
scenario.setCategory(scenarioEle.getFirstChildWithName(SecurityConstants.CATEGORY_QN)
.getText());
scenario.setWsuId(scenarioEle.getFirstChildWithName(SecurityConstants.WSUID_QN)
.getText());
scenario.setType(scenarioEle.getFirstChildWithName(SecurityConstants.TYPE_QN).getText());
String resourceUri = SecurityConstants.SECURITY_POLICY + "/" + scenarioId;
for (Iterator modules = scenarioEle.getFirstChildWithName(SecurityConstants.MODULES_QN)
.getChildElements(); modules.hasNext(); ) {
String module = ((OMElement) modules.next()).getText();
scenario.addModule(module);
}
// Save it in the DB
SecurityScenarioDatabase.put(scenarioId, scenario);
// Store the scenario in the Registry
if (!scenarioId.equals(SecurityConstants.SCENARIO_DISABLE_SECURITY) &&
!scenarioId.equals(SecurityConstants.POLICY_FROM_REG_SCENARIO)) {
Resource scenarioResource = new ResourceImpl();
scenarioResource.
setContentStream(bundleContext.getBundle().
getResource("scenarios/" + scenarioId + "-policy.xml").openStream());
scenarioResource.setMediaType("application/policy+xml");
if (!registry.resourceExists(resourceUri)) {
registry.put(resourceUri, scenarioResource);
}
// Cache the resource in-memory in order to add it to the newly created tenants
SecurityServiceHolder.addPolicyResource(resourceUri, scenarioResource);
}
}
if (!transactionStarted) {
registry.commitTransaction();
}
} catch (Exception e) {
registry.rollbackTransaction();
throw e;
}
}
private void addKeystores() throws RegistryException {
Registry registry = SecurityServiceHolder.getRegistryService().getGovernanceSystemRegistry();
try {
boolean transactionStarted = Transaction.isStarted();
if (!transactionStarted) {
registry.beginTransaction();
}
if (!registry.resourceExists(SecurityConstants.KEY_STORES)) {
Collection kstores = registry.newCollection();
registry.put(SecurityConstants.KEY_STORES, kstores);
Resource primResource = registry.newResource();
if (!registry.resourceExists(RegistryResources.SecurityManagement.PRIMARY_KEYSTORE_PHANTOM_RESOURCE)) {
registry.put(RegistryResources.SecurityManagement.PRIMARY_KEYSTORE_PHANTOM_RESOURCE,
primResource);
}
}
if (!transactionStarted) {
registry.commitTransaction();
}
} catch (Exception e) {
registry.rollbackTransaction();
throw e;
}
}
private void applySecurityParameters(AxisService service, SecurityScenario secScenario,
Policy policy) {
try {
UserRealm userRealm = (UserRealm) PrivilegedCarbonContext.getThreadLocalCarbonContext()
.getUserRealm();
UserRegistry govRegistry = (UserRegistry) PrivilegedCarbonContext
.getThreadLocalCarbonContext().getRegistry(RegistryType.SYSTEM_GOVERNANCE);
String serviceGroupId = service.getAxisServiceGroup().getServiceGroupName();
String serviceName = service.getName();
SecurityConfigParams configParams =
SecurityConfigParamBuilder.getSecurityParams(getSecurityConfig(policy));
// Set Trust (Rahas) Parameters
if (secScenario.getModules().contains(SecurityConstants.TRUST_MODULE)) {
AxisModule trustModule = service.getAxisConfiguration()
.getModule(SecurityConstants.TRUST_MODULE);
if (log.isDebugEnabled()) {
log.debug("Enabling trust module : " + SecurityConstants.TRUST_MODULE);
}
service.disengageModule(trustModule);
service.engageModule(trustModule);
Properties cryptoProps = new Properties();
cryptoProps.setProperty(ServerCrypto.PROP_ID_PRIVATE_STORE,
configParams.getPrivateStore());
cryptoProps.setProperty(ServerCrypto.PROP_ID_DEFAULT_ALIAS,
configParams.getKeyAlias());
if (configParams.getTrustStores() != null) {
cryptoProps.setProperty(ServerCrypto.PROP_ID_TRUST_STORES,
configParams.getTrustStores());
}
service.addParameter(RahasUtil.getSCTIssuerConfigParameter(
ServerCrypto.class.getName(), cryptoProps, -1, null, true, true));
service.addParameter(RahasUtil.getTokenCancelerConfigParameter());
}
// Authorization
AuthorizationManager manager = userRealm.getAuthorizationManager();
String resourceName = serviceGroupId + "/" + serviceName;
removeAuthorization(userRealm,serviceGroupId,serviceName);
String allowRolesParameter = configParams.getAllowedRoles();
if (allowRolesParameter != null) {
if (log.isDebugEnabled()) {
log.debug("Authorizing roles " + allowRolesParameter);
}
String[] allowRoles = allowRolesParameter.split(",");
if (allowRoles != null) {
for (String role : allowRoles) {
manager.authorizeRole(role, resourceName,
UserCoreConstants.INVOKE_SERVICE_PERMISSION);
}
}
}
// Password Callback Handler
ServicePasswordCallbackHandler handler =
new ServicePasswordCallbackHandler(configParams, serviceGroupId, serviceName,
govRegistry, userRealm);
Parameter param = new Parameter();
param.setName(WSHandlerConstants.PW_CALLBACK_REF);
param.setValue(handler);
service.addParameter(param);
} catch (Throwable e) {
//TODO: Copied from 4.2.2.
//TODO: Not sure why we are catching throwable. Need to check error handling is correct
String msg = "Cannot apply security parameters";
log.error(msg, e);
}
}
/**
* Extract carbon security config element from the Policy
*
* @param policy Security Policy
* @return security config element
*/
private OMElement getSecurityConfig(Policy policy) {
Iterator<PolicyComponent> iterator = policy.getPolicyComponents().iterator();
while (iterator.hasNext()) {
PolicyComponent component = iterator.next();
if (component instanceof XmlPrimtiveAssertion) {
OMElement value = ((XmlPrimtiveAssertion) component).getValue();
if (value != null &&
SecurityConfigParamBuilder.SECURITY_CONFIG_QNAME.equals(value.getQName())) {
if (log.isDebugEnabled()) {
log.debug("Carbon Security config found : " + value.toString());
}
return value;
}
}
}
return null;
}
@Override
public void addParameter(Parameter param) throws AxisFault {
// This method will not be used
}
@Override
public void deserializeParameters(OMElement parameterElement) throws AxisFault {
// This method will not be used
}
@Override
public Parameter getParameter(String name) {
// This method will not be used
return null;
}
@Override
public ArrayList getParameters() {
// This method will not be used
return new ArrayList();
}
@Override
public boolean isParameterLocked(String parameterName) {
// This method will not be used
return false;
}
@Override
public void removeParameter(Parameter param) throws AxisFault {
// This method will not be used
}
protected void setRegistryService(RegistryService registryService) {
SecurityServiceHolder.setRegistryService(registryService);
}
protected void setRealmService(RealmService realmService) {
SecurityServiceHolder.setRealmService(realmService);
}
protected void unsetRealmService(RealmService realmService) {
SecurityServiceHolder.setRealmService(null);
}
protected void unsetRegistryService(RegistryService registryService) {
SecurityServiceHolder.setRegistryService(null);
}
/**
* Check whether policyID belongs to a security scenario
*
* @param policyId policy id
* @return whether policyID belongs to a security scenario
*/
private boolean isSecPolicy(String policyId) {
if ("RMPolicy".equals(policyId) || "WSO2CachingPolicy".equals(policyId)
|| "WSO2ServiceThrottlingPolicy".equals(policyId)) {
return false;
}
if (log.isDebugEnabled()) {
log.debug("Policy ID : " + policyId + " is identified as a security policy");
}
return true;
}
private void removeAuthorization (UserRealm userRealm, String serviceGroupId,
String serviceName) throws UserStoreException {
AuthorizationManager manager = userRealm.getAuthorizationManager();
String resourceName = serviceGroupId + "/" + serviceName;
String[] roles = manager.
getAllowedRolesForResource(resourceName,
UserCoreConstants.INVOKE_SERVICE_PERMISSION);
if (roles != null) {
for (String role : roles) {
manager.clearRoleAuthorization(role, resourceName,
UserCoreConstants.INVOKE_SERVICE_PERMISSION);
}
}
}
private void applyPolicyToSTS(AxisService service) throws SecurityConfigException, AxisFault {
try {
int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
Registry configRegistry = SecurityServiceHolder.getRegistryService()
.getConfigSystemRegistry(tenantId);
String servicePath = getRegistryServicePath(service);
Parameter param = new Parameter();
param.setName(APPLY_POLICY_TO_BINDINGS);
param.setValue(Boolean.TRUE.toString());
service.addParameter(param);
String policyResourcePath = servicePath + RegistryResources.POLICIES;
if (configRegistry.resourceExists(policyResourcePath)) {
Resource resource = configRegistry.get(policyResourcePath);
if (resource instanceof Collection) {
for (String policyPath : ((Collection) resource).getChildren()) {
Resource res = configRegistry.get(policyPath);
Policy policy = loadPolicy(res);
service.getPolicySubject().attachPolicy(policy);
}
}
}
} catch (org.wso2.carbon.registry.api.RegistryException e) {
log.error("Error occurred while persisting policy", e);
} catch (XMLStreamException e) {
log.error("Error occurred while persisting policy", e);
}
}
private Policy loadPolicy(Resource resource) throws org.wso2.carbon.registry.api.RegistryException, XMLStreamException {
InputStream in = resource.getContentStream();
XMLStreamReader parser = XMLInputFactory.newInstance().createXMLStreamReader(in);
StAXOMBuilder builder = new StAXOMBuilder(parser);
OMElement policyElement = builder.getDocumentElement();
return PolicyEngine.getPolicy(policyElement);
}
private String getRegistryServicePath(AxisService service) {
StringBuilder pathValue = new StringBuilder();
return (pathValue
.append(RegistryResources.SERVICE_GROUPS)
.append(service.getAxisServiceGroup().getServiceGroupName())
.append(RegistryResources.SERVICES)
.append(service.getName())).toString();
}
public PolicySubject getPolicySubjectFromBindings(AxisService service){
return null;
}
public void addPolicyToAllBindings(AxisService axisService, Policy policy)
throws ServerException {
try {
if (policy.getId() == null) {
// Generate an ID
policy.setId(UUIDGenerator.getUUID());
}
Map endPointMap = axisService.getEndpoints();
for (Object o : endPointMap.entrySet()) {
Map.Entry entry = (Map.Entry) o;
AxisEndpoint point = (AxisEndpoint) entry.getValue();
AxisBinding binding = point.getBinding();
String bindingName = binding.getName().getLocalPart();
//only UTOverTransport is allowed for HTTP
if (bindingName.endsWith("HttpBinding") &&
(!policy.getAttributes().containsValue("UTOverTransport"))) {
continue;
}
binding.getPolicySubject().attachPolicy(policy);
// Add the new policy to the registry
}
} catch (Exception e) {
log.error("Error in adding security policy to all bindings", e);
throw new ServerException("addPoliciesToService", e);
}
}
private Policy applyPolicyToBindings(AxisService axisService) throws ServerException {
Parameter parameter = axisService.getParameter(APPLY_POLICY_TO_BINDINGS);
if (parameter != null && "true".equalsIgnoreCase(parameter.getValue().toString()) &&
axisService.getPolicySubject() != null && axisService.getPolicySubject().getAttachedPolicyComponents()
!= null) {
Iterator iterator = axisService.getPolicySubject().
getAttachedPolicyComponents().iterator();
while (iterator.hasNext()) {
PolicyComponent currentPolicyComponent = (PolicyComponent) iterator.next();
if (currentPolicyComponent instanceof Policy) {
Policy policy = ((Policy) currentPolicyComponent);
String policyId = policy.getId();
axisService.getPolicySubject().detachPolicyComponent(policyId);
addPolicyToAllBindings(axisService, policy);
return policy;
}
}
}
return null;
}
}