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')
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 external server
136 #When it fails, break and return 419 HTTP status code
137 if (EXT_SRV_URL is not None):
138 resp = callout_external_server(policy_id, data, 'PUT')
139 if (resp != retcode):
140 pjson=create_error_response(resp)
141 return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
143 if (fp_previous is not None):
144 del policy_fingerprint[fp_previous]
146 policy_fingerprint[fp]=policy_id
148 noti=request.args.get('notificationDestination')
149 callbacks[policy_id]=noti
151 policy_instances[policy_type_id][policy_id]=data
153 if (policy_types[policy_type_id]['statusSchema'] is not None):
155 ps["enforceStatus"] = ""
156 ps["enforceReason"] = ""
157 policy_status[policy_id] = ps
160 return Response(json.dumps(data), 200, mimetype=APPL_JSON)
163 headers['Location']='/A1-P/v2/policytypes/' + policy_type_id + '/policies/' + policy_id
164 return Response(json.dumps(data), 201, headers=headers, mimetype=APPL_JSON)
166 # API Function: Get a policy
167 def get_policy(policyTypeId, policyId):
169 extract_host_name(hosts_set, request)
171 if ((r := check_modified_response()) is not None):
174 policy_type_id=str(policyTypeId)
175 policy_id=str(policyId)
177 if (policy_type_id not in policy_types.keys()):
178 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
179 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
181 if (policy_id not in policy_instances[policy_type_id].keys()):
182 pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id)
183 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
185 return Response(json.dumps(policy_instances[policy_type_id][policy_id]), 200, mimetype=APPL_JSON)
187 # API Function: Delete a policy
188 def delete_policy(policyTypeId, policyId):
190 extract_host_name(hosts_set, request)
192 if ((r := check_modified_response()) is not None):
195 policy_type_id=str(policyTypeId)
196 policy_id=str(policyId)
198 if (policy_type_id not in policy_types.keys()):
199 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
200 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
202 if (policy_id not in policy_instances[policy_type_id].keys()):
203 pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id)
204 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
206 #Callout hooks for external server
207 #When it fails, break and return 419 HTTP status code
208 if (EXT_SRV_URL is not None):
209 resp = callout_external_server(policy_id, None, 'DELETE')
211 pjson=create_error_response(resp)
212 return Response(json.dumps(pjson), 500, mimetype=APPL_PROB_JSON)
214 if (is_duplicate_check()):
215 fp_previous=calcFingerprint(policy_instances[policy_type_id][policy_id], policy_type_id)
217 fp_previous=policy_id
219 policy_fingerprint.pop(fp_previous)
220 policy_instances[policy_type_id].pop(policy_id)
221 policy_status.pop(policy_id)
222 callbacks.pop(policy_id)
223 return Response('', 204, mimetype=APPL_JSON)
225 # API Function: Get status for a policy
226 def get_policy_status(policyTypeId, policyId):
228 extract_host_name(hosts_set, request)
230 if ((r := check_modified_response()) is not None):
233 policy_type_id=str(policyTypeId)
234 policy_id=str(policyId)
236 if (policy_type_id not in policy_types.keys()):
237 pjson=create_problem_json(None, "The policy type does not exist.", 404, None, policy_id)
238 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
240 if (policy_id not in policy_instances[policy_type_id].keys()):
241 pjson=create_problem_json(None, "The requested policy does not exist.", 404, None, policy_id)
242 return Response(json.dumps(pjson), 404, mimetype=APPL_PROB_JSON)
244 return Response(json.dumps(policy_status[policy_id]), status=200, mimetype=APPL_JSON)
246 # Helper: Callout external server to notify it for policy operations
247 # Returns 200, 201 and 204 for the success callout hooks, for the others returns 419
248 def callout_external_server(policy_id, payload, operation):
250 target_url=EXT_SRV_URL + policy_id
252 if (operation == 'PUT'):
253 #Suppress error when self-signed certificate is being used with verify flag
254 resp=requests.put(target_url, json=payload, timeout=10, verify=False)
255 return resp.status_code
256 elif (operation == 'DELETE'):
257 resp=requests.delete(target_url, timeout=10, verify=False)
258 return resp.status_code
260 #Return a generic unassigned HTTP status code as per iana, for all exceptions (419:Callout failed)
261 #https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
264 # Helper: Create a response object if forced http response code is set
265 def get_forced_response():
267 if (forced_settings['code'] is not None):
268 value=forced_settings['code']
269 pjson=create_error_response(int(value))
270 forced_settings['code']=None
271 return Response(json.dumps(pjson), pjson['status'], mimetype=APPL_PROB_JSON)
274 # Helper: Delay if delayed response code is set
277 if (forced_settings['delay'] is not None):
279 val=int(forced_settings['delay'])
284 # Helper: Check if response shall be delayed or a forced response shall be sent
285 def check_modified_response():
288 return get_forced_response()
290 # Helper: Create a problem json object
291 def create_problem_json(type_of, title, status, detail, instance):
294 if type_of is not None:
295 error["type"] = type_of
296 if title is not None:
297 error["title"] = title
298 if status is not None:
299 error["status"] = status
300 if detail is not None:
301 error["detail"] = detail
302 if instance is not None:
303 error["instance"] = instance
306 # Helper: Create a problem json based on a generic http response code
307 def create_error_response(code):
310 return(create_problem_json(None, "Bad request", 400, "Object in payload not properly formulated or not related to the method", None))
312 return(create_problem_json(None, "Not found", 404, "No resource found at the URI", None))
314 return(create_problem_json(None, "Method not allowed", 405, "Method not allowed for the URI", None))
316 return(create_problem_json(None, "Conflict", 409, "Request could not be processed in the current state of the resource", None))
318 return(create_problem_json(None, "Callout failed", 419, "Callout hooks could not be processed on the external server", None))
320 return(create_problem_json(None, "Too many requests", 429, "Too many requests have been sent in a given amount of time", None))
322 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))
324 return(create_problem_json(None, "Service unavailable", 503, "The provider is currently unable to handle the request due to a temporary overload", None))
326 return(create_problem_json(None, "Unknown", code, "Not implemented response code", None))