Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

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 ugettext as _ 

13from zeep.wsse import BinarySignature # type: ignore 

14from jbank.models import WsEdiConnection, WsEdiSoapCall 

15from lxml import etree # type: ignore # pytype: disable=import-error 

16 

17 

18logger = logging.getLogger(__name__) 

19 

20 

21def wsedi_get(command: str, file_type: str, status: str, file_reference: str = '', verbose: bool = False) -> requests.Response: 

22 """ 

23 Download Finnish bank files. Assumes WS-EDI API parameter compatible HTTP REST API end-point. 

24 Uses project settings WSEDI_URL and WSEDI_TOKEN. 

25 :param command: Command, e.g. DownloadFileList or DownloadFile 

26 :param file_type: File type, e.g. TO or SVM 

27 :param status: Status, e.g. DLD or NEW 

28 :param file_reference: File reference (if command is DownloadFile) 

29 :param verbose: Debug output 

30 :return: requests.Response 

31 """ 

32 url = settings.WSEDI_URL + '?command={command}'.format(command=command) 

33 if file_reference: 

34 url += '&file-reference=' + file_reference 

35 if file_type: 

36 url += '&file-type=' + file_type 

37 if status: 

38 url += '&status=' + status 

39 headers = { 

40 'Content-Type': 'application/json', 

41 'Authorization': 'Token ' + settings.WSEDI_TOKEN, 

42 } 

43 res = requests.get(url, headers=headers) 

44 if res.status_code >= 300: 

45 logger.error("wsedi_get(command={}, file_type={}, status={}, file_reference={}) response HTTP {}:\n".format( 

46 command, file_type, status, file_reference, res.status_code) + res.text) 

47 elif verbose: 

48 logger.info("wsedi_get(command={}, file_type={}, status={}, file_reference={}) response HTTP {}:\n".format( 

49 command, file_type, status, file_reference, res.status_code) + res.text) 

50 

51 if res.status_code >= 300: 

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

53 return res 

54 

55 

56def wsedi_upload_file(file_content: str, file_type: str, file_name: str, verbose: bool = False) -> requests.Response: 

57 """ 

58 Upload Finnish bank file. Assumes WS-EDI API parameter compatible HTTP REST API end-point. 

59 Uses project settings WSEDI_URL and WSEDI_TOKEN. 

60 :param file_content: File content 

61 :param file_type: File type, e.g. pain.001.001.03 

62 :param file_name: File (base) name 

63 :param verbose: Debug output 

64 :return: requests.Response 

65 """ 

66 command = 'UploadFile' 

67 url = settings.WSEDI_URL 

68 data = { 

69 'command': command, 

70 'file-type': file_type, 

71 'file-name': basename(file_name), 

72 'file-content': base64.b64encode(file_content.encode('utf8')).decode('ascii'), 

73 } 

74 headers = { 

75 'Content-Type': 'application/x-www-form-urlencoded', 

76 'Authorization': 'Token ' + settings.WSEDI_TOKEN, 

77 } 

78 res = requests.post(url, data=data, headers=headers) 

79 if res.status_code >= 300: 

80 logger.error("wsedi_upload_file(command={}, file_type={}, file_name={}) response HTTP {}:\n".format( 

81 command, file_type, file_name, res.status_code) + res.text) 

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

83 if verbose: 

84 logger.info("wsedi_upload_file(command={}, file_type={}, file_name={}) response HTTP {}:\n".format( 

85 command, file_type, file_name, res.status_code) + res.text) 

86 return res 

87 

88 

89def wsedi_execute(ws: WsEdiConnection, command: str, file_type: str = '', status: str = '', file_reference: str = '', # noqa 

90 file_content: str = '', start_date: Optional[date] = None, end_date: Optional[date] = None, 

91 verbose: bool = False, cls: Callable = WsEdiSoapCall, **kwargs) -> bytes: 

92 """ 

93 :param ws: 

94 :param command: 

95 :param file_type: 

96 :param status: 

97 :param file_reference: 

98 :param file_content: 

99 :param start_date: 

100 :param end_date: 

101 :param verbose: 

102 :param cls: 

103 :return: str 

104 """ 

105 if ws and not ws.enabled: 

106 raise Exception(_('ws.edi.connection.not.enabled').format(ws=ws)) 

107 

108 soap_call = cls(connection=ws, command=command, **kwargs) 

109 soap_call.full_clean() 

110 soap_call.save() 

111 call_str = 'WsEdiSoapCall({})'.format(soap_call.id) 

112 try: 

113 content = '' 

114 if file_content: 

