/* * 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.entitlement; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xerces.impl.Constants; import org.apache.xerces.util.SecurityManager; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.wso2.balana.AbstractPolicy; import org.wso2.balana.Balana; import org.wso2.balana.ParsingException; import org.wso2.balana.Policy; import org.wso2.balana.PolicySet; import org.wso2.balana.XACMLConstants; import org.wso2.balana.attr.AttributeValue; import org.wso2.balana.attr.BooleanAttribute; import org.wso2.balana.attr.DateAttribute; import org.wso2.balana.attr.DateTimeAttribute; import org.wso2.balana.attr.DoubleAttribute; import org.wso2.balana.attr.HexBinaryAttribute; import org.wso2.balana.attr.IntegerAttribute; import org.wso2.balana.attr.StringAttribute; import org.wso2.balana.attr.TimeAttribute; import org.wso2.balana.combine.PolicyCombiningAlgorithm; import org.wso2.balana.combine.xacml2.FirstApplicablePolicyAlg; import org.wso2.balana.combine.xacml2.OnlyOneApplicablePolicyAlg; import org.wso2.balana.combine.xacml3.DenyOverridesPolicyAlg; import org.wso2.balana.combine.xacml3.DenyUnlessPermitPolicyAlg; import org.wso2.balana.combine.xacml3.OrderedDenyOverridesPolicyAlg; import org.wso2.balana.combine.xacml3.OrderedPermitOverridesPolicyAlg; import org.wso2.balana.combine.xacml3.PermitOverridesPolicyAlg; import org.wso2.balana.combine.xacml3.PermitUnlessDenyPolicyAlg; import org.wso2.balana.ctx.AbstractRequestCtx; import org.wso2.balana.ctx.Attribute; import org.wso2.balana.xacml3.Attributes; import org.wso2.carbon.identity.entitlement.cache.EntitlementBaseCache; import org.wso2.carbon.identity.entitlement.cache.IdentityCacheEntry; import org.wso2.carbon.identity.entitlement.cache.IdentityCacheKey; import org.wso2.carbon.identity.entitlement.common.EntitlementConstants; import org.wso2.carbon.identity.entitlement.dto.AttributeDTO; import org.wso2.carbon.identity.entitlement.dto.PolicyDTO; import org.wso2.carbon.identity.entitlement.dto.PolicyStoreDTO; import org.wso2.carbon.identity.entitlement.internal.EntitlementExtensionBuilder; import org.wso2.carbon.identity.entitlement.internal.EntitlementServiceComponent; import org.wso2.carbon.identity.entitlement.pap.EntitlementAdminEngine; import org.wso2.carbon.identity.entitlement.pap.store.PAPPolicyStore; import org.wso2.carbon.identity.entitlement.pap.store.PAPPolicyStoreManager; import org.wso2.carbon.identity.entitlement.pap.store.PAPPolicyStoreReader; import org.wso2.carbon.identity.entitlement.policy.publisher.PolicyPublisher; import org.wso2.carbon.identity.entitlement.policy.store.PolicyStoreManageModule; import org.wso2.carbon.identity.entitlement.policy.version.PolicyVersionManager; 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.exceptions.RegistryException; import org.wso2.carbon.utils.CarbonUtils; import org.wso2.carbon.identity.entitlement.util.CarbonEntityResolver; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.validation.Schema; import javax.xml.validation.Validator; import javax.xml.XMLConstants; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.text.DateFormat; import java.text.ParseException; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; /** * Provides utility functionalities used across different classes. */ public class EntitlementUtil { private static Log log = LogFactory.getLog(EntitlementUtil.class); private static final String SECURITY_MANAGER_PROPERTY = Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY; private static final int ENTITY_EXPANSION_LIMIT = 0; public static final String EXTERNAL_GENERAL_ENTITIES_URI = "http://xml.org/sax/features/external-general-entities"; /** * Return an instance of a named cache that is common to all tenants. * * @param name the name of the cache. * @return the named cache instance. */ public static EntitlementBaseCache<IdentityCacheKey, IdentityCacheEntry> getCommonCache(String name) { // TODO Should verify the cache creation done per tenant or as below // We create a single cache for all tenants. It is not a good choice to create per-tenant // caches in this case. We qualify tenants by adding the tenant identifier in the cache key. // PrivilegedCarbonContext currentContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); // PrivilegedCarbonContext.startTenantFlow(); // try { // currentContext.setTenantId(MultitenantConstants.SUPER_TENANT_ID); // return CacheManager.getInstance().getCache(name); // } finally { // PrivilegedCarbonContext.endTenantFlow(); // } return new EntitlementBaseCache<IdentityCacheKey, IdentityCacheEntry>(name); } /** * Return the Attribute Value Object for given string value and data type * * @param value attribute value as a String object * @param type attribute data type name as String object * @return Attribute Value Object * @throws EntitlementException throws */ public static AttributeValue getAttributeValue(final String value, String type) throws EntitlementException { try { if (StringAttribute.identifier.equals(type)) { return new StringAttribute(value); } if (IntegerAttribute.identifier.equals(type)) { return new IntegerAttribute(Long.parseLong(value)); } if (BooleanAttribute.identifier.equals(type)) { return BooleanAttribute.getInstance(value); } if (DoubleAttribute.identifier.equals(type)) { return new DoubleAttribute(Double.parseDouble(value)); } if (DateAttribute.identifier.equals(type)) { return new DateAttribute(DateFormat.getDateInstance().parse(value)); } if (DateTimeAttribute.identifier.equals(type)) { return new DateTimeAttribute(DateFormat.getDateInstance().parse(value)); } if (TimeAttribute.identifier.equals(type)) { return TimeAttribute.getInstance(value); } if (HexBinaryAttribute.identifier.equals(type)) { return new HexBinaryAttribute(value.getBytes()); } return new AttributeValue(new URI(type)) { @Override public String encode() { return value; } }; } catch (ParsingException e) { throw new EntitlementException("Error while creating AttributeValue object for given " + "string value and data type"); } catch (ParseException e) { throw new EntitlementException("Error while creating AttributeValue object for given " + "string value and data type"); } catch (URISyntaxException e) { throw new EntitlementException("Error while creating AttributeValue object for given " + "string value and data type"); } } /** * This creates the XACML 3.0 Request context from AttributeDTO object model * * @param attributeDTOs AttributeDTO objects as List * @return DOM element as XACML request * @throws EntitlementException throws, if fails */ public static AbstractRequestCtx createRequestContext(List<AttributeDTO> attributeDTOs) { Set<Attributes> attributesSet = new HashSet<Attributes>(); for (AttributeDTO DTO : attributeDTOs) { Attributes attributes = getAttributes(DTO); if (attributes != null) { attributesSet.add(attributes); } } return new org.wso2.balana.ctx.xacml3.RequestCtx(attributesSet, null); } /** * Validates the given policy XML files against the standard XACML policies. * * @param policy Policy to validate * @return return false, If validation failed or XML parsing failed or any IOException occurs */ public static boolean validatePolicy(PolicyDTO policy) { try { if (!"true".equalsIgnoreCase((String) EntitlementServiceComponent.getEntitlementConfig() .getEngineProperties().get(EntitlementExtensionBuilder.PDP_SCHEMA_VALIDATION))) { return true; } // there may be cases where you only updated the policy meta data in PolicyDTO not the // actual XACML policy String if (policy.getPolicy() == null || policy.getPolicy().trim().length() < 1) { return true; } //get policy version String policyXMLNS = getPolicyVersion(policy.getPolicy()); Map<String, Schema> schemaMap = EntitlementServiceComponent. getEntitlementConfig().getPolicySchemaMap(); //load correct schema by version Schema schema = schemaMap.get(policyXMLNS); if (schema != null) { //build XML document DocumentBuilder documentBuilder = getSecuredDocumentBuilder(false); InputStream stream = new ByteArrayInputStream(policy.getPolicy().getBytes()); Document doc = documentBuilder.parse(stream); //Do the DOM validation DOMSource domSource = new DOMSource(doc); DOMResult domResult = new DOMResult(); Validator validator = schema.newValidator(); validator.validate(domSource, domResult); if (log.isDebugEnabled()) { log.debug("XACML Policy validation succeeded with the Schema"); } return true; } else { log.error("Invalid Namespace in policy"); } } catch (SAXException e) { log.error("XACML policy is not valid according to the schema :" + e.getMessage()); } catch (IOException e) { //ignore } catch (ParserConfigurationException e) { //ignore } return false; } public static String getPolicyVersion(String policy) { try { //build XML document DocumentBuilder documentBuilder = getSecuredDocumentBuilder(false); InputStream stream = new ByteArrayInputStream(policy.getBytes()); Document doc = documentBuilder.parse(stream); //get policy version Element policyElement = doc.getDocumentElement(); return policyElement.getNamespaceURI(); } catch (Exception e) { log.debug(e); // ignore exception as default value is used log.warn("Policy version can not be identified. Default XACML 3.0 version is used"); return XACMLConstants.XACML_3_0_IDENTIFIER; } } public static Attributes getAttributes(AttributeDTO attributeDataDTO) { try { AttributeValue value = Balana.getInstance().getAttributeFactory(). createValue(new URI(attributeDataDTO.getAttributeDataType()), attributeDataDTO.getAttributeValue()); Attribute attribute = new Attribute(new URI(attributeDataDTO.getAttributeId()), null, null, value, XACMLConstants.XACML_VERSION_3_0); Set<Attribute> set = new HashSet<Attribute>(); set.add(attribute); String category = attributeDataDTO.getCategory(); // We are only creating XACML 3.0 requests Therefore covert order XACML categories to new uris if (PDPConstants.SUBJECT_ELEMENT.equals(category)) { category = PDPConstants.SUBJECT_CATEGORY_URI; } else if (PDPConstants.RESOURCE_ELEMENT.equals(category)) { category = PDPConstants.RESOURCE_CATEGORY_URI; } else if (PDPConstants.ACTION_ELEMENT.equals(category)) { category = PDPConstants.ACTION_CATEGORY_URI; } else if (PDPConstants.ENVIRONMENT_ELEMENT.equals(category)) { category = PDPConstants.ENVIRONMENT_CATEGORY_URI; } return new Attributes(new URI(category), set); } catch (Exception e) { log.debug(e); //ignore and return null; } return null; } /** * Creates PolicyCombiningAlgorithm object based on policy combining url * * @param uri policy combining url as String * @return PolicyCombiningAlgorithm object * @throws EntitlementException throws if unsupported algorithm */ public static PolicyCombiningAlgorithm getPolicyCombiningAlgorithm(String uri) throws EntitlementException { if (FirstApplicablePolicyAlg.algId.equals(uri)) { return new FirstApplicablePolicyAlg(); } else if (DenyOverridesPolicyAlg.algId.equals(uri)) { return new DenyOverridesPolicyAlg(); } else if (PermitOverridesPolicyAlg.algId.equals(uri)) { return new PermitOverridesPolicyAlg(); } else if (OnlyOneApplicablePolicyAlg.algId.equals(uri)) { return new OnlyOneApplicablePolicyAlg(); } else if (OrderedDenyOverridesPolicyAlg.algId.equals(uri)) { return new OrderedDenyOverridesPolicyAlg(); } else if (OrderedPermitOverridesPolicyAlg.algId.equals(uri)) { return new OrderedPermitOverridesPolicyAlg(); } else if (DenyUnlessPermitPolicyAlg.algId.equals(uri)) { return new DenyUnlessPermitPolicyAlg(); } else if (PermitUnlessDenyPolicyAlg.algId.equals(uri)) { return new PermitUnlessDenyPolicyAlg(); } throw new EntitlementException("Unsupported policy algorithm " + uri); } /** * Creates Simple XACML request using given attribute value.Here category, attribute ids and datatypes are * taken as default values. * * @param subject user or role * @param resource resource name * @param action action name * @param environment environment name * @return String XACML request as String */ public static String createSimpleXACMLRequest(String subject, String resource, String action, String environment) { return "<Request xmlns=\"urn:oasis:names:tc:xacml:3.0:core:schema:wd-17\" CombinedDecision=\"false\" ReturnPolicyIdList=\"false\">\n" + "<Attributes Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:action\">\n" + "<Attribute AttributeId=\"urn:oasis:names:tc:xacml:1.0:action:action-id\" IncludeInResult=\"false\">\n" + "<AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">" + action + "</AttributeValue>\n" + "</Attribute>\n" + "</Attributes>\n" + "<Attributes Category=\"urn:oasis:names:tc:xacml:1.0:subject-category:access-subject\">\n" + "<Attribute AttributeId=\"urn:oasis:names:tc:xacml:1.0:subject:subject-id\" IncludeInResult=\"false\">\n" + "<AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">" + subject + "</AttributeValue>\n" + "</Attribute>\n" + "</Attributes>\n" + "<Attributes Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:environment\">\n" + "<Attribute AttributeId=\"urn:oasis:names:tc:xacml:1.0:environment:environment-id\" IncludeInResult=\"false\">\n" + "<AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">" + environment + "</AttributeValue>\n" + "</Attribute>\n" + "</Attributes>\n" + "<Attributes Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:resource\">\n" + "<Attribute AttributeId=\"urn:oasis:names:tc:xacml:1.0:resource:resource-id\" IncludeInResult=\"false\">\n" + "<AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">" + resource + "</AttributeValue>\n" + "</Attribute>\n" + "</Attributes>\n" + "</Request> "; } public static void addSamplePolicies(Registry registry) { File policyFolder = new File(CarbonUtils.getCarbonHome() + File.separator + "repository" + File.separator + "resources" + File.separator + "identity" + File.separator + "policies" + File.separator + "xacml" + File.separator + "default"); if (policyFolder.exists()) { for (File policyFile : policyFolder.listFiles()) { if (policyFile.isFile()) { PolicyDTO policyDTO = new PolicyDTO(); try { policyDTO.setPolicy(FileUtils.readFileToString(policyFile)); EntitlementUtil.addFilesystemPolicy(policyDTO, registry, false); } catch (Exception e) { // log and ignore log.error("Error while adding sample XACML policies", e); } } } } } /** * This method checks whether there is a policy having the same policyId as the given policyId is in the registry * * @param policyId * @param registry * @return * @throws EntitlementException */ public static boolean isPolicyExists(String policyId, Registry registry) throws EntitlementException { PAPPolicyStoreReader policyReader = null; policyReader = new PAPPolicyStoreReader(new PAPPolicyStore(registry)); return policyReader.isExistPolicy(policyId); } /** * This method persists a new XACML policy, which was read from filesystem, * in the registry * * @param policyDTO PolicyDTO object * @param registry Registry * @param promote where policy must be promote PDP or not * @return returns whether True/False * @throws org.wso2.carbon.identity.entitlement.EntitlementException throws if policy with same id is exist */ public static boolean addFilesystemPolicy(PolicyDTO policyDTO, Registry registry, boolean promote) throws EntitlementException { PAPPolicyStoreManager policyAdmin; AbstractPolicy policyObj; if (policyDTO.getPolicy() != null) { policyDTO.setPolicy(policyDTO.getPolicy().replaceAll(">\\s+<", "><")); } policyObj = getPolicy(policyDTO.getPolicy()); if (policyObj != null) { PAPPolicyStore policyStore = new PAPPolicyStore(registry); policyAdmin = new PAPPolicyStoreManager(); policyDTO.setPolicyId(policyObj.getId().toASCIIString()); policyDTO.setActive(true); if (isPolicyExists(policyDTO.getPolicyId(), registry)) { throw new EntitlementException( "An Entitlement Policy with the given ID already exists"); } policyDTO.setPromote(promote); PolicyVersionManager versionManager = EntitlementAdminEngine.getInstance().getVersionManager(); try { String version = versionManager.createVersion(policyDTO); policyDTO.setVersion(version); } catch (EntitlementException e) { log.error("Policy versioning is not supported", e); } policyAdmin.addOrUpdatePolicy(policyDTO); PAPPolicyStoreReader reader = new PAPPolicyStoreReader(policyStore); policyDTO = reader.readPolicyDTO(policyDTO.getPolicyId()); PolicyStoreDTO policyStoreDTO = new PolicyStoreDTO(); policyStoreDTO.setPolicyId(policyDTO.getPolicyId()); policyStoreDTO.setPolicy(policyDTO.getPolicy()); policyStoreDTO.setPolicyOrder(policyDTO.getPolicyOrder()); policyStoreDTO.setAttributeDTOs(policyDTO.getAttributeDTOs()); policyStoreDTO.setActive(policyDTO.isActive()); policyStoreDTO.setSetActive(policyDTO.isActive()); if (promote) { addPolicyToPDP(policyStoreDTO); } policyAdmin.addOrUpdatePolicy(policyDTO); return true; } else { throw new EntitlementException("Invalid Entitlement Policy"); } } public static AbstractPolicy getPolicy(String policy) { DocumentBuilder builder; InputStream stream = null; // now use the factory to create the document builder try { builder = getSecuredDocumentBuilder(true); stream = new ByteArrayInputStream(policy.getBytes("UTF-8")); Document doc = builder.parse(stream); Element root = doc.getDocumentElement(); String name = root.getTagName(); // see what type of policy this is if (name.equals("Policy")) { return Policy.getInstance(root); } else if (name.equals("PolicySet")) { return PolicySet.getInstance(root, null); } else { // this isn't a root type that we know how to handle throw new ParsingException("Unknown root document type: " + name); } } catch (Exception e) { throw new IllegalArgumentException("Error while parsing start up policy", e); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { log.error("Error while closing input stream"); } } } } /** * Gets policy dto for a given policy id * * @param policyId policy id * @param registry Registry * @return returns policy * @throws org.wso2.carbon.identity.entitlement.EntitlementException */ public static PolicyDTO getPolicy(String policyId, Registry registry) throws EntitlementException { PAPPolicyStoreReader policyReader = null; policyReader = new PAPPolicyStoreReader(new PAPPolicyStore(registry)); return policyReader.readPolicyDTO(policyId); } /** * @param policyStoreDTO * @return */ public static void addPolicyToPDP(PolicyStoreDTO policyStoreDTO) throws EntitlementException { Registry registry; String policyPath; Collection policyCollection; Resource resource; Map.Entry<PolicyStoreManageModule, Properties> entry = EntitlementServiceComponent .getEntitlementConfig().getPolicyStore().entrySet().iterator().next(); String policyStorePath = entry.getValue().getProperty("policyStorePath"); if (policyStorePath == null) { policyStorePath = "/repository/identity/entitlement/policy/pdp/"; } if (policyStoreDTO == null || policyStoreDTO.getPolicy() == null || policyStoreDTO.getPolicy().trim().length() == 0 || policyStoreDTO.getPolicyId() == null || policyStoreDTO.getPolicyId().trim().length() == 0) { return; } try { registry = EntitlementServiceComponent.getRegistryService() .getGovernanceSystemRegistry(); if (registry.resourceExists(policyStorePath)) { policyCollection = (Collection) registry.get(policyStorePath); } else { policyCollection = registry.newCollection(); } registry.put(policyStorePath, policyCollection); policyPath = policyStorePath + policyStoreDTO.getPolicyId(); if (registry.resourceExists(policyPath)) { resource = registry.get(policyPath); } else { resource = registry.newResource(); } resource.setProperty("policyOrder", Integer.toString(policyStoreDTO.getPolicyOrder())); resource.setContent(policyStoreDTO.getPolicy()); resource.setMediaType("application/xacml-policy+xml"); resource.setProperty("active", String.valueOf(policyStoreDTO.isActive())); AttributeDTO[] attributeDTOs = policyStoreDTO.getAttributeDTOs(); if (attributeDTOs != null) { setAttributesAsProperties(attributeDTOs, resource); } registry.put(policyPath, resource); //Enable published policies in PDP PAPPolicyStoreManager storeManager = EntitlementAdminEngine.getInstance().getPapPolicyStoreManager(); if (storeManager.isExistPolicy(policyStoreDTO.getPolicyId())) { PolicyPublisher publisher = EntitlementAdminEngine.getInstance().getPolicyPublisher(); String[] subscribers = new String[]{EntitlementConstants.PDP_SUBSCRIBER_ID}; if (policyStoreDTO.isActive()) { publisher.publishPolicy(new String[]{policyStoreDTO.getPolicyId()}, null, EntitlementConstants.PolicyPublish.ACTION_ENABLE, false, 0, subscribers, null); } else { publisher.publishPolicy(new String[]{policyStoreDTO.getPolicyId()}, null, EntitlementConstants.PolicyPublish.ACTION_DISABLE, false, 0, subscribers, null); } } } catch (RegistryException e) { log.error(e); throw new EntitlementException("Error while adding policy to PDP", e); } } /** * This helper method creates properties object which contains the policy meta data. * * @param attributeDTOs List of AttributeDTO * @param resource registry resource */ public static void setAttributesAsProperties(AttributeDTO[] attributeDTOs, Resource resource) { int attributeElementNo = 0; if (attributeDTOs != null) { for (AttributeDTO attributeDTO : attributeDTOs) { resource.setProperty("policyMetaData" + attributeElementNo, attributeDTO.getCategory() + "," + attributeDTO.getAttributeValue() + "," + attributeDTO.getAttributeId() + "," + attributeDTO.getAttributeDataType()); attributeElementNo++; } } } /** * * This method provides a secured document builder which will secure XXE attacks. * * @param setIgnoreComments whether to set setIgnoringComments in DocumentBuilderFactory. * @return DocumentBuilder * @throws ParserConfigurationException */ private static DocumentBuilder getSecuredDocumentBuilder(boolean setIgnoreComments) throws ParserConfigurationException { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setIgnoringComments(setIgnoreComments); documentBuilderFactory.setNamespaceAware(true); documentBuilderFactory.setExpandEntityReferences(false); documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); documentBuilderFactory.setFeature(EXTERNAL_GENERAL_ENTITIES_URI, false); SecurityManager securityManager = new SecurityManager(); securityManager.setEntityExpansionLimit(ENTITY_EXPANSION_LIMIT); documentBuilderFactory.setAttribute(SECURITY_MANAGER_PROPERTY, securityManager); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); documentBuilder.setEntityResolver(new CarbonEntityResolver()); return documentBuilder; } }