Coverage for jbank/wsedi.py: 0%

1# pylint: disable=logging-format-interpolation,logging-not-lazy,too-many-arguments,too-many-locals,too-many-statements,c-extension-no-member

2 import base64

3 import logging

4 import traceback

5 from datetime import date

6 from os.path import basename

7 from typing import Callable, Optional

8 import requests

9 from django.conf import settings

10 from django.template.loader import get_template

11 from django.utils.timezone import now

12 from django.utils.translation import gettext as _

13 from lxml import etree # type: ignore

14 from zeep.wsse import BinarySignature # type: ignore

15 from jbank.models import WsEdiConnection, WsEdiSoapCall

16

17 logger = logging.getLogger(__name__)

18

19

20 def 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 )

57

58 if res.status_code >= 300:

59 raise Exception("WS-EDI {} HTTP {}".format(command, res.status_code))

60 return res

61

62

63 def 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

96

97

98 def 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))

126

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")

135

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)

151

152 signed_app = ws.sign_application_request(app)

153 if verbose:

154 logger.info("------------------------------------------------------ {} signed_app\n{}".format(call_str, signed_app.decode()))

155

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 )

166

167 b64_app = ws.encode_application_request(enc_app)

168 if verbose:

169 logger.info("------------------------------------------------------ {} b64_app\n{}".format(call_str, b64_app.decode()))

170

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))

179

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))

192

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))

208

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()))

217

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 )

229

230 soap_call.executed = now()

231 soap_call.save(update_fields=["executed"])

232

233 if debug_output:

234 with open(soap_call.debug_response_full_path, "wb") as fp:

235 fp.write(app_res)

236

237 return app_res

238 except Exception:

239 soap_call.error = traceback.format_exc()

240 soap_call.save(update_fields=["error"])

241 raise