Update Policy committers in policy/xacml-pdp
[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-2021 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2020, 2023 Nordix Foundation.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  *
20  * SPDX-License-Identifier: Apache-2.0
21  * ============LICENSE_END=========================================================
22  */
23
24 package org.onap.policy.xacml.pdp.application.guard;
25
26 import com.att.research.xacml.api.DataTypeException;
27 import com.att.research.xacml.api.Decision;
28 import com.att.research.xacml.api.Identifier;
29 import com.att.research.xacml.api.Request;
30 import com.att.research.xacml.api.Response;
31 import com.att.research.xacml.api.Result;
32 import com.att.research.xacml.api.XACML3;
33 import com.att.research.xacml.std.IdentifierImpl;
34 import com.att.research.xacml.std.annotations.RequestParser;
35 import com.google.gson.annotations.SerializedName;
36 import java.time.OffsetDateTime;
37 import java.time.OffsetTime;
38 import java.util.Collection;
39 import java.util.List;
40 import java.util.Map;
41 import lombok.Getter;
42 import lombok.NoArgsConstructor;
43 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AllOfType;
44 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
45 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ApplyType;
46 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeDesignatorType;
47 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeValueType;
48 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ConditionType;
49 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
50 import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType;
51 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObjectFactory;
52 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
53 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
54 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
55 import oasis.names.tc.xacml._3_0.core.schema.wd_17.VariableDefinitionType;
56 import oasis.names.tc.xacml._3_0.core.schema.wd_17.VariableReferenceType;
57 import org.onap.policy.common.parameters.annotations.NotBlank;
58 import org.onap.policy.common.parameters.annotations.NotNull;
59 import org.onap.policy.common.parameters.annotations.Valid;
60 import org.onap.policy.models.decisions.concepts.DecisionRequest;
61 import org.onap.policy.models.decisions.concepts.DecisionResponse;
62 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
63 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
64 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
65 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslator;
66 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
67 import org.onap.policy.pdp.xacml.application.common.operationshistory.CountRecentOperationsPip;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71 @NoArgsConstructor
72 public class GuardTranslator implements ToscaPolicyTranslator {
73     private static final Logger LOGGER = LoggerFactory.getLogger(GuardTranslator.class);
74
75     //
76     // common guard property fields
77     //
78     public static final String FIELD_ACTOR = "actor";
79     public static final String FIELD_OPERATION = "operation";
80     public static final String FIELD_CONTROLLOOP = "id";
81     public static final String FIELD_TIMERANGE = "timeRange";
82
83     //
84     // frequency property fields
85     //
86     public static final String FIELD_TIMEWINDOW = "timeWindow";
87     public static final String FIELD_TIMEUNITS = "timeUnits";
88     public static final String FIELD_LIMIT = "limit";
89
90     //
91     // minmax property fields
92     //
93     public static final String FIELD_TARGET = "target";
94     public static final String FIELD_MIN = "min";
95     public static final String FIELD_MAX = "max";
96
97     //
98     // blacklist property fields
99     //
100     public static final String FIELD_BLACKLIST = "blacklist";
101
102     //
103     // filter property fields
104     //
105     public static final String FIELD_FILTER_WHITELIST = "whitelist";
106     public static final String FIELD_FILTER_ALGORITHM = "algorithm";
107     public static final String FIELD_FILTER_FILTERS = "filters";
108     public static final String FIELD_FILTER_FIELD = "field";
109     public static final String FIELD_FILTER_FUNCTION = "function";
110     public static final String FIELD_FILTER_FILTER = "filter";
111     public static final String FIELD_FILTER_BLACKLIST = "blacklist";
112
113     public static final String POLICYTYPE_FREQUENCY = "onap.policies.controlloop.guard.common.FrequencyLimiter";
114     public static final String POLICYTYPE_MINMAX = "onap.policies.controlloop.guard.common.MinMax";
115     public static final String POLICYTYPE_BLACKLIST = "onap.policies.controlloop.guard.common.Blacklist";
116     public static final String POLICYTYPE_FILTER = "onap.policies.controlloop.guard.common.Filter";
117
118     //
119     // Variable definitions
120     //
121     private static final String VARIABLE_TIMEINRANGE = "timeInRange";
122
123
124     /**
125      * Convert the policy.
126      */
127     @Override
128     public Object convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
129         //
130         // Policy name should be at the root
131         //
132         String policyName = String.valueOf(toscaPolicy.getMetadata().get("policy-id"));
133         //
134         // Set it as the policy ID
135         //
136         var newPolicyType = new PolicyType();
137         newPolicyType.setPolicyId(policyName);
138         //
139         // Optional description
140         //
141         newPolicyType.setDescription(toscaPolicy.getDescription());
142         //
143         // There should be a metadata section
144         //
145         this.fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
146         //
147         // There should be properties metadata section
148         //
149         if (toscaPolicy.getProperties() == null) {
150             throw new ToscaPolicyConversionException("no properties specified on guard policy: " + policyName);
151         }
152         //
153         // Generate the TargetType - add true if not blacklist
154         //
155         newPolicyType.setTarget(this.generateTargetType(toscaPolicy.getProperties(),
156             !POLICYTYPE_BLACKLIST.equals(toscaPolicy.getType())));
157         //
158         // Add specific's per guard policy type
159         //
160         if (POLICYTYPE_FREQUENCY.equals(toscaPolicy.getType())) {
161             newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_DENY_UNLESS_PERMIT.stringValue());
162             generateFrequencyRules(toscaPolicy, policyName, newPolicyType);
163         } else if (POLICYTYPE_MINMAX.equals(toscaPolicy.getType())) {
164             newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_DENY_UNLESS_PERMIT.stringValue());
165             generateMinMaxRules(toscaPolicy, policyName, newPolicyType);
166         } else if (POLICYTYPE_BLACKLIST.equals(toscaPolicy.getType())) {
167             newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_PERMIT_UNLESS_DENY.stringValue());
168             generateBlacklistRules(toscaPolicy, policyName, newPolicyType);
169         } else if (POLICYTYPE_FILTER.equals(toscaPolicy.getType())) {
170             newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_PERMIT_UNLESS_DENY.stringValue());
171             generateFilterRules(toscaPolicy, policyName, newPolicyType);
172         } else {
173             throw new ToscaPolicyConversionException("Unknown guard policy type " + toscaPolicy.getType());
174         }
175         //
176         // Add in our variable definition
177         //
178         VariableReferenceType variable = this.createTimeRangeVariable(toscaPolicy.getProperties(), newPolicyType);
179         if (variable != null) {
180             //
181             // Update all the rules to have conditions for this variable
182             //
183             this.addVariableToConditionTypes(variable, newPolicyType);
184         }
185         return newPolicyType;
186     }
187
188     /**
189      * This method iterates through all the existing rules, adding in a conditionType that will test
190      * whether the Variable is true or false. Any existing ConditionType will be updated to AND with the
191      * Variable.
192      *
193      * @param variable      VariableDefinitionType to add
194      * @param newPolicyType PolicyType that will be updated
195      */
196     protected void addVariableToConditionTypes(VariableReferenceType variable,
197                                                PolicyType newPolicyType) {
198         //
199         // Iterate through the rules
200         //
201         for (Object objectType : newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition()) {
202             if (objectType instanceof RuleType rule) {
203                 if (rule.getCondition() == null) {
204                     //
205                     // No condition already, just create and add a new one
206                     //
207                     var condition = new ConditionType();
208                     condition.setExpression(new ObjectFactory().createVariableReference(variable));
209                     rule.setCondition(condition);
210                 } else {
211                     //
212                     // Need to create a new ConditionType that treats all the expressions as an AND
213                     // with the Variable.
214                     //
215                     rule.setCondition(ToscaPolicyTranslatorUtils.addVariableToCondition(rule.getCondition(), variable,
216                         XACML3.ID_FUNCTION_AND));
217                 }
218             }
219         }
220     }
221
222     /**
223      * Convert Request.
224      */
225     @Override
226     public Request convertRequest(DecisionRequest request) throws ToscaPolicyConversionException {
227         LOGGER.info("Converting Request {}", request);
228         try {
229             return RequestParser.parseRequest(GuardPolicyRequest.createInstance(request));
230         } catch (IllegalArgumentException | IllegalAccessException | DataTypeException e) {
231             throw new ToscaPolicyConversionException("Failed to convert DecisionRequest", e);
232         }
233     }
234
235     /**
236      * Convert response.
237      */
238     @Override
239     public DecisionResponse convertResponse(Response xacmlResponse) {
240         LOGGER.info("Converting Response {}", xacmlResponse);
241         var decisionResponse = new DecisionResponse();
242         //
243         // Iterate through all the results
244         //
245         for (Result xacmlResult : xacmlResponse.getResults()) {
246             //
247             // Check the result
248             //
249             if (xacmlResult.getDecision() == Decision.PERMIT) {
250                 //
251                 // Just simply return a Permit response
252                 //
253                 decisionResponse.setStatus(Decision.PERMIT.toString());
254             } else if (xacmlResult.getDecision() == Decision.DENY) {
255                 //
256                 // Just simply return a Deny response
257                 //
258                 decisionResponse.setStatus(Decision.DENY.toString());
259             } else {
260                 //
261                 // There is no guard policy, so we return a permit
262                 //
263                 decisionResponse.setStatus(Decision.PERMIT.toString());
264             }
265         }
266
267         return decisionResponse;
268     }
269
270     /**
271      * From the TOSCA metadata section, pull in values that are needed into the XACML policy.
272      *
273      * @param policy Policy Object to store the metadata
274      * @param map    The Metadata TOSCA Map
275      * @return Same Policy Object
276      */
277     protected PolicyType fillMetadataSection(PolicyType policy, Map<String, Object> map) {
278         //
279         // NOTE: The models code ensures the metadata section ALWAYS exists
280         //
281         //
282         // Add in the Policy Version
283         //
284         policy.setVersion(String.valueOf(map.get("policy-version")));
285         return policy;
286     }
287
288     /**
289      * Generate the targetType for the policy. Optional to add MatchType for the target. eg. the
290      * blacklist policy type uses the target in a different manner.
291      *
292      * @param properties TOSCA properties object
293      * @param addTargets true to go ahead and add target to the match list.
294      * @return TargetType object
295      * @throws ToscaPolicyConversionException if there is a missing property
296      */
297     protected TargetType generateTargetType(Map<String, Object> properties, boolean addTargets)
298         throws ToscaPolicyConversionException {
299         //
300         // Decode the definition from the policy's properties
301         //
302         TargetTypeDefinition targetTypeDef =
303             ToscaPolicyTranslatorUtils.decodeProperties(properties, TargetTypeDefinition.class);
304         //
305         // Go through potential properties
306         //
307         var allOf = new AllOfType();
308         if (targetTypeDef.getActor() != null) {
309             addMatch(allOf, targetTypeDef.getActor(), ToscaDictionary.ID_RESOURCE_GUARD_ACTOR);
310         }
311         if (targetTypeDef.getOperation() != null) {
312             addMatch(allOf, targetTypeDef.getOperation(), ToscaDictionary.ID_RESOURCE_GUARD_RECIPE);
313         }
314         if (addTargets && targetTypeDef.getTarget() != null) {
315             addMatch(allOf, targetTypeDef.getTarget(), ToscaDictionary.ID_RESOURCE_GUARD_TARGETID);
316         }
317         if (targetTypeDef.getId() != null) {
318             addMatch(allOf, targetTypeDef.getId(), ToscaDictionary.ID_RESOURCE_GUARD_CLNAME);
319         }
320         //
321         // Create target
322         //
323         var target = new TargetType();
324         var anyOf = new AnyOfType();
325         anyOf.getAllOf().add(allOf);
326         target.getAnyOf().add(anyOf);
327         return target;
328     }
329
330     @SuppressWarnings("unchecked")
331     protected AllOfType addMatch(AllOfType allOf, Object value, Identifier attributeId) {
332         if (value instanceof String) {
333             if (".*".equals(value.toString())) {
334                 //
335                 // There's no point to even have a match
336                 //
337                 return allOf;
338             } else {
339                 //
340                 // Exact match
341                 //
342                 var match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
343                     XACML3.ID_FUNCTION_STRING_EQUAL,
344                     value,
345                     XACML3.ID_DATATYPE_STRING,
346                     attributeId,
347                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
348
349                 allOf.getMatch().add(match);
350             }
351             return allOf;
352         }
353         if (value instanceof Collection) {
354             ((Collection<String>) value).forEach(val -> {
355                 var match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
356                     XACML3.ID_FUNCTION_STRING_EQUAL,
357                     val,
358                     XACML3.ID_DATATYPE_STRING,
359                     attributeId,
360                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
361
362                 allOf.getMatch().add(match);
363             });
364         }
365         return allOf;
366     }
367
368     protected void addTimeRangeMatch(AllOfType allOf, TimeRange timeRange) {
369
370         var matchStart = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
371             XACML3.ID_FUNCTION_TIME_GREATER_THAN_OR_EQUAL,
372             timeRange.getStartTime(),
373             XACML3.ID_DATATYPE_TIME,
374             XACML3.ID_ENVIRONMENT_CURRENT_TIME,
375             XACML3.ID_ATTRIBUTE_CATEGORY_ENVIRONMENT);
376
377         allOf.getMatch().add(matchStart);
378
379         var matchEnd = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
380             XACML3.ID_FUNCTION_TIME_LESS_THAN_OR_EQUAL,
381             timeRange.getEndTime(),
382             XACML3.ID_DATATYPE_TIME,
383             XACML3.ID_ENVIRONMENT_CURRENT_TIME,
384             XACML3.ID_ATTRIBUTE_CATEGORY_ENVIRONMENT);
385
386         allOf.getMatch().add(matchEnd);
387     }
388
389     protected VariableReferenceType createTimeRangeVariable(Map<String, Object> properties, PolicyType newPolicyType)
390         throws ToscaPolicyConversionException {
391         //
392         // Decode the definition from the policy's properties
393         //
394         TimeRangeDefinition timeRangeDef =
395             ToscaPolicyTranslatorUtils.decodeProperties(properties, TimeRangeDefinition.class);
396         TimeRange timeRange = timeRangeDef.getTimeRange();
397         if (timeRange == null) {
398             return null;
399         }
400         //
401         // Should also be parseable as an ISO8601 timestamp
402         //
403         var startTimeObject = parseTimestamp(timeRange.getStartTime());
404         var endTimeObject = parseTimestamp(timeRange.getEndTime());
405         //
406         // They should be the same object types. We cannot establish a range
407         // between an OffsetDateTime and an OffsetTime
408         //
409         if (!startTimeObject.getClass().equals(endTimeObject.getClass())) {
410             throw new ToscaPolicyConversionException("start_time and end_time class types do not match");
411         }
412         //
413         // Create the inner timeInRange ApplyType
414         //
415         ApplyType timeInRange = ToscaPolicyTranslatorUtils.generateTimeInRange(timeRange.getStartTime(),
416             timeRange.getEndTime(), true);
417         var variable = new VariableDefinitionType();
418         variable.setVariableId(VARIABLE_TIMEINRANGE);
419         variable.setExpression(new ObjectFactory().createApply(timeInRange));
420         //
421         // Add it to the policy
422         //
423         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(variable);
424         //
425         // Create and return the reference to the variable
426         //
427         var reference = new VariableReferenceType();
428         reference.setVariableId(variable.getVariableId());
429         return reference;
430     }
431
432     protected Object parseTimestamp(String string) throws ToscaPolicyConversionException {
433         //
434         // First see if it is a full datetime object
435         //
436         try {
437             return OffsetDateTime.parse(string);
438         } catch (Exception e) {
439             LOGGER.warn("timestamp {} could not be parsed. This may not be an error.", string, e);
440         }
441         //
442         // May only be a time object
443         //
444         try {
445             return OffsetTime.parse(string);
446         } catch (Exception e) {
447             throw new ToscaPolicyConversionException("timestamp " + string + " could not be parsed ", e);
448         }
449     }
450
451     protected void generateFrequencyRules(ToscaPolicy toscaPolicy, String policyName, PolicyType newPolicyType)
452         throws ToscaPolicyConversionException {
453         //
454         // Decode the definition from the policy's properties
455         //
456         FrequencyDefinition frequencyDef = ToscaPolicyTranslatorUtils.decodeProperties(toscaPolicy.getProperties(),
457             FrequencyDefinition.class);
458         //
459         // See if its possible to generate a count
460         //
461         String timeWindow = null;
462         if (frequencyDef.getTimeWindow() != null) {
463             timeWindow = frequencyDef.getTimeWindow().toString();
464         }
465         //
466         // Generate a count
467         //
468         final ApplyType countCheck =
469             generateCountCheck(frequencyDef.getLimit(), timeWindow, frequencyDef.getTimeUnits());
470         //
471         // Create our condition
472         //
473         final var condition = new ConditionType();
474         condition.setExpression(new ObjectFactory().createApply(countCheck));
475
476         //
477         // Now we can create our rule
478         //
479         var frequencyRule = new RuleType();
480         frequencyRule.setDescription("Frequency limit permit rule");
481         frequencyRule.setRuleId(policyName + ":frequency");
482         frequencyRule.setEffect(EffectType.PERMIT);
483         frequencyRule.setTarget(new TargetType());
484         //
485         // Add the condition
486         //
487         frequencyRule.setCondition(condition);
488         //
489         // Add the rule to the policy
490         //
491         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(frequencyRule);
492     }
493
494     protected ApplyType generateCountCheck(Integer limit, String timeWindow, String timeUnits) {
495         var designator = new AttributeDesignatorType();
496         designator.setAttributeId(ToscaDictionary.ID_RESOURCE_GUARD_OPERATIONCOUNT.stringValue());
497         designator.setCategory(XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE.stringValue());
498         designator.setDataType(XACML3.ID_DATATYPE_INTEGER.stringValue());
499         //
500         // Setup issuer - used by the operations PIP to determine
501         // how to do the database query.
502         //
503         String issuer = ToscaDictionary.GUARD_ISSUER_PREFIX
504             + CountRecentOperationsPip.ISSUER_NAME
505             + ":tw:" + timeWindow + ":" + timeUnits;
506         designator.setIssuer(issuer);
507
508         var valueLimit = new AttributeValueType();
509         valueLimit.setDataType(XACML3.ID_DATATYPE_INTEGER.stringValue());
510         //
511         // Yes really use toString(), the marshaller will
512         // throw an exception if this is an integer object
513         // and not a string.
514         //
515         valueLimit.getContent().add(limit.toString());
516
517         var factory = new ObjectFactory();
518
519         var applyOneAndOnly = new ApplyType();
520         applyOneAndOnly.setDescription("Unbag the limit");
521         applyOneAndOnly.setFunctionId(XACML3.ID_FUNCTION_INTEGER_ONE_AND_ONLY.stringValue());
522         applyOneAndOnly.getExpression().add(factory.createAttributeDesignator(designator));
523
524         var applyLessThan = new ApplyType();
525         applyLessThan.setDescription("return true if current count is less than.");
526         applyLessThan.setFunctionId(XACML3.ID_FUNCTION_INTEGER_LESS_THAN.stringValue());
527         applyLessThan.getExpression().add(factory.createApply(applyOneAndOnly));
528         applyLessThan.getExpression().add(factory.createAttributeValue(valueLimit));
529
530         return applyLessThan;
531     }
532
533     protected void generateMinMaxRules(ToscaPolicy toscaPolicy, String policyName, PolicyType newPolicyType)
534         throws ToscaPolicyConversionException {
535         //
536         // Decode the definition from the policy's properties
537         //
538         MinMaxDefinition minMaxDef = ToscaPolicyTranslatorUtils.decodeProperties(toscaPolicy.getProperties(),
539             MinMaxDefinition.class);
540         //
541         // Add the target
542         //
543         var matchTarget = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
544             XACML3.ID_FUNCTION_STRING_EQUAL,
545             minMaxDef.getTarget(),
546             XACML3.ID_DATATYPE_STRING,
547             ToscaDictionary.ID_RESOURCE_GUARD_TARGETID,
548             XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
549         //
550         // For the min, if the # of instances is less than the minimum
551         // then allow the scale.
552         //
553         if (minMaxDef.getMin() != null) {
554             var matchMin = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
555                 XACML3.ID_FUNCTION_INTEGER_GREATER_THAN,
556                 minMaxDef.getMin().toString(),
557                 XACML3.ID_DATATYPE_INTEGER,
558                 ToscaDictionary.ID_RESOURCE_GUARD_VFCOUNT,
559                 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
560
561             newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(
562                 generateMinMaxRule(matchTarget, matchMin, policyName + ":minrule", "check minimum"));
563         }
564         if (minMaxDef.getMax() != null) {
565             var matchMax = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
566                 XACML3.ID_FUNCTION_INTEGER_GREATER_THAN,
567                 minMaxDef.getMax().toString(),
568                 XACML3.ID_DATATYPE_INTEGER,
569                 ToscaDictionary.ID_RESOURCE_GUARD_VFCOUNT,
570                 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
571
572             newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(
573                 generateMinMaxRule(matchTarget, matchMax, policyName + ":maxrule", "check maximum"));
574         }
575         //
576         // Do we have at least a min or max?
577         //
578         if (minMaxDef.getMin() == null && minMaxDef.getMax() == null) {
579             throw new ToscaPolicyConversionException("Missing min or max field in minmax policy");
580         }
581     }
582
583     protected RuleType generateMinMaxRule(MatchType matchTarget, MatchType matchMinOrMax, String ruleId, String desc) {
584         var allOf = new AllOfType();
585         allOf.getMatch().add(matchTarget);
586         allOf.getMatch().add(matchMinOrMax);
587         var anyOf = new AnyOfType();
588         anyOf.getAllOf().add(allOf);
589         var target = new TargetType();
590         target.getAnyOf().add(anyOf);
591         var minMaxRule = new RuleType();
592         minMaxRule.setEffect(EffectType.PERMIT);
593         minMaxRule.setDescription(desc);
594         minMaxRule.setRuleId(ruleId);
595         minMaxRule.setTarget(target);
596         return minMaxRule;
597     }
598
599     protected void generateBlacklistRules(ToscaPolicy toscaPolicy, String policyName, PolicyType newPolicyType)
600         throws ToscaPolicyConversionException {
601         //
602         // Decode the definition from the policy's properties
603         //
604         BlacklistDefinition blacklistDef = ToscaPolicyTranslatorUtils.decodeProperties(toscaPolicy.getProperties(),
605             BlacklistDefinition.class);
606         //
607         // Iterate the entries and create individual AnyOf so each entry is
608         // treated as an OR.
609         //
610         var target = new TargetType();
611         var anyOf = new AnyOfType();
612         for (Object blacklisted : blacklistDef.blacklist) {
613             var allOf = new AllOfType();
614             this.addMatch(allOf, blacklisted, ToscaDictionary.ID_RESOURCE_GUARD_TARGETID);
615             anyOf.getAllOf().add(allOf);
616         }
617         target.getAnyOf().add(anyOf);
618         //
619         // Create our rule and add the target
620         //
621         var blacklistRule = new RuleType();
622         blacklistRule.setEffect(EffectType.DENY);
623         blacklistRule.setDescription("blacklist the entities");
624         blacklistRule.setRuleId(policyName + ":blacklist");
625         blacklistRule.setTarget(target);
626         //
627         // Add the rule to the policy
628         //
629         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(blacklistRule);
630     }
631
632     protected void generateFilterRules(ToscaPolicy toscaPolicy, String policyName, PolicyType newPolicyType)
633         throws ToscaPolicyConversionException {
634         //
635         // Decode the definition from the policy's properties
636         //
637         FilterDefinition filterDef = ToscaPolicyTranslatorUtils.decodeProperties(toscaPolicy.getProperties(),
638             FilterDefinition.class);
639         //
640         // Set the combining algorithm
641         //
642         switch (filterDef.getAlgorithm()) {
643             case "whitelist-overrides":
644                 newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_PERMIT_OVERRIDES.stringValue());
645                 break;
646             case "blacklist-overrides":
647                 newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_DENY_OVERRIDES.stringValue());
648                 break;
649             default:
650                 throw new ToscaPolicyConversionException(
651                     "Unexpected value for algorithm, should be whitelist-overrides or blacklist-overrides");
652         }
653         //
654         // Iterate the filters
655         //
656         var ruleId = 1;
657         for (FilterAttribute filterAttributes : filterDef.filters) {
658             //
659             // Check fields requiring extra validation
660             //
661             String field = validateFilterPropertyField(filterAttributes.getField());
662             Identifier function = validateFilterPropertyFunction(filterAttributes.getFunction());
663             //
664             // Create our filter rule
665             //
666             RuleType filterRule = createFilterRule(policyName + ":rule" + ruleId++, field, filterAttributes.getFilter(),
667                 function, filterAttributes.getBlacklist());
668             //
669             // Add the rule to the policy
670             //
671             newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(filterRule);
672         }
673     }
674
675     protected String validateFilterPropertyField(String field)
676         throws ToscaPolicyConversionException {
677         String fieldLowerCase = field.toLowerCase();
678         return switch (fieldLowerCase) {
679             case "generic-vnf.vnf-name", "generic-vnf.vnf-id", "generic-vnf.vnf-type", "generic-vnf.nf-naming-code",
680                 "vserver.vserver-id", "cloud-region.cloud-region-id" -> fieldLowerCase;
681             default -> throw new ToscaPolicyConversionException("Unexpected value for field in filter");
682         };
683     }
684
685     protected Identifier validateFilterPropertyFunction(String function)
686         throws ToscaPolicyConversionException {
687         return switch (function.toLowerCase()) {
688             case "string-equal" -> XACML3.ID_FUNCTION_STRING_EQUAL;
689             case "string-equal-ignore-case" -> XACML3.ID_FUNCTION_STRING_EQUAL_IGNORE_CASE;
690             case "string-regexp-match" -> XACML3.ID_FUNCTION_STRING_REGEXP_MATCH;
691             case "string-contains" -> XACML3.ID_FUNCTION_STRING_CONTAINS;
692             case "string-greater-than" -> XACML3.ID_FUNCTION_STRING_GREATER_THAN;
693             case "string-greater-than-or-equal" -> XACML3.ID_FUNCTION_STRING_GREATER_THAN_OR_EQUAL;
694             case "string-less-than" -> XACML3.ID_FUNCTION_STRING_LESS_THAN;
695             case "string-less-than-or-equal" -> XACML3.ID_FUNCTION_STRING_LESS_THAN_OR_EQUAL;
696             case "string-starts-with" -> XACML3.ID_FUNCTION_STRING_STARTS_WITH;
697             case "string-ends-with" -> XACML3.ID_FUNCTION_STRING_ENDS_WITH;
698             default -> throw new ToscaPolicyConversionException("Unexpected value for function in filter");
699         };
700     }
701
702     protected RuleType createFilterRule(String ruleId, String field, String filter, Identifier function,
703                                         boolean isBlacklisted) {
704         var rule = new RuleType();
705         rule.setRuleId(ruleId);
706
707         //
708         // Create the Match
709         //
710         var matchFilter = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
711             function,
712             filter,
713             XACML3.ID_DATATYPE_STRING,
714             new IdentifierImpl(GuardPolicyRequest.PREFIX_RESOURCE_ATTRIBUTE_ID + field),
715             XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE
716         );
717         var allOf = new AllOfType();
718         allOf.getMatch().add(matchFilter);
719         var anyOf = new AnyOfType();
720         anyOf.getAllOf().add(allOf);
721         var target = new TargetType();
722         target.getAnyOf().add(anyOf);
723
724         rule.setTarget(target);
725
726         if (isBlacklisted) {
727             rule.setEffect(EffectType.DENY);
728         } else {
729             rule.setEffect(EffectType.PERMIT);
730         }
731         return rule;
732     }
733
734     @Getter
735     public static class TimeRangeDefinition {
736         private @Valid TimeRange timeRange;
737     }
738
739     @Getter
740     public static class TargetTypeDefinition {
741         private String actor;
742         private String operation;
743         private String target;
744         private String id;
745     }
746
747     @Getter
748     @NotNull
749     @NotBlank
750     public static class TimeRange {
751         @SerializedName("start_time")
752         private String startTime;
753
754         @SerializedName("end_time")
755         private String endTime;
756     }
757
758     @Getter
759     public static class FrequencyDefinition {
760         @NotNull
761         private Integer limit;
762         private Integer timeWindow;
763         private String timeUnits;
764     }
765
766     @Getter
767     public static class MinMaxDefinition {
768         @NotNull
769         private String target;
770         private Integer min;
771         private Integer max;
772     }
773
774     @Getter
775     @NotNull
776     public static class BlacklistDefinition {
777         private List<@NotNull Object> blacklist;
778     }
779
780     @Getter
781     @NotNull
782     public static class FilterDefinition {
783         private String algorithm;
784         private List<@NotNull @Valid FilterAttribute> filters;
785     }
786
787     @Getter
788     @NotNull
789     public static class FilterAttribute {
790         private String field;
791         private String filter;
792         private String function;
793         private Boolean blacklist;
794     }
795 }