Commit includes ControlLoopPolicy API and bugfixes
[policy/engine.git] / POLICY-SDK-APP / src / main / java / org / openecomp / policy / admin / RESTfulPAPEngine.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ECOMP Policy Engine
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
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  * ============LICENSE_END=========================================================
19  */
20
21 package org.openecomp.policy.admin;
22
23
24
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.io.UnsupportedEncodingException;
31 import java.net.HttpURLConnection;
32 import java.net.URL;
33 import java.net.URLEncoder;
34 import java.nio.charset.StandardCharsets;
35 import java.util.Base64;
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.Map;
39 import java.util.Set;
40
41 import org.apache.commons.io.IOUtils;
42 import org.openecomp.policy.rest.XACMLRestProperties;
43 import org.openecomp.policy.rest.adapter.PolicyRestAdapter;
44 import org.openecomp.policy.xacml.api.XACMLErrorConstants;
45 import org.openecomp.policy.xacml.api.pap.EcompPDP;
46 import org.openecomp.policy.xacml.api.pap.EcompPDPGroup;
47 import org.openecomp.policy.xacml.api.pap.PAPPolicyEngine;
48 import org.openecomp.policy.xacml.std.pap.StdPAPPolicy;
49 import org.openecomp.policy.xacml.std.pap.StdPDP;
50 import org.openecomp.policy.xacml.std.pap.StdPDPGroup;
51 import org.openecomp.policy.xacml.std.pap.StdPDPItemSetChangeNotifier;
52 import org.openecomp.policy.xacml.std.pap.StdPDPPolicy;
53 import org.openecomp.policy.xacml.std.pap.StdPDPStatus;
54 import com.att.research.xacml.api.pap.PAPException;
55 import com.att.research.xacml.api.pap.PDPPolicy;
56 import com.att.research.xacml.api.pap.PDPStatus;
57 import com.att.research.xacml.util.XACMLProperties;
58 import com.fasterxml.jackson.databind.DeserializationFeature;
59 import com.fasterxml.jackson.databind.ObjectMapper;
60 import com.fasterxml.jackson.databind.type.CollectionType;
61 import org.openecomp.policy.common.logging.flexlogger.FlexLogger; 
62 import org.openecomp.policy.common.logging.flexlogger.Logger;
63
64 /**
65  * Implementation of the PAPEngine interface that communicates with a PAP engine in a remote servlet
66  * through a RESTful interface
67  * 
68  *
69  */
70 public class RESTfulPAPEngine extends StdPDPItemSetChangeNotifier implements PAPPolicyEngine {
71         private static final Logger LOGGER      = FlexLogger.getLogger(RESTfulPAPEngine.class);
72
73         //
74         // URL of the PAP Servlet that this Admin Console talks to
75         //
76         private String papServletURLString;
77         
78         /**
79          * Set up link with PAP Servlet and get our initial set of Groups
80          * @throws Exception 
81          */
82         public RESTfulPAPEngine (String myURLString) throws PAPException, IOException  {
83                 //
84                 // Get our URL to the PAP servlet
85                 //
86                 this.papServletURLString = XACMLProperties.getProperty(XACMLRestProperties.PROP_PAP_URL);
87                 if (this.papServletURLString == null || this.papServletURLString.length() == 0) {
88                         String message = "The property 'POLICYENGINE_ADMIN_ACTIVE' was not set during installation.  Admin Console cannot call PAP.";
89                         LOGGER.error(message);
90                         throw new PAPException(message);
91                 }
92
93                 //
94                 // register this Admin Console with the PAP Servlet to get updates
95                 //
96                 Object newURL = sendToPAP("PUT", null, null, null, "adminConsoleURL=" + myURLString);
97                 if (newURL != null) {
98                         // assume this was a re-direct and try again
99                         LOGGER.warn("Redirecting to '" + newURL + "'");
100                         this.papServletURLString = (String)newURL;
101                         newURL = sendToPAP("PUT", null, null, null, "adminConsoleURL=" + myURLString);
102                         if (newURL != null) {
103                                 LOGGER.error("Failed to redirect to " + this.papServletURLString);
104                                 throw new PAPException("Failed to register with PAP");
105                         }
106                 }
107         }
108         
109
110         //
111         // High-level commands used by the Admin Console code through the PAPEngine Interface
112         //
113         
114         @Override
115         public EcompPDPGroup getDefaultGroup() throws PAPException {
116                 EcompPDPGroup newGroup = (EcompPDPGroup)sendToPAP("GET", null, null, StdPDPGroup.class, "groupId=", "default=");
117                 return newGroup;
118         }
119
120         @Override
121         public void SetDefaultGroup(EcompPDPGroup group) throws PAPException {
122                 sendToPAP("POST", null, null, null, "groupId=" + group.getId(), "default=true");
123         }
124
125         @SuppressWarnings("unchecked")
126         @Override
127         public Set<EcompPDPGroup> getEcompPDPGroups() throws PAPException {
128                 Set<EcompPDPGroup> newGroupSet;
129                 newGroupSet = (Set<EcompPDPGroup>) this.sendToPAP("GET", null, Set.class, StdPDPGroup.class, "groupId=");
130                 return Collections.unmodifiableSet(newGroupSet);
131         }
132
133
134         @Override
135         public EcompPDPGroup getGroup(String id) throws PAPException {
136                 EcompPDPGroup newGroup = (EcompPDPGroup)sendToPAP("GET", null, null, StdPDPGroup.class, "groupId=" + id);
137                 return newGroup;
138         }
139
140         @Override
141         public void newGroup(String name, String description)
142                         throws PAPException, NullPointerException {
143                 String escapedName = null;
144                 String escapedDescription = null;
145                 try {
146                         escapedName = URLEncoder.encode(name, "UTF-8");
147                         escapedDescription = URLEncoder.encode(description, "UTF-8");
148                 } catch (UnsupportedEncodingException e) {
149                         throw new PAPException("Unable to send name or description to PAP: " + e.getMessage());
150                 }
151                 
152                 this.sendToPAP("POST", null, null, null, "groupId=", "groupName="+escapedName, "groupDescription=" + escapedDescription);
153         }
154         
155         
156         /**
157          * Update the configuration on the PAP for a single Group.
158          * 
159          * @param group
160          * @return
161          * @throws PAPException
162          */
163         public void updateGroup(EcompPDPGroup group) throws PAPException {
164
165                 try {
166                         
167                         //
168                         // ASSUME that all of the policies mentioned in this group are already located in the correct directory on the PAP!
169                         //
170                         // Whenever a Policy is added to the group, that file must be automatically copied to the PAP from the Workspace.
171                         // 
172                         
173                         
174                         // Copy all policies from the local machine's workspace to the PAP's PDPGroup directory.
175                         // This is not efficient since most of the policies will already exist there.
176                         // However, the policy files are (probably!) not too huge, and this is a good way to ensure that any corrupted files on the PAP get refreshed.
177                 
178                         
179                         // now update the group object on the PAP
180                         
181                         sendToPAP("PUT", group, null, null, "groupId=" + group.getId());
182                 } catch (Exception e) {
183                         String message = "Unable to PUT policy '" + group.getId() + "', e:" + e;
184                         LOGGER.error(XACMLErrorConstants.ERROR_PROCESS_FLOW + message, e);
185                         throw new PAPException(message);
186                 }
187         }
188         
189         
190         @Override
191         public void removeGroup(EcompPDPGroup group, EcompPDPGroup newGroup)
192                         throws PAPException, NullPointerException {
193                 String moveToGroupString = null;
194                 if (newGroup != null) {
195                         moveToGroupString = "movePDPsToGroupId=" + newGroup.getId();
196                 }
197                 sendToPAP("DELETE", null, null, null, "groupId=" + group.getId(), moveToGroupString);
198         }
199         
200         @Override
201         public EcompPDPGroup getPDPGroup(EcompPDP pdp) throws PAPException {
202                 return getPDPGroup(pdp.getId());
203         }
204
205         
206         public EcompPDPGroup getPDPGroup(String pdpId) throws PAPException {
207                 EcompPDPGroup newGroup = (EcompPDPGroup)sendToPAP("GET", null, null, StdPDPGroup.class, "groupId=", "pdpId=" + pdpId, "getPDPGroup=");
208                 return newGroup;
209         }
210
211         @Override
212         public EcompPDP getPDP(String pdpId) throws PAPException {
213                 EcompPDP newPDP = (EcompPDP)sendToPAP("GET", null, null, StdPDP.class, "groupId=", "pdpId=" + pdpId);
214                 return newPDP;
215         }
216         
217         @Override
218         public void newPDP(String id, EcompPDPGroup group, String name, String description, int jmxport) throws PAPException,
219                         NullPointerException {
220                 StdPDP newPDP = new StdPDP(id, name, description, jmxport);
221                 sendToPAP("PUT", newPDP, null, null, "groupId=" + group.getId(), "pdpId=" + id);
222                 return;
223         }
224
225         @Override
226         public void movePDP(EcompPDP pdp, EcompPDPGroup newGroup) throws PAPException {
227                 sendToPAP("POST", null, null, null, "groupId=" + newGroup.getId(), "pdpId=" + pdp.getId());
228                 return;
229         }
230
231         @Override
232         public void updatePDP(EcompPDP pdp) throws PAPException {
233                 EcompPDPGroup group = getPDPGroup(pdp);
234                 sendToPAP("PUT", pdp, null, null, "groupId=" + group.getId(), "pdpId=" + pdp.getId());
235                 return;
236         }
237         
238         @Override
239         public void removePDP(EcompPDP pdp) throws PAPException {
240                 EcompPDPGroup group = getPDPGroup(pdp);
241                 sendToPAP("DELETE", null, null, null, "groupId=" + group.getId(), "pdpId=" + pdp.getId());
242                 return;
243         }
244         
245         //Validate the Policy Data
246         public boolean validatePolicyRequest(PolicyRestAdapter policyAdapter, String policyType) throws PAPException {
247                 Boolean isValidData = false;
248                 StdPAPPolicy newPAPPolicy = new StdPAPPolicy(policyAdapter.getPolicyName(), policyAdapter.getConfigBodyData(), policyAdapter.getConfigType(), "Base");
249                 
250                 //send JSON object to PAP
251                 isValidData = (Boolean) sendToPAP("PUT", newPAPPolicy, null, null, "operation=validate", "apiflag=admin", "policyType=" + policyType);
252                 return isValidData;
253         }
254         
255         
256         
257         @Override
258         public void publishPolicy(String id, String name, boolean isRoot,
259                         InputStream policy, EcompPDPGroup group) throws PAPException {
260                 
261
262                 // copy the (one) file into the target directory on the PAP servlet
263                 copyFile(id, group, policy);
264                 
265                 // adjust the local copy of the group to include the new policy
266                 PDPPolicy pdpPolicy = new StdPDPPolicy(id, isRoot, name);
267                 group.getPolicies().add(pdpPolicy);
268                 
269                 // tell the PAP servlet to include the policy in the configuration
270                 updateGroup(group);
271                                 
272                 return;
273         }
274         
275         /**
276          * Copy a single Policy file from the input stream to the PAP Servlet.
277          * Either this works (silently) or it throws an exception.
278          * 
279          * @param policyId
280          * @param group
281          * @param policy
282          * @return
283          * @throws PAPException
284          */
285         public void copyFile(String policyId, EcompPDPGroup group, InputStream policy) throws PAPException {
286                 // send the policy file to the PAP Servlet
287                 try {
288                         sendToPAP("POST", policy, null, null, "groupId=" + group.getId(), "policyId="+policyId);
289                 } catch (Exception e) {
290                         String message = "Unable to PUT policy '" + policyId + "', e:" + e;
291                         LOGGER.error(XACMLErrorConstants.ERROR_PROCESS_FLOW + message, e);
292                         throw new PAPException(message);
293                 }
294         }
295         
296
297         @Override
298         public void     copyPolicy(PDPPolicy policy, EcompPDPGroup group) throws PAPException {
299                 if (policy == null || group == null) {
300                         throw new PAPException("Null input policy="+policy+"  group="+group);
301                 }
302                 try (InputStream is = new FileInputStream(new File(policy.getLocation())) ) {
303                         copyFile(policy.getId(), group, is );
304                 } catch (Exception e) {
305                         String message = "Unable to PUT policy '" + policy.getId() + "', e:" + e;
306                         LOGGER.error(XACMLErrorConstants.ERROR_PROCESS_FLOW + message, e);
307                         throw new PAPException(message);
308                 }
309         }
310         
311         @Override
312         public void     removePolicy(PDPPolicy policy, EcompPDPGroup group) throws PAPException {
313                 throw new PAPException("NOT IMPLEMENTED");
314
315         }
316
317         
318         /**
319          * Special operation - Similar to the normal PAP operations but this one contacts the PDP directly
320          * to get detailed status info.
321          * 
322          * @param pdp
323          * @return
324          * @throws PAPException 
325          */
326         
327         public PDPStatus getStatus(EcompPDP pdp) throws PAPException {
328                 StdPDPStatus status = (StdPDPStatus)sendToPAP("GET", pdp, null, StdPDPStatus.class);
329                 return status;
330         }
331         
332         
333         //
334         // Internal Operations called by the PAPEngine Interface methods
335         //
336         
337         /**
338          * Send a request to the PAP Servlet and get the response.
339          * 
340          * The content is either an InputStream to be copied to the Request OutputStream
341          *      OR it is an object that is to be encoded into JSON and pushed into the Request OutputStream.
342          * 
343          * The Request parameters may be encoded in multiple "name=value" sets, or parameters may be combined by the caller.
344          * 
345          * @param method
346          * @param content       - EITHER an InputStream OR an Object to be encoded in JSON
347          * @param collectionTypeClass
348          * @param responseContentClass
349          * @param parameters
350          * @return
351          * @throws Exception
352          */
353         @SuppressWarnings({ "rawtypes", "unchecked" })
354         private Object sendToPAP(String method, Object content, Class collectionTypeClass, Class responseContentClass, String... parameters ) throws PAPException {
355                 HttpURLConnection connection = null;
356                 String papID = XACMLProperties.getProperty(XACMLRestProperties.PROP_PAP_USERID);
357                 LOGGER.info("User Id is " + papID);
358                 String papPass = XACMLProperties.getProperty(XACMLRestProperties.PROP_PAP_PASS);
359                 LOGGER.info("Pass is: " + papPass);
360                 Base64.Encoder encoder = Base64.getEncoder();
361                 String encoding = encoder.encodeToString((papID+":"+papPass).getBytes(StandardCharsets.UTF_8));
362                 LOGGER.info("Encoding for the PAP is: " + encoding);
363                 try {
364                         String fullURL = papServletURLString;
365                         if (parameters != null && parameters.length > 0) {
366                                 String queryString = "";
367                                 for (String p : parameters) {
368                                         queryString += "&" + p;
369                                 }
370                                 fullURL += "?" + queryString.substring(1);
371                         }
372                         
373                         // special case - Status (actually the detailed status) comes from the PDP directly, not the PAP
374                         if (method.equals("GET") &&     (content instanceof EcompPDP) &&        responseContentClass == StdPDPStatus.class) {
375                                 // Adjust the url and properties appropriately
376                                 String pdpID =((EcompPDP)content).getId(); 
377                                 fullURL = pdpID + "?type=Status";
378                                 content = null;
379                                 if(CheckPDP.validateID(pdpID)){
380                                         encoding = CheckPDP.getEncoding(pdpID);
381                                 }
382                         }
383                         
384                         
385                         URL url = new URL(fullURL);
386
387                         //
388                         // Open up the connection
389                         //
390                         connection = (HttpURLConnection)url.openConnection();
391                         //
392                         // Setup our method and headers
393                         //
394             connection.setRequestMethod(method);
395             connection.setUseCaches(false);
396             //
397             // Adding this in. It seems the HttpUrlConnection class does NOT
398             // properly forward our headers for POST re-direction. It does so
399             // for a GET re-direction.
400             //
401             // So we need to handle this ourselves.
402             //
403             connection.setInstanceFollowRedirects(false);
404             connection.setRequestProperty("Authorization", "Basic " + encoding);
405                         connection.setDoOutput(true);
406                         connection.setDoInput(true);
407                         
408                         if (content != null) {
409                                 if (content instanceof InputStream) {
410                                 try {
411                                         //
412                                         // Send our current policy configuration
413                                         //
414                                         try (OutputStream os = connection.getOutputStream()) {
415                                                 int count = IOUtils.copy((InputStream)content, os);
416                                                 if (LOGGER.isDebugEnabled()) {
417                                                         LOGGER.debug("copied to output, bytes="+count);
418                                                 }
419                                         }
420                                 } catch (Exception e) {
421                                         LOGGER.error(XACMLErrorConstants.ERROR_PROCESS_FLOW + "Failed to write content in '" + method + "'", e);
422                                         throw e;
423                                 }
424                                 } else {
425                                         // The content is an object to be encoded in JSON
426                             ObjectMapper mapper = new ObjectMapper();
427                             mapper.writeValue(connection.getOutputStream(),  content);
428                                 }
429                         }
430             //
431             // Do the connect
432             //
433             connection.connect();
434             if (connection.getResponseCode() == 204) {
435                 LOGGER.info("Success - no content.");
436                 return null;
437             } else if (connection.getResponseCode() == 200) {
438                 LOGGER.info("Success. We have a return object.");
439                 String isValidData = connection.getHeaderField("isValidData");
440                 String isSuccess = connection.getHeaderField("successMapKey");
441                 Map<String, String> successMap = new HashMap<>();
442                 if (isValidData != null && isValidData.equalsIgnoreCase("true")){
443                     LOGGER.info("Policy Data is valid.");       
444                         return true;
445                 } else if (isValidData != null && isValidData.equalsIgnoreCase("false")) {
446                     LOGGER.info("Policy Data is invalid.");     
447                         return false;
448                 } else if (isSuccess != null && isSuccess.equalsIgnoreCase("success")) {
449                         LOGGER.info("Policy Created Successfully!" );
450                         String finalPolicyPath = connection.getHeaderField("finalPolicyPath");
451                         successMap.put("success", finalPolicyPath);
452                         return successMap;
453                 } else if (isSuccess != null && isSuccess.equalsIgnoreCase("error")) {
454                         LOGGER.info("There was an error while creating the policy!");
455                         successMap.put("error", "error");
456                         return successMap;
457                 } else {
458                         // get the response content into a String
459                         String json = null;
460                                 // read the inputStream into a buffer (trick found online scans entire input looking for end-of-file)
461                             java.util.Scanner scanner = new java.util.Scanner(connection.getInputStream());
462                             scanner.useDelimiter("\\A");
463                             json =  scanner.hasNext() ? scanner.next() : "";
464                             scanner.close();
465                             LOGGER.info("JSON response from PAP: " + json);
466                         
467                         // convert Object sent as JSON into local object
468                             ObjectMapper mapper = new ObjectMapper();
469                             mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
470                             if (collectionTypeClass != null) {
471                                 // collection of objects expected
472                                 final CollectionType javaType = 
473                                       mapper.getTypeFactory().constructCollectionType(collectionTypeClass, responseContentClass);
474         
475                                 Object objectFromJSON = mapper.readValue(json, javaType);
476                                                 return objectFromJSON;
477                             } else {
478                                 // single value object expected
479                                     Object objectFromJSON = mapper.readValue(json, responseContentClass);
480                                                 return objectFromJSON;
481                             }
482                 }
483
484             } else if (connection.getResponseCode() >= 300 && connection.getResponseCode()  <= 399) {
485                 // redirection
486                 String newURL = connection.getHeaderField("Location");
487                 if (newURL == null) {
488                         LOGGER.error("No Location header to redirect to when response code="+connection.getResponseCode());
489                         throw new IOException("No redirect Location header when response code="+connection.getResponseCode());
490                 }
491                 int qIndex = newURL.indexOf("?");
492                 if (qIndex > 0) {
493                         newURL = newURL.substring(0, qIndex);
494                 }
495                 LOGGER.info("Redirect seen.  Redirecting " + fullURL + " to " + newURL);
496                 return newURL;
497             } else {
498                 LOGGER.warn("Unexpected response code: " + connection.getResponseCode() + "  message: " + connection.getResponseMessage());
499                 throw new IOException("Server Response: " + connection.getResponseCode() + ": " + connection.getResponseMessage());
500             }
501
502                 } catch (Exception e) {
503                         LOGGER.error(XACMLErrorConstants.ERROR_SYSTEM_ERROR + "HTTP Request/Response to PAP: " + e,e);
504                         throw new PAPException("Request/Response threw :" + e);
505                 } finally {
506                         // cleanup the connection
507                                 if (connection != null) {
508                                 try {
509                                         // For some reason trying to get the inputStream from the connection
510                                         // throws an exception rather than returning null when the InputStream does not exist.
511                                         InputStream is = null;
512                                         try {
513                                                 is = connection.getInputStream();
514                                         } catch (Exception e1) {
515                                                 // ignore this
516                                         }
517                                         if (is != null) {
518                                                 is.close();
519                                         }
520
521                                 } catch (IOException ex) {
522                                         LOGGER.error(XACMLErrorConstants.ERROR_PROCESS_FLOW + "Failed to close connection: " + ex, ex);
523                                 }
524                                 connection.disconnect();
525                         }
526                 }
527         }
528 }