Coverage for jbank/wsedi.py: 0%

120 statements  

« 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 

16 

17logger = logging.getLogger(__name__) 

18 

19 

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 ) 

57 

58 if res.status_code >= 300: 

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

60 return res 

61 

62 

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 

96 

97 

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

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