Updates to support fixed guard policy types
[policy/xacml-pdp.git] / applications / guard / src / main / java / org / onap / policy / xacml / pdp / application / guard / GuardTranslator.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2020 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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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  *
19  * SPDX-License-Identifier: Apache-2.0
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.xacml.pdp.application.guard;
24
25 import com.att.research.xacml.api.DataTypeException;
26 import com.att.research.xacml.api.Decision;
27 import com.att.research.xacml.api.Identifier;
28 import com.att.research.xacml.api.Request;
29 import com.att.research.xacml.api.Response;
30 import com.att.research.xacml.api.Result;
31 import com.att.research.xacml.api.XACML3;
32 import com.att.research.xacml.std.annotations.RequestParser;
33 import java.util.Collection;
34 import java.util.Map;
35 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AllOfType;
36 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
37 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ApplyType;
38 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeDesignatorType;
39 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeValueType;
40 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ConditionType;
41 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
42 import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType;
43 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObjectFactory;
44 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
45 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
46 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
47 import org.onap.policy.models.decisions.concepts.DecisionRequest;
48 import org.onap.policy.models.decisions.concepts.DecisionResponse;
49 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
50 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
51 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
52 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslator;
53 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
54 import org.onap.policy.pdp.xacml.application.common.operationshistory.CountRecentOperationsPip;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 public class GuardTranslator implements ToscaPolicyTranslator {
59     private static final Logger LOGGER = LoggerFactory.getLogger(GuardTranslator.class);
60
61     //
62     // common guard property fields
63     //
64     public static final String FIELD_ACTOR = "actor";
65     public static final String FIELD_OPERATION = "operation";
66     public static final String FIELD_CONTROLLOOP = "id";
67     public static final String FIELD_TIMERANGE = "timeRange";
68
69     //
70     // frequency property fields
71     //
72     public static final String FIELD_TIMEWINDOW = "timeWindow";
73     public static final String FIELD_TIMEUNITS = "timeUnits";
74     public static final String FIELD_LIMIT = "limit";
75
76     //
77     // minmax property fields
78     //
79     public static final String FIELD_TARGET = "target";
80     public static final String FIELD_MIN = "min";
81     public static final String FIELD_MAX = "max";
82
83     //
84     // blacklist property fields
85     //
86     public static final String FIELD_BLACKLIST = "blacklist";
87
88     public static final String POLICYTYPE_FREQUENCY = "onap.policies.controlloop.guard.common.FrequencyLimiter";
89     public static final String POLICYTYPE_MINMAX = "onap.policies.controlloop.guard.common.MinMax";
90     public static final String POLICYTYPE_BLACKLIST = "onap.policies.controlloop.guard.common.Blacklist";
91
92     public GuardTranslator() {
93         super();
94     }
95
96     /**
97      * Convert the policy.
98      */
99     public PolicyType convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
100         //
101         // Policy name should be at the root
102         //
103         String policyName = toscaPolicy.getMetadata().get("policy-id");
104         //
105         // Set it as the policy ID
106         //
107         PolicyType newPolicyType = new PolicyType();
108         newPolicyType.setPolicyId(policyName);
109         //
110         // Optional description
111         //
112         newPolicyType.setDescription(toscaPolicy.getDescription());
113         //
114         // There should be a metadata section
115         //
116         this.fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
117         //
118         // Generate the TargetType - add true if not blacklist
119         //
120         newPolicyType.setTarget(this.generateTargetType(toscaPolicy.getProperties(),
121                 ! POLICYTYPE_BLACKLIST.equals(toscaPolicy.getType())));
122         //
123         // Add specific's per guard policy type
124         //
125         if (POLICYTYPE_FREQUENCY.equals(toscaPolicy.getType())) {
126             newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_DENY_UNLESS_PERMIT.stringValue());
127             generateFrequencyRules(toscaPolicy, policyName, newPolicyType);
128         } else if (POLICYTYPE_MINMAX.equals(toscaPolicy.getType())) {
129             newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_DENY_UNLESS_PERMIT.stringValue());
130             generateMinMaxRules(toscaPolicy, policyName, newPolicyType);
131         } else if (POLICYTYPE_BLACKLIST.equals(toscaPolicy.getType())) {
132             newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_PERMIT_UNLESS_DENY.stringValue());
133             generateBlacklistRules(toscaPolicy, policyName, newPolicyType);
134         } else {
135             throw new ToscaPolicyConversionException("Unknown guard policy type " + toscaPolicy.getType());
136         }
137         return newPolicyType;
138     }
139
140     /**
141      * Convert Request.
142      */
143     public Request convertRequest(DecisionRequest request) {
144         LOGGER.info("Converting Request {}", request);
145         try {
146             return RequestParser.parseRequest(GuardPolicyRequest.createInstance(request));
147         } catch (IllegalArgumentException | IllegalAccessException | DataTypeException e) {
148             LOGGER.error("Failed to convert DecisionRequest: {}", e);
149         }
150         //
151         // TODO throw exception
152         //
153         return null;
154     }
155
156     /**
157      * Convert response.
158      */
159     public DecisionResponse convertResponse(Response xacmlResponse) {
160         LOGGER.info("Converting Response {}", xacmlResponse);
161         DecisionResponse decisionResponse = new DecisionResponse();
162         //
163         // Iterate through all the results
164         //
165         for (Result xacmlResult : xacmlResponse.getResults()) {
166             //
167             // Check the result
168             //
169             if (xacmlResult.getDecision() == Decision.PERMIT) {
170                 //
171                 // Just simply return a Permit response
172                 //
173                 decisionResponse.setStatus(Decision.PERMIT.toString());
174             } else if (xacmlResult.getDecision() == Decision.DENY) {
175                 //
176                 // Just simply return a Deny response
177                 //
178                 decisionResponse.setStatus(Decision.DENY.toString());
179             } else {
180                 //
181                 // There is no guard policy, so we return a permit
182                 //
183                 decisionResponse.setStatus(Decision.PERMIT.toString());
184             }
185         }
186
187         return decisionResponse;
188     }
189
190     /**
191      * From the TOSCA metadata section, pull in values that are needed into the XACML policy.
192      *
193      * @param policy Policy Object to store the metadata
194      * @param map The Metadata TOSCA Map
195      * @return Same Policy Object
196      */
197     protected PolicyType fillMetadataSection(PolicyType policy, Map<String, String> map) {
198         //
199         // NOTE: The models code ensures the metadata section ALWAYS exists
200         //
201         //
202         // Add in the Policy Version
203         //
204         policy.setVersion(map.get("policy-version"));
205         return policy;
206     }
207
208     /**
209      * Generate the targettype for the policy. Optional to add MatchType for the target. eg. the
210      * blacklist policy type uses the target in a different manner.
211      *
212      * @param properties TOSCA properties object
213      * @param addTargets true to go ahead and add target to the match list.
214      * @return TargetType object
215      * @throws ToscaPolicyConversionException if there is a missing property
216      */
217     protected TargetType generateTargetType(Map<String, Object> properties, boolean addTargets)
218             throws ToscaPolicyConversionException {
219         //
220         // Go through potential properties
221         //
222         AllOfType allOf = new AllOfType();
223         if (properties.containsKey(FIELD_ACTOR)) {
224             addMatch(allOf, properties.get(FIELD_ACTOR), ToscaDictionary.ID_RESOURCE_GUARD_ACTOR);
225         }
226         if (properties.containsKey(FIELD_OPERATION)) {
227             addMatch(allOf, properties.get(FIELD_OPERATION), ToscaDictionary.ID_RESOURCE_GUARD_RECIPE);
228         }
229         if (addTargets && properties.containsKey(FIELD_TARGET)) {
230             addMatch(allOf, properties.get(FIELD_TARGET), ToscaDictionary.ID_RESOURCE_GUARD_TARGETID);
231         }
232         if (properties.containsKey(FIELD_CONTROLLOOP)) {
233             addMatch(allOf, properties.get(FIELD_CONTROLLOOP), ToscaDictionary.ID_RESOURCE_GUARD_CLNAME);
234         }
235         if (properties.containsKey(FIELD_TIMERANGE)) {
236             addTimeRangeMatch(allOf, properties.get(FIELD_TIMERANGE));
237         }
238         //
239         // Create target
240         //
241         TargetType target = new TargetType();
242         AnyOfType anyOf = new AnyOfType();
243         anyOf.getAllOf().add(allOf);
244         target.getAnyOf().add(anyOf);
245         return target;
246     }
247
248     @SuppressWarnings("unchecked")
249     protected AllOfType addMatch(AllOfType allOf, Object value, Identifier attributeId) {
250         if (value instanceof String) {
251             if (".*".equals(value.toString())) {
252                 //
253                 // There's no point to even have a match
254                 //
255                 return allOf;
256             } else {
257                 //
258                 // Exact match
259                 //
260                 MatchType match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
261                     XACML3.ID_FUNCTION_STRING_EQUAL,
262                     value,
263                     XACML3.ID_DATATYPE_STRING,
264                     attributeId,
265                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
266
267                 allOf.getMatch().add(match);
268             }
269             return allOf;
270         }
271         if (value instanceof Collection) {
272             ((Collection<String>) value).forEach(val -> {
273                 MatchType match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
274                         XACML3.ID_FUNCTION_STRING_EQUAL,
275                         val,
276                         XACML3.ID_DATATYPE_STRING,
277                         attributeId,
278                         XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
279
280                 allOf.getMatch().add(match);
281             });
282         }
283         return allOf;
284     }
285
286     @SuppressWarnings("rawtypes")
287     protected void addTimeRangeMatch(AllOfType allOf, Object timeRange)
288             throws ToscaPolicyConversionException {
289         if (! (timeRange instanceof Map)) {
290             throw new ToscaPolicyConversionException("timeRange is not a map object " + timeRange.getClass());
291         }
292
293         MatchType matchStart = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
294                 XACML3.ID_FUNCTION_TIME_GREATER_THAN_OR_EQUAL,
295                 ((Map) timeRange).get("start_time").toString(),
296                 XACML3.ID_DATATYPE_TIME,
297                 XACML3.ID_ENVIRONMENT_CURRENT_TIME,
298                 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
299
300         allOf.getMatch().add(matchStart);
301
302         MatchType matchEnd = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
303                 XACML3.ID_FUNCTION_TIME_LESS_THAN_OR_EQUAL,
304                 ((Map) timeRange).get("end_time").toString(),
305                 XACML3.ID_DATATYPE_TIME,
306                 XACML3.ID_ENVIRONMENT_CURRENT_TIME,
307                 XACML3.ID_ATTRIBUTE_CATEGORY_ENVIRONMENT);
308
309         allOf.getMatch().add(matchEnd);
310     }
311
312     protected void generateFrequencyRules(ToscaPolicy toscaPolicy, String policyName, PolicyType newPolicyType)
313             throws ToscaPolicyConversionException {
314         //
315         // We must have the limit
316         //
317         if (! toscaPolicy.getProperties().containsKey(FIELD_LIMIT)) {
318             throw new ToscaPolicyConversionException("Missing property limit");
319         }
320         //
321         // See if its possible to generate a count
322         //
323         Integer limit = ToscaPolicyTranslatorUtils.parseInteger(
324                 toscaPolicy.getProperties().get(FIELD_LIMIT).toString());
325         if (limit == null) {
326             throw new ToscaPolicyConversionException("Missing limit value");
327         }
328         String timeWindow = null;
329         if (toscaPolicy.getProperties().containsKey(FIELD_TIMEWINDOW)) {
330             Integer intTimeWindow = ToscaPolicyTranslatorUtils.parseInteger(
331                     toscaPolicy.getProperties().get(FIELD_TIMEWINDOW).toString());
332             if (intTimeWindow == null) {
333                 throw new ToscaPolicyConversionException("timeWindow is not an integer");
334             }
335             timeWindow = intTimeWindow.toString();
336         }
337         String timeUnits = null;
338         if (toscaPolicy.getProperties().containsKey(FIELD_TIMEUNITS)) {
339             timeUnits = toscaPolicy.getProperties().get(FIELD_TIMEUNITS).toString();
340         }
341         //
342         // Generate a count
343         //
344         final ApplyType countCheck = generateCountCheck(limit, timeWindow, timeUnits);
345         //
346         // Create our condition
347         //
348         final ConditionType condition = new ConditionType();
349         condition.setExpression(new ObjectFactory().createApply(countCheck));
350
351         //
352         // Now we can create our rule
353         //
354         RuleType frequencyRule = new RuleType();
355         frequencyRule.setDescription("Frequency limit permit rule");
356         frequencyRule.setRuleId(policyName + ":frequency");
357         frequencyRule.setEffect(EffectType.PERMIT);
358         frequencyRule.setTarget(new TargetType());
359         //
360         // Add the condition
361         //
362         frequencyRule.setCondition(condition);
363         //
364         // Add the rule to the policy
365         //
366         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(frequencyRule);
367     }
368
369     protected ApplyType generateCountCheck(Integer limit, String timeWindow, String timeUnits) {
370         AttributeDesignatorType designator = new AttributeDesignatorType();
371         designator.setAttributeId(ToscaDictionary.ID_RESOURCE_GUARD_OPERATIONCOUNT.stringValue());
372         designator.setCategory(XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE.stringValue());
373         designator.setDataType(XACML3.ID_DATATYPE_INTEGER.stringValue());
374         //
375         // Setup issuer - used by the operations PIP to determine
376         // how to do the database query.
377         //
378         String issuer = ToscaDictionary.GUARD_ISSUER_PREFIX
379             + CountRecentOperationsPip.ISSUER_NAME
380             + ":tw:" + timeWindow + ":" + timeUnits;
381         designator.setIssuer(issuer);
382
383         AttributeValueType valueLimit = new AttributeValueType();
384         valueLimit.setDataType(XACML3.ID_DATATYPE_INTEGER.stringValue());
385         //
386         // Yes really use toString(), the marshaller will
387         // throw an exception if this is an integer object
388         // and not a string.
389         //
390         valueLimit.getContent().add(limit.toString());
391
392         ObjectFactory factory = new ObjectFactory();
393
394         ApplyType applyOneAndOnly = new ApplyType();
395         applyOneAndOnly.setDescription("Unbag the limit");
396         applyOneAndOnly.setFunctionId(XACML3.ID_FUNCTION_INTEGER_ONE_AND_ONLY.stringValue());
397         applyOneAndOnly.getExpression().add(factory.createAttributeDesignator(designator));
398
399         ApplyType applyLessThan = new ApplyType();
400         applyLessThan.setDescription("return true if current count is less than.");
401         applyLessThan.setFunctionId(XACML3.ID_FUNCTION_INTEGER_LESS_THAN.stringValue());
402         applyLessThan.getExpression().add(factory.createApply(applyOneAndOnly));
403         applyLessThan.getExpression().add(factory.createAttributeValue(valueLimit));
404
405         return applyLessThan;
406     }
407
408     protected void generateMinMaxRules(ToscaPolicy toscaPolicy, String policyName, PolicyType newPolicyType)
409             throws ToscaPolicyConversionException {
410         //
411         // Add the target
412         //
413         if (! toscaPolicy.getProperties().containsKey(FIELD_TARGET)) {
414             throw new ToscaPolicyConversionException("Missing target field in minmax policy");
415         }
416         MatchType matchTarget = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
417                 XACML3.ID_FUNCTION_STRING_EQUAL,
418                 toscaPolicy.getProperties().get(FIELD_TARGET).toString(),
419                 XACML3.ID_DATATYPE_STRING,
420                 ToscaDictionary.ID_RESOURCE_GUARD_TARGETID,
421                 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
422         //
423         // For the min, if the # of instances is less than the minimum
424         // then allow the scale.
425         //
426         Integer min = null;
427         if (toscaPolicy.getProperties().containsKey(FIELD_MIN)) {
428             min = ToscaPolicyTranslatorUtils.parseInteger(toscaPolicy.getProperties().get(FIELD_MIN).toString());
429             MatchType matchMin = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
430                     XACML3.ID_FUNCTION_INTEGER_GREATER_THAN,
431                     min.toString(),
432                     XACML3.ID_DATATYPE_INTEGER,
433                     ToscaDictionary.ID_RESOURCE_GUARD_VFCOUNT,
434                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
435
436             newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(
437                     generateMinMaxRule(matchTarget, matchMin, policyName + ":minrule", "check minimum"));
438         }
439         Integer max = null;
440         if (toscaPolicy.getProperties().containsKey(FIELD_MAX)) {
441             max = ToscaPolicyTranslatorUtils.parseInteger(toscaPolicy.getProperties().get(FIELD_MAX).toString());
442             MatchType matchMax = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
443                     XACML3.ID_FUNCTION_INTEGER_GREATER_THAN,
444                     max.toString(),
445                     XACML3.ID_DATATYPE_INTEGER,
446                     ToscaDictionary.ID_RESOURCE_GUARD_VFCOUNT,
447                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
448
449             newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(
450                     generateMinMaxRule(matchTarget, matchMax, policyName + ":maxrule", "check maximum"));
451         }
452         //
453         // Do we have at least a min or max?
454         //
455         if (min == null && max == null) {
456             throw new ToscaPolicyConversionException("Missing min or max field in minmax policy");
457         }
458     }
459
460     protected RuleType generateMinMaxRule(MatchType matchTarget, MatchType matchMinOrMax, String ruleId, String desc) {
461         AllOfType allOf = new AllOfType();
462         allOf.getMatch().add(matchTarget);
463         allOf.getMatch().add(matchMinOrMax);
464         AnyOfType anyOf = new AnyOfType();
465         anyOf.getAllOf().add(allOf);
466         TargetType target = new TargetType();
467         target.getAnyOf().add(anyOf);
468         RuleType minMaxRule = new RuleType();
469         minMaxRule.setEffect(EffectType.PERMIT);
470         minMaxRule.setDescription(desc);
471         minMaxRule.setRuleId(ruleId);
472         minMaxRule.setTarget(target);
473         return minMaxRule;
474     }
475
476     protected void generateBlacklistRules(ToscaPolicy toscaPolicy, String policyName, PolicyType newPolicyType)
477             throws ToscaPolicyConversionException {
478         //
479         // Validate the blacklist exists
480         //
481         if (! toscaPolicy.getProperties().containsKey(FIELD_BLACKLIST)) {
482             throw new ToscaPolicyConversionException("Missing blacklist field");
483         }
484         final AllOfType allOf = new AllOfType();
485         this.addMatch(allOf, toscaPolicy.getProperties().get(FIELD_BLACKLIST),
486                 ToscaDictionary.ID_RESOURCE_GUARD_TARGETID);
487         //
488         // Create our rule and add the target
489         //
490         RuleType blacklistRule = new RuleType();
491         blacklistRule.setEffect(EffectType.DENY);
492         blacklistRule.setDescription("blacklist the entities");
493         blacklistRule.setRuleId(policyName + ":blacklist");
494         TargetType target = new TargetType();
495         AnyOfType anyOf = new AnyOfType();
496         anyOf.getAllOf().add(allOf);
497         target.getAnyOf().add(anyOf);
498         blacklistRule.setTarget(target);
499         //
500         // Add the rule to the policy
501         //
502         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(blacklistRule);
503     }
504
505 }