3d316b9aa5dd5e9060916cba8e2ecfe170a2215e
[policy/xacml-pdp.git] / applications / common / src / main / java / org / onap / policy / pdp / xacml / application / common / OnapOperationsHistoryPipEngine.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
4  * ================================================================================
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.pdp.xacml.application.common;
22
23 import com.att.research.xacml.api.Attribute;
24 import com.att.research.xacml.api.AttributeValue;
25 import com.att.research.xacml.api.Identifier;
26 import com.att.research.xacml.api.XACML3;
27 import com.att.research.xacml.api.pip.PIPException;
28 import com.att.research.xacml.api.pip.PIPFinder;
29 import com.att.research.xacml.api.pip.PIPRequest;
30 import com.att.research.xacml.api.pip.PIPResponse;
31 import com.att.research.xacml.std.StdMutableAttribute;
32 import com.att.research.xacml.std.datatypes.DataTypes;
33 import com.att.research.xacml.std.pip.StdMutablePIPResponse;
34 import com.att.research.xacml.std.pip.StdPIPRequest;
35 import com.att.research.xacml.std.pip.StdPIPResponse;
36 import com.att.research.xacml.std.pip.engines.StdConfigurableEngine;
37 import com.google.common.base.Strings;
38
39 import java.math.BigInteger;
40 import java.util.Arrays;
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.Iterator;
44 import java.util.Properties;
45
46 import javax.persistence.EntityManager;
47 import javax.persistence.Persistence;
48
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 public class OnapOperationsHistoryPipEngine extends StdConfigurableEngine {
53     private static Logger logger = LoggerFactory.getLogger(OnapOperationsHistoryPipEngine.class);
54
55     private static final PIPRequest PIP_REQUEST_ACTOR   = new StdPIPRequest(
56             XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE,
57             ToscaDictionary.ID_RESOURCE_GUARD_ACTOR,
58             XACML3.ID_DATATYPE_STRING);
59
60     private static final PIPRequest PIP_REQUEST_RECIPE  = new StdPIPRequest(
61             XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE,
62             ToscaDictionary.ID_RESOURCE_GUARD_RECIPE,
63             XACML3.ID_DATATYPE_STRING);
64
65     private static final PIPRequest PIP_REQUEST_TARGET  = new StdPIPRequest(
66             XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE,
67             ToscaDictionary.ID_RESOURCE_GUARD_TARGETID,
68             XACML3.ID_DATATYPE_STRING);
69
70     private Properties properties;
71
72     public OnapOperationsHistoryPipEngine() {
73         super();
74     }
75
76     @Override
77     public Collection<PIPRequest> attributesRequired() {
78         return Arrays.asList(PIP_REQUEST_ACTOR, PIP_REQUEST_RECIPE, PIP_REQUEST_TARGET);
79     }
80
81     @Override
82     public Collection<PIPRequest> attributesProvided() {
83         return Collections.emptyList();
84     }
85
86     @Override
87     public PIPResponse getAttributes(PIPRequest pipRequest, PIPFinder pipFinder) throws PIPException {
88         logger.debug("getAttributes requesting attribute {} of type {} for issuer {}",
89                 pipRequest.getAttributeId(), pipRequest.getDataTypeId(), pipRequest.getIssuer());
90         //
91         // Determine if the issuer is correct
92         //
93         if (Strings.isNullOrEmpty(pipRequest.getIssuer())) {
94             logger.debug("issuer is null - returning empty response");
95             //
96             // We only respond to ourself as the issuer
97             //
98             return StdPIPResponse.PIP_RESPONSE_EMPTY;
99         }
100         if (! pipRequest.getIssuer().startsWith(ToscaDictionary.GUARD_ISSUER)) {
101             logger.debug("Issuer does not start with guard");
102             //
103             // We only respond to ourself as the issuer
104             //
105             return StdPIPResponse.PIP_RESPONSE_EMPTY;
106         }
107         //
108         // Parse out the issuer which denotes the time window
109         //
110         // Eg: urn:org:onapxacml:guard:historydb:tw:10:minute
111         //
112         String[] s1 = pipRequest.getIssuer().split("tw:");
113         String[] s2 = s1[1].split(":");
114         int timeWindowVal = Integer.parseInt(s2[0]);
115         String timeWindowScale = s2[1];
116         //
117         // Grab other attribute values
118         //
119         String actor = getActor(pipFinder);
120         String operation = getRecipe(pipFinder);
121         String target = getTarget(pipFinder);
122         String timeWindow = timeWindowVal + " " + timeWindowScale;
123         logger.info("Going to query DB about: actor {} operation {} target {} time window {}",
124                 actor, operation, target, timeWindow);
125         //
126         // Sanity check
127         //
128         if (actor == null || operation == null || target == null) {
129             //
130             // See if we have all the values
131             //
132             logger.error("missing attributes return empty");
133             return StdPIPResponse.PIP_RESPONSE_EMPTY;
134         }
135         //
136         // Ok do the database query
137         //
138         int operationCount = doDatabaseQuery(actor, operation, target, timeWindowVal, timeWindowScale);
139         //
140         // Right now return empty
141         //
142         StdMutablePIPResponse stdPipResponse    = new StdMutablePIPResponse();
143         this.addIntegerAttribute(stdPipResponse,
144                 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE,
145                 ToscaDictionary.ID_RESOURCE_GUARD_OPERATIONCOUNT,
146                 operationCount,
147                 pipRequest);
148         return new StdPIPResponse(stdPipResponse);
149     }
150
151     @Override
152     public void configure(String id, Properties properties) throws PIPException {
153         super.configure(id, properties);
154         logger.debug("Configuring historyDb PIP {}", properties);
155         this.properties = properties;
156     }
157
158     private String getActor(PIPFinder pipFinder) {
159         //
160         // Get the actor value
161         //
162         PIPResponse pipResponse = this.getAttribute(PIP_REQUEST_ACTOR, pipFinder);
163         if (pipResponse == null) {
164             logger.error("Need actor attribute which is not found");
165             return null;
166         }
167         //
168         // Find the actor
169         //
170         return findFirstAttributeValue(pipResponse);
171     }
172
173     private String getRecipe(PIPFinder pipFinder) {
174         //
175         // Get the actor value
176         //
177         PIPResponse pipResponse = this.getAttribute(PIP_REQUEST_RECIPE, pipFinder);
178         if (pipResponse == null) {
179             logger.error("Need recipe attribute which is not found");
180             return null;
181         }
182         //
183         // Find the actor
184         //
185         return findFirstAttributeValue(pipResponse);
186     }
187
188     private String getTarget(PIPFinder pipFinder) {
189         //
190         // Get the actor value
191         //
192         PIPResponse pipResponse = this.getAttribute(PIP_REQUEST_TARGET, pipFinder);
193         if (pipResponse == null) {
194             logger.error("Need target attribute which is not found");
195             return null;
196         }
197         //
198         // Find the actor
199         //
200         return findFirstAttributeValue(pipResponse);
201     }
202
203     private PIPResponse getAttribute(PIPRequest pipRequest, PIPFinder pipFinder) {
204         PIPResponse pipResponse = null;
205         try {
206             pipResponse = pipFinder.getMatchingAttributes(pipRequest, this);
207             if (pipResponse.getStatus() != null && !pipResponse.getStatus().isOk()) {
208                 if (logger.isInfoEnabled()) {
209                     logger.info("get attribute error retrieving {}: {}", pipRequest.getAttributeId().stringValue(),
210                         pipResponse.getStatus());
211                 }
212                 pipResponse = null;
213             }
214             if (pipResponse != null && pipResponse.getAttributes().isEmpty()) {
215                 if (logger.isInfoEnabled()) {
216                     logger.info("No value for {}", pipRequest.getAttributeId().stringValue());
217                 }
218                 pipResponse = null;
219             }
220         } catch (PIPException ex) {
221             logger.error("PIPException getting subject-id attribute: " + ex.getMessage(), ex);
222         }
223         return pipResponse;
224     }
225
226     private String findFirstAttributeValue(PIPResponse pipResponse) {
227         for (Attribute attribute: pipResponse.getAttributes()) {
228             Iterator<AttributeValue<String>> iterAttributeValues    = attribute.findValues(DataTypes.DT_STRING);
229             if (iterAttributeValues != null) {
230                 while (iterAttributeValues.hasNext()) {
231                     String value   = iterAttributeValues.next().getValue();
232                     if (value != null) {
233                         return value;
234                     }
235                 }
236             }
237         }
238         return null;
239     }
240
241     private void addIntegerAttribute(StdMutablePIPResponse stdPipResponse, Identifier category,
242             Identifier attributeId, int value, PIPRequest pipRequest) {
243         AttributeValue<BigInteger> attributeValue   = null;
244         try {
245             attributeValue  = DataTypes.DT_INTEGER.createAttributeValue(value);
246         } catch (Exception e) {
247             logger.error("Failed to convert {} to integer {}", value, e);
248         }
249         if (attributeValue != null) {
250             stdPipResponse.addAttribute(new StdMutableAttribute(category, attributeId, attributeValue,
251                     pipRequest.getIssuer(), false));
252         }
253     }
254
255     private int doDatabaseQuery(String actor, String operation, String target, int timeWindowVal,
256             String timeWindowScale) {
257         logger.info("Querying operations history for {} {} {} {} {}",
258                 actor, operation, target, timeWindowVal, timeWindowScale);
259         //
260         // Create our entity manager
261         //
262         EntityManager em;
263         try {
264             //
265             // In case there are any overloaded properties for the JPA
266             //
267             Properties emProperties = new Properties(properties);
268             //
269             // Create the entity manager factory
270             //
271             em = Persistence.createEntityManagerFactory(
272                     properties.getProperty("historydb.persistenceunit", "OperationsHistoryPU"),
273                     emProperties).createEntityManager();
274         } catch (Exception e) {
275             logger.error("Persistence failed {} operations history db {}", e.getLocalizedMessage(), e);
276             return -1;
277         }
278         //
279         // Compute the time window
280         //
281         if (! "minute".equalsIgnoreCase(timeWindowScale)
282             && ! "hour".equalsIgnoreCase(timeWindowScale)
283             && ! "day".equalsIgnoreCase(timeWindowScale)
284             && ! "week".equalsIgnoreCase(timeWindowScale)
285             && ! "month".equalsIgnoreCase(timeWindowScale)
286             && ! "year".equalsIgnoreCase(timeWindowScale)) {
287             //
288             // Unsupported
289             //
290             logger.error("Unsupported time window scale value {}", timeWindowScale);
291             //
292             // Throw an exception instead?
293             //
294             return -1;
295         }
296         //
297         // Do the query
298         //
299         Object result = null;
300         try {
301             //
302             //
303             //
304             String strQuery = "select count(*) as numops from operationshistory"
305                     + " where outcome<>'Failure_Guard'"
306                     + " and actor=?"
307                     + " and operation=?"
308                     + " and target=?"
309                     + " and endtime between TIMESTAMPADD("
310                     + timeWindowScale.toUpperCase()
311                     + ", ?, CURRENT_TIMESTAMP)"
312                     + " and CURRENT_TIMESTAMP";
313             //
314             // We are expecting a single result
315             //
316             result = em.createNativeQuery(strQuery)
317                 .setParameter(1, actor)
318                 .setParameter(2, operation)
319                 .setParameter(3, target)
320                 .setParameter(4, timeWindowVal * -1)
321                 .getSingleResult();
322         } catch (Exception e) {
323             logger.error("Named query failed ", e);
324         }
325         //
326         // Check our query results
327         //
328         if (result != null) {
329             //
330             // Success let's see what JPA returned to us
331             //
332             logger.info("operations query returned {}", result);
333             //
334             // Should get back a long
335             //
336             if (result instanceof Long) {
337                 return ((Long) result).intValue();
338             }
339             //
340             // We shouldn't really get this result, but just
341             // in case we'll do the dirty work of parsing the
342             // string representation of the object.
343             //
344             return Integer.parseInt(result.toString());
345         }
346         //
347         // We get here if we didn't get a result. Should
348         // we propagate back an exception?
349         //
350         return -1;
351     }
352
353 }