Coverage for jbank/wsedi.py: 0%
120 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-27 13:36 +0700
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-27 13:36 +0700
1# pylint: disable=logging-format-interpolation,logging-not-lazy,too-many-arguments,too-many-locals,too-many-statements,c-extension-no-member
2import base64
3import logging
4import traceback
5from datetime import date
6from os.path import basename
7from typing import Callable, Optional
8import requests
9from django.conf import settings
10from django.template.loader import get_template
11from django.utils.timezone import now
12from django.utils.translation import gettext as _
13from lxml import etree # type: ignore
14from zeep.wsse import BinarySignature # type: ignore
15from jbank.models import WsEdiConnection, WsEdiSoapCall
17logger = logging.getLogger(__name__)
20def wsedi_get(command: str, file_type: str, status: str, file_reference: str = "", verbose: bool = False) -> requests.Response:
21 """
22 Download Finnish bank files. Assumes WS-EDI API parameter compatible HTTP REST API end-point.
23 Uses project settings WSEDI_URL and WSEDI_TOKEN.
24 :param command: Command, e.g. DownloadFileList or DownloadFile
25 :param file_type: File type, e.g. TO or SVM
26 :param status: Status, e.g. DLD or NEW
27 :param file_reference: File reference (if command is DownloadFile)
28 :param verbose: Debug output
29 :return: requests.Response
30 """
31 url = settings.WSEDI_URL + "?command={command}".format(command=command)
32 if file_reference:
33 url += "&file-reference=" + file_reference
34 if file_type:
35 url += "&file-type=" + file_type
36 if status:
37 url += "&status=" + status
38 headers = {
39 "Content-Type": "application/json",
40 "Authorization": "Token " + settings.WSEDI_TOKEN,
41 }
42 res = requests.get(url, headers=headers)
43 if res.status_code >= 300:
44 logger.error(
45 "wsedi_get(command={}, file_type={}, status={}, file_reference={}) response HTTP {}:\n".format(
46 command, file_type, status, file_reference, res.status_code
47 )
48 + res.text
49 )
50 elif verbose:
51 logger.info(
52 "wsedi_get(command={}, file_type={}, status={}, file_reference={}) response HTTP {}:\n".format(
53 command, file_type, status, file_reference, res.status_code
54 )
55 + res.text
56 )
58 if res.status_code >= 300:
59 raise Exception("WS-EDI {} HTTP {}".format(command, res.status_code))
60 return res
63def wsedi_upload_file(file_content: str, file_type: str, file_name: str, verbose: bool = False) -> requests.Response:
64 """
65 Upload Finnish bank file. Assumes WS-EDI API parameter compatible HTTP REST API end-point.
66 Uses project settings WSEDI_URL and WSEDI_TOKEN.
67 :param file_content: File content
68 :param file_type: File type, e.g. pain.001.001.03
69 :param file_name: File (base) name
70 :param verbose: Debug output
71 :return: requests.Response
72 """
73 command = "UploadFile"
74 url = settings.WSEDI_URL
75 data = {
76 "command": command,
77 "file-type": file_type,
78 "file-name": basename(file_name),
79 "file-content": base64.b64encode(file_content.encode("utf8")).decode("ascii"),
80 }
81 headers = {
82 "Content-Type": "application/x-www-form-urlencoded",
83 "Authorization": "Token " + settings.WSEDI_TOKEN,
84 }
85 res = requests.post(url, data=data, headers=headers)
86 if res.status_code >= 300:
87 logger.error(
88 "wsedi_upload_file(command={}, file_type={}, file_name={}) response HTTP {}:\n".format(command, file_type, file_name, res.status_code) + res.text
89 )
90 raise Exception("WS-EDI {} HTTP {}".format(command, res.status_code))
91 if verbose:
92 logger.info(
93 "wsedi_upload_file(command={}, file_type={}, file_name={}) response HTTP {}:\n".format(command, file_type, file_name, res.status_code) + res.text
94 )
95 return res
98def wsedi_execute( # noqa
99 ws: WsEdiConnection,
100 command: str,
101 file_type: str = "",
102 status: str = "",
103 file_reference: str = "", # noqa
104 file_content: str = "",
105 start_date: Optional[date] = None,
106 end_date: Optional[date] = None,
107 verbose: bool = False,
108 cls: Callable = WsEdiSoapCall,
109 **kwargs
110) -> bytes:
111 """
112 :param ws:
113 :param command:
114 :param file_type:
115 :param status:
116 :param file_reference:
117 :param file_content:
118 :param start_date:
119 :param end_date:
120 :param verbose:
121 :param cls:
122 :return: bytes
123 """
124 if ws and not ws.enabled:
125 raise Exception(_("ws.edi.connection.not.enabled").format(ws=ws))
127 soap_call = cls(connection=ws, command=command, **kwargs)
128 soap_call.full_clean()
129 soap_call.save()
130 call_str = "WsEdiSoapCall({})".format(soap_call.id)
131 try:
132 content = ""
133 if file_content:
134 content = base64.b64encode(file_content.encode()).decode("ascii")
136 app = ws.get_application_request(
137 command,
138 file_type=file_type,
139 status=status,
140 file_reference=file_reference,
141 content=content,
142 start_date=start_date,
143 end_date=end_date,
144 )
145 if verbose:
146 logger.info("------------------------------------------------------ {} app\n{}".format(call_str, app.decode()))
147 debug_output = command in ws.debug_command_list or "ALL" in ws.debug_command_list
148 if debug_output:
149 with open(soap_call.debug_request_full_path, "wb") as fp:
150 fp.write(app)
152 signed_app = ws.sign_application_request(app)
153 if verbose:
154 logger.info("------------------------------------------------------ {} signed_app\n{}".format(call_str, signed_app.decode()))
156 if ws.bank_encryption_cert_file:
157 enc_app = ws.encrypt_application_request(signed_app)
158 if verbose:
159 logger.info("------------------------------------------------------ {} enc_app\n{}".format(call_str, enc_app.decode()))
160 else:
161 enc_app = signed_app
162 if verbose:
163 logger.info(
164 "------------------------------------------------------ " "{} enc_app\n(no bank_encryption_cert_file, not encrypting)".format(call_str)
165 )
167 b64_app = ws.encode_application_request(enc_app)
168 if verbose:
169 logger.info("------------------------------------------------------ {} b64_app\n{}".format(call_str, b64_app.decode()))
171 soap_body = get_template("jbank/soap_template.xml").render(
172 {
173 "soap_call": soap_call,
174 "payload": b64_app.decode(),
175 }
176 )
177 if verbose:
178 logger.info("------------------------------------------------------ {} soap_body\n{}".format(call_str, soap_body))
180 body_bytes = soap_body.encode()
181 envelope = etree.fromstring(body_bytes)
182 binary_signature = BinarySignature(ws.signing_key_full_path, ws.signing_cert_full_path)
183 soap_headers: dict = {}
184 # print(f"BEFORE signing with {ws.signing_key_full_path} and {ws.signing_cert_full_path}")
185 # with open("/home/jani/Downloads/e.xml", "wb") as fp:
186 # fp.write(etree.tostring(envelope))
187 # print(etree.tostring(envelope).decode())
188 envelope, soap_headers = binary_signature.apply(envelope, soap_headers)
189 signed_body_bytes = etree.tostring(envelope)
190 if verbose:
191 logger.info("------------------------------------------------------ {} signed_body_bytes\n{}".format(call_str, signed_body_bytes))
193 http_headers = {
194 "Connection": "Close",
195 "Content-Type": "text/xml",
196 "Method": "POST",
197 "SOAPAction": "",
198 "User-Agent": "Kajala WS",
199 }
200 if verbose:
201 logger.info("HTTP POST {}".format(ws.soap_endpoint))
202 res = requests.post(ws.soap_endpoint, data=signed_body_bytes, headers=http_headers)
203 if verbose:
204 logger.info("------------------------------------------------------ {} HTTP response {}\n{}".format(call_str, res.status_code, res.text))
205 if res.status_code >= 300:
206 logger.error("------------------------------------------------------ {} HTTP response {}\n{}".format(call_str, res.status_code, res.text))
207 raise Exception("WS-EDI {} HTTP {}".format(command, res.status_code))
209 envelope = etree.fromstring(res.content)
210 app_res_el = envelope.find(".//{http://model.bxd.fi}ApplicationResponse")
211 if app_res_el is None:
212 logger.error("------------------------------------------------------ {} HTTP response {}\n{}".format(call_str, res.status_code, res.text))
213 raise Exception("WS-EDI {} failed, missing ApplicationResponse".format(command))
214 app_res_enc = ws.decode_application_response(app_res_el.text.encode())
215 if verbose:
216 logger.info("------------------------------------------------------ {} app_res_enc\n{}".format(call_str, app_res_enc.decode()))
218 if ws.encryption_key_file and ws.encryption_cert_file:
219 app_res = ws.decrypt_application_response(app_res_enc)
220 if verbose:
221 logger.info("------------------------------------------------------ {} app_res\n{}".format(call_str, app_res.decode()))
222 else:
223 app_res = app_res_enc
224 if verbose:
225 logger.info(
226 "------------------------------------------------------ "
227 "{} app_res\n(no encryption_key_file or encryption_cert_file, assuming decrypted content)".format(call_str)
228 )
230 soap_call.executed = now()
231 soap_call.save(update_fields=["executed"])
233 if debug_output:
234 with open(soap_call.debug_response_full_path, "wb") as fp:
235 fp.write(app_res)
237 return app_res
238 except Exception:
239 soap_call.error = traceback.format_exc()
240 soap_call.save(update_fields=["error"])
241 raise