1 # ============LICENSE_START===============================================
2 # Copyright (C) 2021 Nordix Foundation. All rights reserved.
3 # ========================================================================
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 # ============LICENSE_END=================================================
27 from connexion import NoContent
28 from flask import Flask, escape, request, Response, make_response
29 from jsonschema import validate
30 from var_declaration import policy_instances, policy_types, policy_status, callbacks, forced_settings, policy_fingerprint, hosts_set
31 from utils import calcFingerprint
32 from maincommon import check_apipath, apipath, get_supported_interfaces_response, extract_host_name, is_duplicate_check
35 APPL_JSON='application/json'
36 APPL_PROB_JSON='application/problem+json'
38 EXT_SRV_URL=os.getenv('EXT_SRV_URL')
39 KAFKA_DISPATCHER_URL=os.getenv('KAFKA_DISPATCHER_URL')
41 # API Function: Get all policy type ids
42 def get_all_policy_types():
44 extract_host_name(hosts_set, request)
46 if ((r := check_modified_response()) is not None):
49 res = list(policy_types.keys())
52 # API Function: Get a policy type
53 def get_policy_type(policyTypeId):
55 extract_host_name(hosts_set, request)
57 if ((r := check_modified_response()) is not None):
60 policy_type_id=str(policyTypeId)
62 if (policy_type_id not in policy_types.keys()):
63 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_type_id)
64 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
66 return Response(json.dumps(policy_types[policy_type_id]), 200, mimetype=APPL_JSON)
68 # API Function: Get all policy ids
69 def get_all_policy_identities(policyTypeId):
71 extract_host_name(hosts_set, request)
73 if ((r := check_modified_response()) is not None):
76 policy_type_id=str(policyTypeId)
78 if (policy_type_id not in policy_types.keys()):
79 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_type_id)
80 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
82 return (list(policy_instances[policy_type_id].keys()), 200)
84 # API Function: Create or update a policy
85 def put_policy(policyTypeId, policyId):
87 extract_host_name(hosts_set, request)
89 if ((r := check_modified_response()) is not None):
92 policy_type_id=str(policyTypeId)
93 policy_id=str(policyId)
95 if (policy_type_id not in policy_types.keys()):
96 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
97 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
101 data = json.loads(data)
103 pjson=create_problem_json(None, "The policy is corrupt or missing.", 400, None, policy_id)
104 return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
107 validate(instance=data, schema=policy_types[policy_type_id]['policySchema'])
113 if policy_id in policy_instances[policy_type_id].keys():
115 if (is_duplicate_check()):
116 fp_previous=calcFingerprint(policy_instances[policy_type_id][policy_id], policy_type_id)
118 fp_previous=policy_id
120 if (policy_id in policy_fingerprint.values()):
121 pjson=create_problem_json(None, "The policy id already exist for other policy type.", 400, None, policy_id)
122 return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
124 if (is_duplicate_check()):
125 fp=calcFingerprint(data, policy_type_id)
129 if ((fp in policy_fingerprint.keys()) and is_duplicate_check()):
130 p_id=policy_fingerprint[fp]
131 if (p_id != policy_id):
132 pjson=create_problem_json(None, "Duplicate, the policy json already exists.", 400, None, policy_id)
133 return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
135 #Callout hooks for kafka dispatcher
136 if (KAFKA_DISPATCHER_URL is not None):
137 resp = callout_kafka_dispatcher(policy_type_id, policy_id, data, retcode)
139 pjson=create_error_response(resp)
140 return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
142 #Callout hooks for external server
143 #When it fails, break and return 419 HTTP status code
144 if (EXT_SRV_URL is not None):
145 resp = callout_external_server(policy_id, data, 'PUT')
146 if (resp != retcode):
147 pjson=create_error_response(resp)
148 return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
150 if (fp_previous is not None):
151 del policy_fingerprint[fp_previous]
153 policy_fingerprint[fp]=policy_id
155 noti=request.args.get('notificationDestination')
156 callbacks[policy_id]=noti
158 policy_instances[policy_type_id][policy_id]=data
160 if (policy_types[policy_type_id]['statusSchema'] is not None):
162 ps["enforceStatus"] = ""
163 ps["enforceReason"] = ""
164 policy_status[policy_id] = ps
167 return Response(json.dumps(data), 200, mimetype=APPL_JSON)
170 headers['Location']='/A1-P/v2/policytypes/' + policy_type_id + '/policies/' + policy_id
171 return Response(json.dumps(data), 201, headers=headers, mimetype=APPL_JSON)
173 # API Function: Get a policy
174 def get_policy(policyTypeId, policyId):
176 extract_host_name(hosts_set, request)
178 if ((r := check_modified_response()) is not None):
181 policy_type_id=str(policyTypeId)
182 policy_id=str(policyId)
184 if (policy_type_id not in policy_types.keys()):
185 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
186 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
188 if (policy_id not in policy_instances[policy_type_id].keys()):
189 pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id)
190 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
192 return Response(json.dumps(policy_instances[policy_type_id][policy_id]), 200, mimetype=APPL_JSON)
194 # API Function: Delete a policy
195 def delete_policy(policyTypeId, policyId):
197 extract_host_name(hosts_set, request)
199 if ((r := check_modified_response()) is not None):
202 policy_type_id=str(policyTypeId)
203 policy_id=str(policyId)
205 if (policy_type_id not in policy_types.keys()):
206 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
207 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
209 if (policy_id not in policy_instances[policy_type_id].keys()):
210 pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id)
211 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
213 #Callout hooks for kafka dispatcher
214 if (KAFKA_DISPATCHER_URL is not None):
215 resp = callout_kafka_dispatcher(policy_type_id, policy_id, None, 204)
217 pjson=create_error_response(resp)
218 return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
220 #Callout hooks for external server
221 #When it fails, break and return 419 HTTP status code
222 if (EXT_SRV_URL is not None):
223 resp = callout_external_server(policy_id, None, 'DELETE')
225 pjson=create_error_response(resp)
226 return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
228 if (is_duplicate_check()):
229 fp_previous=calcFingerprint(policy_instances[policy_type_id][policy_id], policy_type_id)
231 fp_previous=policy_id
233 policy_fingerprint.pop(fp_previous)
234 policy_instances[policy_type_id].pop(policy_id)
235 policy_status.pop(policy_id)
236 callbacks.pop(policy_id)
237 return Response('', 204, mimetype=APPL_JSON)
239 # API Function: Get status for a policy
240 def get_policy_status(policyTypeId, policyId):
242 extract_host_name(hosts_set, request)
244 if ((r := check_modified_response()) is not None):
247 policy_type_id=str(policyTypeId)
248 policy_id=str(policyId)
250 if (policy_type_id not in policy_types.keys()):
251 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
252 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
254 if (policy_id not in policy_instances[policy_type_id].keys()):
255 pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id)
256 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
258 #Callout hooks for kafka dispatcher
259 if (KAFKA_DISPATCHER_URL is not None):
260 resp = callout_kafka_dispatcher(policy_type_id, policy_id, None, 202)
262 pjson=create_error_response(resp)
263 return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
265 return Response(json.dumps(policy_status[policy_id]), status=200, mimetype=APPL_JSON)
268 # Helper: Callout kafka dispatcher server to notify it for policy operations
269 def callout_kafka_dispatcher(policy_type_id, policy_id, payload, retcode):
271 target_url = KAFKA_DISPATCHER_URL + "/policytypes/" + policy_type_id + "/kafkadispatcher/" + policy_id
273 # create operation, publish with payload
275 resp=requests.put(target_url, json=payload, timeout=30, verify=False)
276 return resp.status_code
277 # update operation, publish with payload
278 elif (retcode == 200):
279 # add headers an update-flag
280 headers = {'updateoper' : 'yes'}
281 resp=requests.put(target_url, json=payload, headers=headers, timeout=30, verify=False)
282 return resp.status_code
283 # delete operation, publish without payload
284 elif (retcode == 204):
285 resp=requests.delete(target_url, timeout=30, verify=False)
286 return resp.status_code
287 # get policy status operation, publish without payload
288 elif (retcode == 202):
290 target_url = target_url + "/status"
291 resp=requests.get(target_url, timeout=30, verify=False)
292 return resp.status_code
297 # Helper: Callout external server to notify it for policy operations
298 # Returns 200, 201 and 204 for the success callout hooks, for the others returns 419
299 def callout_external_server(policy_id, payload, operation):
301 target_url=EXT_SRV_URL + policy_id
303 if (operation == 'PUT'):
304 #Suppress error when self-signed certificate is being used with verify flag
305 resp=requests.put(target_url, json=payload, timeout=10, verify=False)
306 return resp.status_code
307 elif (operation == 'DELETE'):
308 resp=requests.delete(target_url, timeout=10, verify=False)
309 return resp.status_code
311 #Return a generic unassigned HTTP status code as per iana, for all exceptions (419:Callout failed)
312 #https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
315 # Helper: Create a response object if forced http response code is set
316 def get_forced_response():
318 if (forced_settings['code'] is not None):
319 value=forced_settings['code']
320 pjson=create_error_response(int(value))
321 forced_settings['code']=None
322 return Response(json.dumps(pjson), pjson['status'], mimetype=APPL_PROB_JSON)
325 # Helper: Delay if delayed response code is set
328 if (forced_settings['delay'] is not None):
330 val=int(forced_settings['delay'])
335 # Helper: Check if response shall be delayed or a forced response shall be sent
336 def check_modified_response():
339 return get_forced_response()
341 # Helper: Create a problem json object
342 def create_problem_json(type_of, title, status, detail, instance):
345 if type_of is not None:
346 error["type"] = type_of
347 if title is not None:
348 error["title"] = title
349 if status is not None:
350 error["status"] = status
351 if detail is not None:
352 error["detail"] = detail
353 if instance is not None:
354 error["instance"] = instance
357 # Helper: Create a problem json based on a generic http response code
358 def create_error_response(code):
361 return(create_problem_json(None, "Bad request", 400, "Object in payload not properly formulated or not related to the method", None))
363 return(create_problem_json(None, "Not found", 404, "No resource found at the URI", None))
365 return(create_problem_json(None, "Method not allowed", 405, "Method not allowed for the URI", None))
367 return(create_problem_json(None, "Conflict", 409, "Request could not be processed in the current state of the resource", None))
369 return(create_problem_json(None, "Callout failed", 419, "Callout hooks could not be processed on the external server", None))
371 return(create_problem_json(None, "Too many requests", 429, "Too many requests have been sent in a given amount of time", None))
373 return(create_problem_json(None, "Insufficient storage", 507, "The method could not be performed on the resource because the provider is unable to store the representation needed to successfully complete the request", None))
375 return(create_problem_json(None, "Service unavailable", 503, "The provider is currently unable to handle the request due to a temporary overload", None))
377 return(create_problem_json(None, "Unknown", code, "Not implemented response code", None))