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