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
33 from models.enforceStatus import EnforceStatus
36 APPL_JSON='application/json'
37 APPL_PROB_JSON='application/problem+json'
39 EXT_SRV_URL=os.getenv('EXT_SRV_URL')
40 KAFKA_DISPATCHER_URL=os.getenv('KAFKA_DISPATCHER_URL')
42 # API Function: Get all policy type ids
43 def get_all_policy_types():
45 extract_host_name(hosts_set, request)
47 if ((r := check_modified_response()) is not None):
50 res = list(policy_types.keys())
53 # API Function: Get a policy type
54 def get_policy_type(policyTypeId):
56 extract_host_name(hosts_set, request)
58 if ((r := check_modified_response()) is not None):
61 policy_type_id=str(policyTypeId)
63 if (policy_type_id not in policy_types.keys()):
64 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_type_id)
65 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
67 return Response(json.dumps(policy_types[policy_type_id]), 200, mimetype=APPL_JSON)
69 # API Function: Get all policy ids
70 def get_all_policy_identities(policyTypeId):
72 extract_host_name(hosts_set, request)
74 if ((r := check_modified_response()) is not None):
77 policy_type_id=str(policyTypeId)
79 if (policy_type_id not in policy_types.keys()):
80 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_type_id)
81 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
83 return (list(policy_instances[policy_type_id].keys()), 200)
85 # API Function: Create or update a policy
86 def put_policy(policyTypeId, policyId):
88 extract_host_name(hosts_set, request)
90 if ((r := check_modified_response()) is not None):
93 policy_type_id=str(policyTypeId)
94 policy_id=str(policyId)
96 if (policy_type_id not in policy_types.keys()):
97 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
98 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
102 data = json.loads(data)
104 pjson=create_problem_json(None, "The policy is corrupt or missing.", 400, None, policy_id)
105 return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
108 validate(instance=data, schema=policy_types[policy_type_id]['policySchema'])
114 if policy_id in policy_instances[policy_type_id].keys():
116 if (is_duplicate_check()):
117 fp_previous=calcFingerprint(policy_instances[policy_type_id][policy_id], policy_type_id)
119 fp_previous=policy_id
121 if (policy_id in policy_fingerprint.values()):
122 pjson=create_problem_json(None, "The policy id already exist for other policy type.", 400, None, policy_id)
123 return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
125 if (is_duplicate_check()):
126 fp=calcFingerprint(data, policy_type_id)
130 if ((fp in policy_fingerprint.keys()) and is_duplicate_check()):
131 p_id=policy_fingerprint[fp]
132 if (p_id != policy_id):
133 pjson=create_problem_json(None, "Duplicate, the policy json already exists.", 400, None, policy_id)
134 return Response(json.dumps(pjson), 400, mimetype=APPL_PROB_JSON)
136 #Callout hooks for kafka dispatcher
137 if (KAFKA_DISPATCHER_URL is not None):
138 resp = callout_kafka_dispatcher(policy_type_id, policy_id, data, retcode)
140 pjson=create_error_response(resp)
141 return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
143 # Callout hooks for external server
144 # When it fails, break and return HTTP status code 500
145 if (EXT_SRV_URL is not None):
146 resp = callout_external_server(policy_id, data, 'PUT')
147 if (resp != retcode):
148 pjson=create_error_response(resp)
149 return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
151 if (fp_previous is not None):
152 del policy_fingerprint[fp_previous]
154 policy_fingerprint[fp]=policy_id
156 noti = request.args.get('notificationDestination')
157 callbacks[policy_id] = noti
159 policy_instances[policy_type_id][policy_id]=data
161 if (policy_types[policy_type_id]['statusSchema'] is not None):
162 enforceStatus = EnforceStatus("NOT_ENFORCED", "OTHER_REASON")
163 policy_status[policy_id] = enforceStatus.to_dict()
166 return Response(json.dumps(data), 200, mimetype=APPL_JSON)
169 headers['Location']='/A1-P/v2/policytypes/' + policy_type_id + '/policies/' + policy_id
170 return Response(json.dumps(data), 201, headers=headers, mimetype=APPL_JSON)
172 # API Function: Get a policy
173 def get_policy(policyTypeId, policyId):
175 extract_host_name(hosts_set, request)
177 if ((r := check_modified_response()) is not None):
180 policy_type_id=str(policyTypeId)
181 policy_id=str(policyId)
183 if (policy_type_id not in policy_types.keys()):
184 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
185 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
187 if (policy_id not in policy_instances[policy_type_id].keys()):
188 pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id)
189 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
191 return Response(json.dumps(policy_instances[policy_type_id][policy_id]), 200, mimetype=APPL_JSON)
193 # API Function: Delete a policy
194 def delete_policy(policyTypeId, policyId):
196 extract_host_name(hosts_set, request)
198 if ((r := check_modified_response()) is not None):
201 policy_type_id=str(policyTypeId)
202 policy_id=str(policyId)
204 if (policy_type_id not in policy_types.keys()):
205 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
206 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
208 if (policy_id not in policy_instances[policy_type_id].keys()):
209 pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id)
210 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
212 #Callout hooks for kafka dispatcher
213 if (KAFKA_DISPATCHER_URL is not None):
214 resp = callout_kafka_dispatcher(policy_type_id, policy_id, None, 204)
216 pjson=create_error_response(resp)
217 return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
219 # Callout hooks for external server
220 # When it fails, break and return HTTP status code 500
221 if (EXT_SRV_URL is not None):
222 resp = callout_external_server(policy_id, None, 'DELETE')
224 pjson=create_error_response(resp)
225 return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
227 if (is_duplicate_check()):
228 fp_previous=calcFingerprint(policy_instances[policy_type_id][policy_id], policy_type_id)
230 fp_previous=policy_id
232 policy_fingerprint.pop(fp_previous)
233 policy_instances[policy_type_id].pop(policy_id)
234 policy_status.pop(policy_id)
235 callbacks.pop(policy_id)
236 return Response('', 204, mimetype=APPL_JSON)
238 # API Function: Get status for a policy
239 def get_policy_status(policyTypeId, policyId):
241 extract_host_name(hosts_set, request)
243 if ((r := check_modified_response()) is not None):
246 policy_type_id=str(policyTypeId)
247 policy_id=str(policyId)
249 if (policy_type_id not in policy_types.keys()):
250 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
251 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
253 if (policy_id not in policy_instances[policy_type_id].keys()):
254 pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id)
255 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
257 #Callout hooks for kafka dispatcher
258 if (KAFKA_DISPATCHER_URL is not None):
259 resp = callout_kafka_dispatcher(policy_type_id, policy_id, None, 202)
261 pjson=create_error_response(resp)
262 return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
264 return Response(json.dumps(policy_status[policy_id]), status=200, mimetype=APPL_JSON)
267 # Helper: Callout kafka dispatcher server to notify it for policy operations
268 def callout_kafka_dispatcher(policy_type_id, policy_id, payload, retcode):
270 target_url = KAFKA_DISPATCHER_URL + "/policytypes/" + policy_type_id + "/kafkadispatcher/" + policy_id
272 # create operation, publish with payload
274 resp=requests.put(target_url, json=payload, timeout=30, verify=False)
275 return resp.status_code
276 # update operation, publish with payload
277 elif (retcode == 200):
278 # add headers an update-flag
279 headers = {'updateoper' : 'yes'}
280 resp=requests.put(target_url, json=payload, headers=headers, timeout=30, verify=False)
281 return resp.status_code
282 # delete operation, publish without payload
283 elif (retcode == 204):
284 resp=requests.delete(target_url, timeout=30, verify=False)
285 return resp.status_code
286 # get policy status operation, publish without payload
287 elif (retcode == 202):
289 target_url = target_url + "/status"
290 resp=requests.get(target_url, timeout=30, verify=False)
291 return resp.status_code
296 # Helper: Callout external server to notify it for policy operations
297 # Returns 200, 201 and 204 for the success callout hooks, for the others returns 419
298 def callout_external_server(policy_id, payload, operation):
300 target_url=EXT_SRV_URL + policy_id
302 if (operation == 'PUT'):
303 #Suppress error when self-signed certificate is being used with verify flag
304 resp=requests.put(target_url, json=payload, timeout=10, verify=False)
305 return resp.status_code
306 elif (operation == 'DELETE'):
307 resp=requests.delete(target_url, timeout=10, verify=False)
308 return resp.status_code
310 #Return a generic unassigned HTTP status code as per iana, for all exceptions (419:Callout failed)
311 #https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
314 # Helper: Create a response object if forced http response code is set
315 def get_forced_response():
317 if (forced_settings['code'] is not None):
318 value=forced_settings['code']
319 pjson=create_error_response(int(value))
320 forced_settings['code']=None
321 return Response(json.dumps(pjson), pjson['status'], mimetype=APPL_PROB_JSON)
324 # Helper: Delay if delayed response code is set
327 if (forced_settings['delay'] is not None):
329 val=int(forced_settings['delay'])
334 # Helper: Check if response shall be delayed or a forced response shall be sent
335 def check_modified_response():
338 return get_forced_response()
340 # Helper: Create a problem json object
341 def create_problem_json(type_of, title, status, detail, instance):
344 if type_of is not None:
345 error["type"] = type_of
346 if title is not None:
347 error["title"] = title
348 if status is not None:
349 error["status"] = status
350 if detail is not None:
351 error["detail"] = detail
352 if instance is not None:
353 error["instance"] = instance
356 # Helper: Create a problem json based on a generic http response code
357 def create_error_response(code):
360 return(create_problem_json(None, "Bad request", 400, "Object in payload not properly formulated or not related to the method", None))
362 return(create_problem_json(None, "Not found", 404, "No resource found at the URI", None))
364 return(create_problem_json(None, "Method not allowed", 405, "Method not allowed for the URI", None))
366 return(create_problem_json(None, "Conflict", 409, "Request could not be processed in the current state of the resource", None))
368 return(create_problem_json(None, "Callout failed", 419, "Callout hooks could not be processed on the external server", None))
370 return(create_problem_json(None, "Too many requests", 429, "Too many requests have been sent in a given amount of time", None))
372 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))
374 return(create_problem_json(None, "Service unavailable", 503, "The provider is currently unable to handle the request due to a temporary overload", None))
376 return(create_problem_json(None, "Unknown", code, "Not implemented response code", None))