2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.openecomp.policy.xacml.test;
23 import java.io.IOException;
24 import java.net.MalformedURLException;
25 import java.nio.file.FileVisitResult;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.nio.file.SimpleFileVisitor;
30 import java.nio.file.attribute.BasicFileAttributes;
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.util.HashSet;
35 import java.util.List;
38 import java.util.regex.Matcher;
40 import javax.xml.bind.JAXBContext;
41 import javax.xml.bind.JAXBElement;
42 import javax.xml.bind.Marshaller;
44 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeType;
45 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeValueType;
46 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributesType;
47 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObjectFactory;
48 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicySetType;
49 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
50 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RequestType;
52 import org.apache.commons.cli.CommandLine;
53 import org.apache.commons.cli.GnuParser;
54 import org.apache.commons.cli.Option;
55 import org.apache.commons.cli.ParseException;
56 import org.apache.commons.logging.Log;
57 import org.apache.commons.logging.LogFactory;
59 import com.att.research.xacml.api.AttributeValue;
60 import com.att.research.xacml.api.DataType;
61 import com.att.research.xacml.api.DataTypeException;
62 import com.att.research.xacml.api.DataTypeFactory;
63 import com.att.research.xacml.api.Identifier;
64 import com.att.research.xacml.api.XACML3;
65 import com.att.research.xacml.std.IdentifierImpl;
66 import com.att.research.xacml.util.FactoryException;
67 import com.att.research.xacml.util.XACMLObjectCopy;
68 import com.att.research.xacml.util.XACMLPolicyAggregator;
69 import com.att.research.xacml.util.XACMLPolicyScanner;
70 import com.att.research.xacml.util.XACMLProperties;
73 * This class reads the policy in and extracts all the attributes and their values that is contained
74 * in the Policy. It then generates a request every single combination of attributes found.
76 * The attributes mostly come from the Target Match elements, since they have both an attribute designator/selector
77 * matched with an attribute value.
81 public class TestPolicy extends TestBase {
82 private static Log logger = LogFactory.getLog(TestPolicy.class);
86 private XACMLPolicyAggregator aggregator = new XACMLPolicyAggregator();
90 // Our command line parameters
92 public static final String OPTION_POLICY = "policy";
93 public static final String OPTION_SKIP_GENERATE = "skip";
96 options.addOption(new Option(OPTION_POLICY, true, "Path to the policy file."));
97 options.addOption(new Option(OPTION_SKIP_GENERATE, false, "Skip generating requests."));
100 public class FlattenerObject {
103 Identifier attribute;
104 Set<AttributeValue<?>> values;
108 * This application exercises a policy by producing ALL the possible request combinations for a policy.
110 * -policy Path to a policy file
113 * @throws HelpException
114 * @throws ParseException
115 * @throws MalformedURLException
118 public TestPolicy(String[] args) throws MalformedURLException, ParseException, HelpException {
123 * Look for the -policy command line argument. This application needs a pointer to a specific policy
128 * @see com.att.research.xacmlatt.pdp.test.TestBase#parseCommands(java.lang.String[])
131 protected void parseCommands(String[] args) throws ParseException, MalformedURLException, HelpException {
133 // Have our super do its job
135 super.parseCommands(args);
137 // Look for the policy option
140 cl = new GnuParser().parse(options, args);
141 if (cl.hasOption(OPTION_POLICY)) {
142 this.policy = Paths.get(cl.getOptionValue(OPTION_POLICY));
146 if (Files.notExists(this.policy)) {
147 throw new ParseException("Policy file does not exist.");
150 throw new ParseException("You need to specify the policy file to be used.");
152 if (cl.hasOption(OPTION_SKIP_GENERATE)) {
160 * We override this method because here is where we want to scan the policy and aggregate all
161 * the attributes that are defined within the policy. This routine will then dump all the possible
162 * requests into the requests sub-directory. Thus, when this method returns the TestBase can proceed
163 * to iterate each generated request and run it against the PDP engine.
166 * @see com.att.research.xacmlatt.pdp.test.TestBase#configure()
169 protected void configure() throws FactoryException {
171 // Have our base class do its thing
175 // Setup where the PDP can find the policy
177 System.setProperty(XACMLProperties.PROP_ROOTPOLICIES, "policy");
178 System.setProperty("policy.file", this.policy.toString());
180 // Determine if they want us to skip generation. This helps when a huge number of
181 // requests will get generated for a policy and can take some time to do so. The user
182 // can generate the requests once and then start testing a policy against the requests. Thus,
183 // the attributes never changed but the policy logic did (saves time).
189 // Now we will scan the policy and get all the attributes.
191 XACMLPolicyScanner scanner = new XACMLPolicyScanner(this.policy, this.aggregator);
193 // The scanner returns us a policy object
195 Object policyObject = scanner.scan();
197 // Just dump some info
199 if (policyObject instanceof PolicySetType) {
200 logger.info("Creating requests for policyset: " + ((PolicySetType)policyObject).getDescription());
201 } else if (policyObject instanceof PolicyType) {
202 logger.info("Creating requests for policy: " + ((PolicyType)policyObject).getDescription());
205 // Call the function to create the requests
207 if (policyObject != null) {
208 this.createRequests();
211 logger.info("Completed Generating requests.");
214 @SuppressWarnings("unchecked")
215 protected void createRequests() {
217 // Clear out our request directory
219 this.removeRequests();
223 Map<Identifier, Map<Identifier, Map<Identifier, Set<AttributeValue<?>>>>> attributeMap = this.aggregator.getAttributeMap();
225 // We're going to create an initial flat list of requests for each unique attribute ID. Unique being the
226 // category, datatype and attribute id.
228 // By flattening the list, it makes it easier to then generate all the combinations of possible requests.
230 List<FlattenerObject> attributes = new ArrayList<FlattenerObject>();
232 // Iterate through all the maps, we are going to flatten it
233 // out into an array list.
235 for (Map.Entry<Identifier, Map<Identifier, Map<Identifier, Set<AttributeValue<?>>>>> categoryEntry : attributeMap.entrySet()) {
236 String category = categoryEntry.getKey().toString();
237 if (logger.isDebugEnabled()) {
238 logger.debug("Category: " + category);
240 Map<Identifier, Map<Identifier, Set<AttributeValue<?>>>> datatypeMap = categoryEntry.getValue();
241 for (Map.Entry<Identifier, Map<Identifier, Set<AttributeValue<?>>>> datatypeEntry : datatypeMap.entrySet()) {
242 String datatype = datatypeEntry.getKey().toString();
243 if (logger.isDebugEnabled()) {
244 logger.debug("\tData Type: " + datatype);
246 Map<Identifier, Set<AttributeValue<?>>> attributeIDMap = datatypeEntry.getValue();
247 for (Map.Entry<Identifier, Set<AttributeValue<?>>> attributeIDEntry : attributeIDMap.entrySet()) {
248 String attributeID = attributeIDEntry.getKey().toString();
249 if (logger.isDebugEnabled()) {
250 logger.debug("\t\tAttribute ID: " + attributeID);
252 Set<AttributeValue<?>> attributeValueSet = attributeIDEntry.getValue();
254 // Sanity check to see if there are any values. Sometimes there isn't if an attribute
255 // is a designator that is part of a condition or variable.
257 if (attributeValueSet.isEmpty()) {
258 if (logger.isDebugEnabled()) {
259 logger.debug("No values for attribute " + attributeIDEntry.getKey().stringValue());
262 // Check for the boolean datatype, in that case we can safely
263 // assume the true/false are ALL the possible values.
265 if (datatypeEntry.getKey().equals(XACML3.ID_DATATYPE_BOOLEAN) == false) {
267 // Not boolean, so skip it
271 if (logger.isDebugEnabled()) {
272 logger.debug("No values but its a boolean datatype, we will include it anyway.");
276 // Create our flattener object
278 FlattenerObject flat = new FlattenerObject();
279 flat.category = categoryEntry.getKey();
280 flat.datatype = datatypeEntry.getKey();
281 flat.attribute = attributeIDEntry.getKey();
282 flat.values = new HashSet<AttributeValue<?>>();
283 if (datatypeEntry.getKey().equals(XACML3.ID_DATATYPE_BOOLEAN)) {
285 // There are only 2 possible values, true or false
287 flat.values.add(this.createAttributeValue(flat.datatype, true));
288 flat.values.add(this.createAttributeValue(flat.datatype, false));
290 flat.values.addAll(attributeValueSet);
292 attributes.add(flat);
296 if (attributes.size() <= 1) {
298 // Only one attribute, why bother
300 logger.info("Not enough attributes in policy: " + attributes.size());
304 * PLD work more on this later. This combinatorial formula is only accurate if each
305 * attribute has one value.
308 if (logger.isDebugEnabled()) {
310 // This isn't really accurate, if an attribute has more than one value
312 logger.debug(attributes.size() + " will generate " + computePossibleCombinations(attributes.size()));
315 for (int i = 0; i < attributes.size(); i++) {
316 FlattenerObject flat = attributes.get(i);
317 for (AttributeValue<?> value : flat.values) {
319 // Create a basic request object for just that attribute value.
321 RequestType request = new RequestType();
323 AttributesType attrs = new AttributesType();
324 attrs.setCategory(flat.category.stringValue());
325 request.getAttributes().add(attrs);
327 AttributeType attr = new AttributeType();
328 attr.setAttributeId(flat.attribute.stringValue());
329 attrs.getAttribute().add(attr);
331 AttributeValueType val = new AttributeValueType();
332 val.setDataType(flat.datatype.stringValue());
333 if (value.getValue() instanceof Collection) {
334 val.getContent().addAll((Collection<? extends Object>) value.getValue());
336 val.getContent().add(value.getValue().toString());
339 attr.getAttributeValue().add(val);
343 this.writeRequest(request);
345 // Initiate recursive call to add other attributes to the request
347 this.recursivelyGenerateRequests(request, i + 1, attributes);
352 protected void recursivelyGenerateRequests(RequestType request, int i, List<FlattenerObject> attributes) {
353 if (logger.isTraceEnabled()) {
354 logger.trace("recursiveGenerate index: " + index + " i: " + i);
356 for ( ; i < attributes.size(); i++) {
357 FlattenerObject flat = attributes.get(i);
358 for (AttributeValue<?> value : flat.values) {
360 // Make a copy of the request
362 RequestType copyRequest = XACMLObjectCopy.deepCopy(request);
364 // Create the value object
366 AttributeValueType newValue = new AttributeValueType();
367 newValue.setDataType(flat.datatype.stringValue());
368 if (value.getValue() instanceof Collection) {
369 for (Object v : (Collection<?>) value.getValue()) {
370 newValue.getContent().add(v.toString());
373 newValue.getContent().add(value.getValue().toString());
376 // Add the value to the request
378 this.addAttribute(copyRequest, flat.category.stringValue(), flat.attribute.stringValue(), newValue);
382 this.writeRequest(copyRequest);
384 // Recursively go through the rest of the attributes
386 this.recursivelyGenerateRequests(copyRequest, i + 1, attributes);
391 public static long computePossibleCombinations(long numberOfAttributes) {
393 for (long i = numberOfAttributes; i > 0; i--) {
394 num += computeCombinations(numberOfAttributes, i);
399 public static long computeFactorial(long n) {
401 for (long i = 1; i <= n; i++) {
407 public static long computePermutationsWithoutRepetition(long n, long r) {
415 for (long i = n; i > 1; i--) {
419 for (long i = (n - r); i > 1; i--) {
422 return nPrime / n_rPrime;
425 public static long computeCombinations(long n, long r) {
435 for (long i = n; i > 1; i--) {
439 for (long i = r; i > 1; i--) {
443 for (long i = (n - r); i > 1; i--) {
447 return nPrime / (rPrime * n_rPrime);
450 protected Set<AttributeValue<?>> getAttributeValues(RequestType request) {
454 Map<Identifier, Map<Identifier, Map<Identifier, Set<AttributeValue<?>>>>> attributeMap = this.aggregator.getAttributeMap();
456 // Find the attribute
458 AttributesType attrs = request.getAttributes().get(0);
459 Map<Identifier, Map<Identifier, Set<AttributeValue<?>>>> categoryMap = attributeMap.get(new IdentifierImpl(attrs.getCategory()));
460 if (categoryMap != null) {
461 AttributeType a = attrs.getAttribute().get(0);
462 Map<Identifier, Set<AttributeValue<?>>> datatypeMap = categoryMap.get(new IdentifierImpl(a.getAttributeValue().get(0).getDataType()));
463 if (datatypeMap != null) {
464 Set<AttributeValue<?>> values = datatypeMap.get(new IdentifierImpl(a.getAttributeId()));
465 if (values != null) {
470 return Collections.emptySet();
473 protected AttributeValue<?> createAttributeValue(Identifier datatype, Object value) {
474 DataTypeFactory dataTypeFactory = null;
476 dataTypeFactory = DataTypeFactory.newInstance();
477 if (dataTypeFactory == null) {
478 logger.error("Could not create data type factory");
481 } catch (FactoryException e) {
482 logger.error("Can't get Data type Factory: " + e.getLocalizedMessage());
485 DataType<?> dataTypeExtended = dataTypeFactory.getDataType(datatype);
486 if (dataTypeExtended == null) {
487 logger.error("Unknown datatype: " + datatype);
491 return dataTypeExtended.createAttributeValue(value);
492 } catch (DataTypeException e) {
498 protected void removeRequests() {
500 // Delete any existing request files that we generate. i.e. Have the Unknown in the file name.
503 Files.walkFileTree(Paths.get(this.directory.toString(), "requests"), new SimpleFileVisitor<Path>() {
506 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
508 // Sanity check the file name
510 Matcher matcher = pattern.matcher(file.getFileName().toString());
511 if (matcher.matches()) {
514 // Pull what this request is supposed to be
517 int count = matcher.groupCount();
519 group = matcher.group(count-1);
524 if (group.equals("Unknown")) {
530 } catch (Exception e) {
532 logger.error("Exception Occured"+e);
535 return super.visitFile(file, attrs);
538 } catch (IOException e) {
539 logger.error("Failed to removeRequests from " + this.directory + " " + e);
543 protected void addRequests(RequestType request, List<RequestType> requests, int index) {
544 for (RequestType req : requests) {
546 // There really should only be one attribute
548 for (AttributesType attrs : req.getAttributes()) {
549 for (AttributeType attr : attrs.getAttribute()) {
550 for (AttributeValueType value : attr.getAttributeValue()) {
551 if (this.addAttribute(request, attrs.getCategory(), attr.getAttributeId(), value)) {
552 this.writeRequest(request);
561 * Writes the request into the "requests" sub-directory, relative to the value of the "directory" setup
562 * during initialization.
564 * Writing the requests out allows one to go back and easily refer to the request when analyzing the responses
565 * generated after the PDP decide() call. Also, one can then use the generated requests into any test tools
566 * they wish to build.
568 * @param request - The request to be written.
570 protected void writeRequest(RequestType request) {
571 if (logger.isTraceEnabled()) {
572 logger.trace("writeRequest: " + index);
575 ObjectFactory of = new ObjectFactory();
576 JAXBElement<RequestType> requestElement = of.createRequest(request);
577 JAXBContext context = JAXBContext.newInstance(RequestType.class);
578 Marshaller m = context.createMarshaller();
579 m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
580 Path outFile = Paths.get(this.directory, "requests", String.format("Request.%06d.Unknown.xml", this.index));
581 m.marshal(requestElement, outFile.toFile());
582 } catch (Exception e) {
583 logger.error("Failed to write request: " + e.getMessage());
588 protected boolean addAttribute(RequestType request, String category, String id, AttributeValueType value) {
590 // See if the category exists
592 for (AttributesType attrs : request.getAttributes()) {
593 if (attrs.getCategory().equals(category)) {
595 // It does have the category. But does it have the attribute ID?
597 for (AttributeType attr : attrs.getAttribute()) {
598 if (attr.getAttributeId().equals(id)) {
600 // Yes, check for the same datatype
602 for (AttributeValueType val : attr.getAttributeValue()) {
603 if (val.getDataType().equals(value.getDataType())) {
605 // We have something already there
611 // The ID exists, but not the datatype
613 attr.getAttributeValue().add(value);
618 // If we get here, the ID does not exist
620 AttributeType attr = new AttributeType();
621 attr.setAttributeId(id);
622 attr.getAttributeValue().add(value);
623 attrs.getAttribute().add(attr);
628 // If we get here, the category does not exist. So add it in.
630 AttributesType attrs = new AttributesType();
631 attrs.setCategory(category);
632 AttributeType attr = new AttributeType();
633 attr.setAttributeId(id);
634 attr.getAttributeValue().add(value);
635 attrs.getAttribute().add(attr);
636 request.getAttributes().add(attrs);
640 public static void main(String[] args) {
642 new TestPolicy(args).run();
643 } catch (ParseException | IOException | FactoryException e) {
645 } catch (HelpException e) {
650 // Map<CATEGORY, MAP<DATATYPE, MAP<ATTRIBUTEID, SET<VALUES>>>
651 @SuppressWarnings("unchecked")
652 private void generateRequests(Map<Identifier, Map<Identifier, Map<Identifier, Set<AttributeValue<?>>>>> categoryMap) {
653 meta = new ArrayList<>();
655 for (Map.Entry<Identifier, Map<Identifier, Map<Identifier, Set<AttributeValue<?>>>>> categoryEntry : categoryMap.entrySet()) {
656 String category = categoryEntry.getKey().toString();
657 logger.debug("Category: " + category);
658 Map<Identifier, Map<Identifier, Set<AttributeValue<?>>>> datatypeMap = categoryEntry.getValue();
659 for (Map.Entry<Identifier, Map<Identifier, Set<AttributeValue<?>>>> datatypeEntry : datatypeMap.entrySet()) {
660 String datatype = datatypeEntry.getKey().toString();
661 logger.debug("\tData Type: " + datatype);
662 Map<Identifier, Set<AttributeValue<?>>> attributeIDMap = datatypeEntry.getValue();
663 for (Map.Entry<Identifier, Set<AttributeValue<?>>> attributeIDEntry : attributeIDMap.entrySet()) {
664 String attributeID = attributeIDEntry.getKey().toString();
665 logger.debug("\t\tAttribute ID: " + attributeID);
666 Set<AttributeValue<?>> attributeValueSet = attributeIDEntry.getValue();
667 for (AttributeValue<?> value : attributeValueSet) {
668 logger.debug("\t\t\tAttribute Value: " + value);
670 Iterator<AttributeValue<?>> iterator = attributeValueSet.iterator();
671 logger.debug("\t\t\t# of Attribute values: " + attributeValueSet.size());
672 meta.add(new Object[] {category, datatype, attributeID, iterator.next(), iterator, attributeValueSet});
678 for (File file : output.toFile().listFiles()) {
683 RequestType request = new RequestType();
684 request.setCombinedDecision(false);
685 request.setReturnPolicyIdList(false);
686 List<AttributesType> attributesList = request.getAttributes();
688 Map<String, AttributesType> category2Attribute= new HashMap<>();
689 for (int i = 0; i < meta.size(); i++) {
690 Object[] record = meta.get(i);
692 AttributesType attributes = null;
693 if (category2Attribute.containsKey(record[0].toString()))
694 attributes = category2Attribute.get(record[0].toString());
696 attributes = new AttributesType();
697 attributes.setCategory(record[0].toString());
698 category2Attribute.put(record[0].toString(), attributes);
699 attributesList.add(attributes);
701 // attributes.setId(record[2].toString());
702 List<AttributeType> attrList = attributes.getAttribute();
704 AttributeType attribute = new AttributeType();
705 attribute.setAttributeId(record[2].toString());
706 List<AttributeValueType> valueList = attribute.getAttributeValue();
708 AttributeValue<?> attributeValue = (AttributeValue<?>) record[3];
710 AttributeValueType value = new AttributeValueType();
711 value.setDataType(attributeValue.getDataTypeId().toString());
712 List<Object> content = value.getContent();
713 content.addAll((Collection<? extends Object>) attributeValue.getValue());
714 valueList.add(value);
716 attrList.add(attribute);
720 ObjectFactory of = new ObjectFactory();
721 JAXBElement<RequestType> requestElement = of.createRequest(request);
722 JAXBContext context = JAXBContext.newInstance(RequestType.class);
723 Marshaller m = context.createMarshaller();
724 m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
725 m.marshal(requestElement, output.resolve("request" + count + ".xml").toFile());
727 // if (count == 0) {//Just send the first request to the engine
728 StringWriter sw = new StringWriter();
729 m.marshal(requestElement, sw);
730 logger.info(sw.toString());
731 EngineCaller engine = new LocalEngineCaller();
732 if (engine.startEngine("")) {
733 String response = engine.decide(sw.toString(), "xml");
734 FileWriter writer = new FileWriter(output.resolve("response" + count + ".xml").toFile());
735 writer.write(response);
737 logger.info("Response received: \n" + response);
740 } catch (Exception e) {
741 logger.error("Exception Occured"+e);
745 } while (hasNextRequest());
747 logger.info("# of requests generated: " + count);
750 private boolean hasNextRequest() {
751 int i = meta.size() - 1;
752 Object[] record = meta.get(i);
754 @SuppressWarnings("unchecked")
755 Iterator<AttributeValue<?>> iterator = (Iterator<AttributeValue<?>>) record[4];
756 if (iterator.hasNext()) {
757 record[3] = iterator.next();
759 return recycleAttributeValue(i);
765 @SuppressWarnings("unchecked")
766 private boolean recycleAttributeValue(int position) {
772 Object[] record = meta.get(position);
773 Set<AttributeValue<?>> attributeValueSet = (Set<AttributeValue<?>>) record[5];
774 Iterator<AttributeValue<?>> newIt = attributeValueSet.iterator();
776 record[3] = newIt.next();
777 int i = position - 1;
778 Object[] previous = meta.get(i);
779 Iterator<AttributeValue<?>> preIt = (Iterator<AttributeValue<?>>) previous[4];
780 if (preIt.hasNext()) {
781 previous[3] = preIt.next();
783 rc = recycleAttributeValue(i);