115 content = base64.b64encode(file_content.encode()).decode('ascii') 

116 

117 app = ws.get_application_request(command, file_type=file_type, status=status, file_reference=file_reference, content=content, 

118 start_date=start_date, end_date=end_date) 

119 if verbose: 

120 logger.info('------------------------------------------------------ {} app\n{}'.format(call_str, app.decode())) 

121 debug_output = command in ws.debug_command_list or 'ALL' in ws.debug_command_list 

122 if debug_output: 

123 with open(soap_call.debug_request_full_path, 'wb') as fp: 

124 fp.write(app) 

125 

126 signed_app = ws.sign_application_request(app) 

127 if verbose: 

128 logger.info('------------------------------------------------------ {} signed_app\n{}'.format(call_str, signed_app.decode())) 

129 

130 if ws.bank_encryption_cert_file: 

131 enc_app = ws.encrypt_application_request(signed_app) 

132 if verbose: 

133 logger.info('------------------------------------------------------ {} enc_app\n{}'.format(call_str, enc_app.decode())) 

134 else: 

135 enc_app = signed_app 

136 if verbose: 

137 logger.info('------------------------------------------------------ ' 

138 '{} enc_app\n(no bank_encryption_cert_file, not encrypting)'.format(call_str)) 

139 

140 b64_app = ws.encode_application_request(enc_app) 

141 if verbose: 

142 logger.info('------------------------------------------------------ {} b64_app\n{}'.format(call_str, b64_app.decode())) 

143 

144 soap_body = get_template('jbank/soap_template.xml').render({ 

145 'soap_call': soap_call, 

146 'payload': b64_app.decode(), 

147 }) 

148 if verbose: 

149 logger.info('------------------------------------------------------ {} soap_body\n{}'.format(call_str, soap_body)) 

150 

151 body_bytes = soap_body.encode() 

152 envelope = etree.fromstring(body_bytes) 

153 binary_signature = BinarySignature(ws.signing_key_full_path, ws.signing_cert_full_path) 

154 soap_headers: dict = {} 

155 envelope, soap_headers = binary_signature.apply(envelope, soap_headers) 

156 signed_body_bytes = etree.tostring(envelope) 

157 if verbose: 

158 logger.info('------------------------------------------------------ {} signed_body_bytes\n{}'.format(call_str, signed_body_bytes)) 

159 

160 http_headers = { 

161 'Connection': 'Close', 

162 'Content-Type': 'text/xml', 

163 'Method': 'POST', 

164 'SOAPAction': '', 

165 'User-Agent': 'Kajala WS', 

166 } 

167 if verbose: 

168 logger.info('HTTP POST {}'.format(ws.soap_endpoint)) 

169 res = requests.post(ws.soap_endpoint, data=signed_body_bytes, headers=http_headers) 

170 if verbose: 

171 logger.info('------------------------------------------------------ {} HTTP response {}\n{}'.format(call_str, res.status_code, res.text)) 

172 if res.status_code >= 300: 

173 logger.error('------------------------------------------------------ {} HTTP response {}\n{}'.format(call_str, res.status_code, res.text)) 

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

175 

176 envelope = etree.fromstring(res.content) 

177 app_res_el = envelope.find('.//{http://model.bxd.fi}ApplicationResponse') 

178 if app_res_el is None: 

179 logger.error('------------------------------------------------------ {} HTTP response {}\n{}'.format(call_str, res.status_code, res.text)) 

180 raise Exception("WS-EDI {} failed, missing ApplicationResponse".format(command)) 

181 app_res_enc = ws.decode_application_response(app_res_el.text.encode()) 

182 if verbose: 

183 logger.info('------------------------------------------------------ {} app_res_enc\n{}'.format(call_str, app_res_enc.decode())) 

184 

185 if ws.encryption_key_file: 

186 app_res = ws.decrypt_application_response(app_res_enc) 

187 if verbose: 

188 logger.info('------------------------------------------------------ {} app_res\n{}'.format(call_str, app_res.decode())) 

189 else: 

190 app_res = app_res_enc 

191 if verbose: 

192 logger.info('------------------------------------------------------ ' 

193 '{} app_res\n(no encryption_key_file, assuming decrypted content)'.format(call_str)) 

194 

195 soap_call.executed = now() 

196 soap_call.save(update_fields=['executed']) 

197 

198 if debug_output: 

199 with open(soap_call.debug_response_full_path, 'wb') as fp: 

200 fp.write(app_res) 

201 

202 return app_res 

203 except Exception: 

204 soap_call.error = traceback.format_exc() 

205 soap_call.save(update_fields=['error']) 

206 raise