sepafm 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +14 -30
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +15 -0
  5. data/Gemfile +1 -1
  6. data/LICENSE +16 -4
  7. data/README.md +180 -319
  8. data/Rakefile +7 -2
  9. data/lib/sepa/application_request.rb +100 -131
  10. data/lib/sepa/application_response.rb +28 -84
  11. data/lib/sepa/attribute_checks.rb +169 -0
  12. data/lib/sepa/banks/danske/danske_response.rb +19 -0
  13. data/lib/sepa/banks/danske/soap_danske.rb +132 -0
  14. data/lib/sepa/banks/nordea/nordea_response.rb +20 -0
  15. data/lib/sepa/banks/nordea/soap_nordea.rb +51 -0
  16. data/lib/sepa/client.rb +72 -60
  17. data/lib/sepa/error_messages.rb +15 -0
  18. data/lib/sepa/response.rb +88 -85
  19. data/lib/sepa/soap_builder.rb +51 -341
  20. data/lib/sepa/utilities.rb +132 -0
  21. data/lib/sepa/version.rb +1 -1
  22. data/lib/sepa/xml_schemas/PKIFactory.xsd +334 -0
  23. data/lib/sepa/xml_schemas/xml_id.xsd +9 -0
  24. data/lib/sepa/xml_templates/application_request/create_certificate.xml +15 -10
  25. data/lib/sepa/xml_templates/application_request/danske_get_bank_certificate.xml +13 -9
  26. data/lib/sepa/xml_templates/application_request/download_file.xml +32 -30
  27. data/lib/sepa/xml_templates/application_request/download_file_list.xml +29 -27
  28. data/lib/sepa/xml_templates/application_request/encrypted_request.xml +22 -0
  29. data/lib/sepa/xml_templates/application_request/get_certificate.xml +9 -8
  30. data/lib/sepa/xml_templates/application_request/get_user_info.xml +26 -24
  31. data/lib/sepa/xml_templates/application_request/upload_file.xml +29 -27
  32. data/lib/sepa/xml_templates/soap/create_certificate.xml +17 -15
  33. data/lib/sepa/xml_templates/soap/danske_get_bank_certificate.xml +15 -13
  34. data/lib/sepa/xml_templates/soap/download_file.xml +19 -15
  35. data/lib/sepa/xml_templates/soap/download_file_list.xml +19 -15
  36. data/lib/sepa/xml_templates/soap/get_certificate.xml +2 -1
  37. data/lib/sepa/xml_templates/soap/get_user_info.xml +19 -15
  38. data/lib/sepa/xml_templates/soap/header.xml +48 -37
  39. data/lib/sepa/xml_templates/soap/upload_file.xml +19 -15
  40. data/lib/sepafm.rb +20 -18
  41. data/{sepa.gemspec → sepafm.gemspec} +10 -9
  42. data/test/sepa/banks/danske/danske_cert_response_test.rb +52 -0
  43. data/test/sepa/banks/danske/danske_cert_soap_builder_test.rb +100 -0
  44. data/test/sepa/banks/danske/danske_generic_soap_builder_test.rb +278 -0
  45. data/test/sepa/banks/danske/danske_get_bank_cert_test.rb +111 -0
  46. data/{lib/sepa/danske_testing/keys/danske_encryption.crt → test/sepa/banks/danske/keys/bank_encryption_cert.pem} +0 -0
  47. data/test/sepa/{danske_test_keys/danskeroot.pem → banks/danske/keys/bank_root_cert.pem} +1 -1
  48. data/test/sepa/banks/danske/keys/bank_signing_cert.pem +24 -0
  49. data/test/sepa/banks/danske/keys/danske_encryption.crt +24 -0
  50. data/test/sepa/banks/danske/keys/enc_private_key.pem +27 -0
  51. data/test/sepa/{danske_test_keys → banks/danske/keys}/encryption_pkcs.csr +0 -0
  52. data/test/sepa/banks/danske/keys/own_enc_cert.pem +21 -0
  53. data/test/sepa/banks/danske/keys/own_signing_cert.pem +22 -0
  54. data/test/sepa/{danske_test_keys → banks/danske/keys}/signing_key.pem +0 -0
  55. data/test/sepa/{danske_test_keys → banks/danske/keys}/signing_pkcs.csr +0 -0
  56. data/test/sepa/banks/danske/keys/signing_private_key.pem +27 -0
  57. data/test/sepa/banks/danske/responses/create_cert.xml +38 -0
  58. data/test/sepa/banks/danske/responses/get_bank_cert.xml +37 -0
  59. data/{lib/sepa/nordea_testing → test/sepa/banks/nordea}/keys/nordea.crt +0 -4
  60. data/test/sepa/{nordea_test_keys → banks/nordea/keys}/nordea.key +0 -3
  61. data/test/sepa/{nordea_test_keys → banks/nordea/keys}/root_cert.cer +0 -0
  62. data/test/sepa/{nordea_test_keys → banks/nordea/keys}/testcert.csr +0 -0
  63. data/test/sepa/banks/nordea/nordea_application_request_test.rb +252 -0
  64. data/test/sepa/{application_response_test.rb → banks/nordea/nordea_application_response_test.rb} +40 -46
  65. data/test/sepa/banks/nordea/nordea_cert_application_request_test.rb +72 -0
  66. data/test/sepa/banks/nordea/nordea_cert_request_soap_builder_test.rb +65 -0
  67. data/test/sepa/banks/nordea/nordea_generic_soap_builder_test.rb +280 -0
  68. data/test/sepa/banks/nordea/nordea_response_test.rb +116 -0
  69. data/test/sepa/banks/nordea/responses/df_ktl.xml +45 -0
  70. data/test/sepa/{test_files/test_responses/df.xml → banks/nordea/responses/df_tito.xml} +1 -1
  71. data/test/sepa/{test_files/test_responses → banks/nordea/responses}/dfl.xml +0 -0
  72. data/test/sepa/banks/nordea/responses/gbc.xml +15 -0
  73. data/test/sepa/banks/nordea/responses/gc.xml +49 -0
  74. data/test/sepa/{test_files/test_responses → banks/nordea/responses}/gui.xml +0 -0
  75. data/test/sepa/{test_files/test_responses → banks/nordea/responses}/uf.xml +0 -0
  76. data/test/sepa/client_test.rb +156 -302
  77. data/test/sepa/fixtures.rb +214 -0
  78. data/test/sepa/sepa_test.rb +3 -13
  79. data/test/sepa/test_files/{invalid.wsdl → invalid_wsdl.wsdl} +0 -0
  80. data/test/test_helper.rb +29 -3
  81. metadata +140 -116
  82. data/lib/danske_get_bank_certificate_test.rb +0 -15
  83. data/lib/sepa/custom_exceptions.rb +0 -2
  84. data/lib/sepa/filedescriptor.rb +0 -7
  85. data/lib/sepa/filetypeservice.rb +0 -6
  86. data/lib/sepa/nordea_testing/keys/CSR.csr +0 -0
  87. data/lib/sepa/nordea_testing/keys/nordea.key +0 -19
  88. data/lib/sepa/nordea_testing/response/content_053.xml +0 -998
  89. data/lib/sepa/nordea_testing/response/content_054.xml +0 -1
  90. data/lib/sepa/nordea_testing/response/download_file_response.xml +0 -14
  91. data/lib/sepa/nordea_testing/response/download_filelist_response.xml +0 -14
  92. data/lib/sepa/nordea_testing/response/get_user_info_response.xml +0 -14
  93. data/lib/sepa/nordea_testing/response/upload_file_response.xml +0 -14
  94. data/lib/sepa/payload.rb +0 -109
  95. data/lib/sepa/payment.rb +0 -97
  96. data/lib/sepa/sender_verifier.rb +0 -15
  97. data/lib/sepa/signature.rb +0 -7
  98. data/lib/sepa/soap_danske.rb +0 -47
  99. data/lib/sepa/soap_nordea.rb +0 -68
  100. data/lib/sepa/transaction.rb +0 -178
  101. data/lib/sepa/userfiletype.rb +0 -16
  102. data/lib/sepa/xml_parser.rb +0 -291
  103. data/lib/sepa_client_testing_mika.rb +0 -32
  104. data/lib/sepa_client_testing_tiere.rb +0 -257
  105. data/test/sepa/application_request_test.rb +0 -423
  106. data/test/sepa/cert_application_request_test.rb +0 -99
  107. data/test/sepa/nordea_cert_request_soap_builder_test.rb +0 -112
  108. data/test/sepa/nordea_generic_soap_builder_test.rb +0 -427
  109. data/test/sepa/nordea_test_keys/nordea.crt +0 -27
  110. data/test/sepa/payload_test.rb +0 -297
  111. data/test/sepa/payment_test.rb +0 -198
  112. data/test/sepa/response_test.rb +0 -269
  113. data/test/sepa/transaction_test.rb +0 -362
  114. data/test/sepa/user_file_type_test.rb +0 -21
  115. data/test/sepa/xml_parser_test.rb +0 -73
@@ -0,0 +1,132 @@
1
+ module Sepa
2
+ module DanskeSoapRequest
3
+
4
+ def find_correct_build
5
+ case @command
6
+ when :create_certificate
7
+ build_certificate_request
8
+ when :upload_file, :download_file, :get_user_info, :download_file_list
9
+ build_danske_generic_request
10
+ when :get_bank_certificate
11
+ build_get_bank_certificate_request
12
+ end
13
+ end
14
+
15
+ def encrypt_application_request
16
+ encryption_cert = OpenSSL::X509::Certificate.new(@enc_cert)
17
+ encryption_public_key = encryption_cert.public_key
18
+ encryption_cert = format_cert(encryption_cert)
19
+ encrypted_ar, key = encrypt_ar
20
+ encrypted_key = encrypt_key(key, encryption_public_key)
21
+ build_encrypted_ar(encryption_cert, encrypted_key, encrypted_ar)
22
+ end
23
+
24
+ # Encrypts a given symmetric encryption key with a public key and returns it in base64 encoded
25
+ # format
26
+ def encrypt_key(key, public_key)
27
+ encrypted_key = public_key.public_encrypt(key)
28
+ Base64.encode64(encrypted_key)
29
+ end
30
+
31
+ # Encrypts the application request and returns it in base64 encoded format.
32
+ # Also returns the key needed to decrypt it
33
+ def encrypt_ar
34
+ cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').encrypt
35
+
36
+ key = cipher.random_key
37
+ iv = cipher.random_iv
38
+
39
+ encrypted_data = cipher.update(@ar.to_xml)
40
+ encrypted_data << cipher.final
41
+ encrypted_data = iv + encrypted_data
42
+ encrypted_data = Base64.encode64(encrypted_data)
43
+
44
+ return encrypted_data, key
45
+ end
46
+
47
+ def build_encrypted_ar(cert, encrypted_data, encrypted_key)
48
+ ar = Nokogiri::XML File.open "#{AR_TEMPLATE_PATH}/encrypted_request.xml"
49
+ set_node(ar, 'dsig|X509Certificate', cert)
50
+ set_node(ar, 'dsig|KeyInfo xenc|CipherValue', encrypted_data)
51
+ set_node(ar, 'xenc|EncryptedData > xenc|CipherData > xenc|CipherValue', encrypted_key)
52
+ ar
53
+ end
54
+
55
+ def set_generic_request_contents
56
+ set_node(@template, 'bxd|SenderId', @customer_id)
57
+ set_node(@template, 'bxd|RequestId', request_id)
58
+ set_node(@template, 'bxd|Timestamp', iso_time)
59
+ set_node(@template, 'bxd|Language', @language)
60
+ set_node(@template, 'bxd|UserAgent', "Sepa Transfer Library version " + VERSION)
61
+ set_node(@template, 'bxd|ReceiverId', @target_id)
62
+ end
63
+
64
+ def set_create_cert_contents
65
+ set_node(@template, 'pkif|SenderId', @customer_id)
66
+ set_node(@template, 'pkif|CustomerId', @customer_id)
67
+ set_node(@template, 'pkif|RequestId', request_id)
68
+ set_node(@template, 'pkif|Timestamp', iso_time)
69
+ set_node(@template, 'pkif|InterfaceVersion', 1)
70
+ set_node(@template, 'pkif|Environment', @environment)
71
+ end
72
+
73
+ def set_bank_certificate_contents
74
+ set_node(@template, 'pkif|SenderId', @customer_id)
75
+ set_node(@template, 'pkif|CustomerId', @customer_id)
76
+ set_node(@template, 'pkif|RequestId', request_id)
77
+ set_node(@template, 'pkif|Timestamp', iso_time)
78
+ set_node(@template, 'pkif|InterfaceVersion', 1)
79
+ end
80
+
81
+ def build_danske_generic_request
82
+ set_generic_request_contents
83
+ encrypted_request = encrypt_application_request
84
+ add_encrypted_generic_request_to_soap(encrypted_request)
85
+
86
+ process_header
87
+ add_body_to_header
88
+ end
89
+
90
+ def build_certificate_request
91
+ set_create_cert_contents
92
+ encrypted_request = encrypt_application_request
93
+ add_encrypted_request_to_soap(encrypted_request)
94
+ end
95
+
96
+ def build_get_bank_certificate_request
97
+ set_bank_certificate_contents
98
+ add_bank_certificate_body_to_soap
99
+ end
100
+
101
+ def add_encrypted_request_to_soap(encrypted_request)
102
+ encrypted_request = Nokogiri::XML(encrypted_request.to_xml)
103
+ encrypted_request = encrypted_request.root
104
+ @template.at_css('pkif|CreateCertificateIn').add_child(encrypted_request)
105
+
106
+ @template
107
+ end
108
+
109
+ def add_encrypted_generic_request_to_soap(encrypted_request)
110
+ encrypted_request = Nokogiri::XML(encrypted_request.to_xml)
111
+ encrypted_request = encrypted_request.root
112
+ encrypted_request = Base64.encode64(encrypted_request.to_xml)
113
+ @template.at_css('bxd|ApplicationRequest').add_child(encrypted_request)
114
+
115
+ @template
116
+ end
117
+
118
+ def add_bank_certificate_body_to_soap
119
+ ar = @ar.to_nokogiri
120
+
121
+ ar = ar.at_css('elem|GetBankCertificateRequest')
122
+ @template.at_css('pkif|GetBankCertificateIn').add_child(ar)
123
+
124
+ @template
125
+ end
126
+
127
+ def request_id
128
+ SecureRandom.hex(5)
129
+ end
130
+
131
+ end
132
+ end
@@ -0,0 +1,20 @@
1
+ module Sepa
2
+ class NordeaResponse < Response
3
+
4
+ def initialize(response, command: nil)
5
+ super
6
+
7
+ if @command == :get_certificate
8
+ @application_response = extract_application_response('http://bxd.fi/CertificateService')
9
+ @content = extract_own_cert
10
+ end
11
+ end
12
+
13
+ def extract_own_cert
14
+ node = Nokogiri::XML(@application_response)
15
+ .at('xmlns|Certificate > xmlns|Certificate', xmlns: 'http://filetransfer.nordea.com/xmldata/')
16
+
17
+ Base64.encode64(OpenSSL::X509::Certificate.new(process_cert_value(node.content)).to_s) if node
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,51 @@
1
+ module Sepa
2
+ module NordeaSoapRequest
3
+
4
+ private
5
+
6
+ def find_correct_build
7
+ case @command
8
+ when :get_certificate
9
+ build_certificate_request
10
+ when :get_user_info, :download_file_list, :download_file, :upload_file
11
+ build_common_request
12
+ end
13
+ end
14
+
15
+ # Builds : Get Certificate
16
+ def build_certificate_request
17
+ set_body_contents
18
+ end
19
+
20
+ def set_body_contents
21
+ set_node(@template, 'cer|ApplicationRequest', @ar.to_base64)
22
+ set_node(@template, 'cer|SenderId', @customer_id)
23
+ set_node(@template, 'cer|RequestId', request_id)
24
+ set_node(@template, 'cer|Timestamp', iso_time)
25
+
26
+ @template
27
+ end
28
+
29
+ # Builds : Get User Info, Download File, Download File List, Upload File
30
+ def build_common_request
31
+ common_set_body_contents
32
+ process_header
33
+ add_body_to_header
34
+ end
35
+
36
+ def common_set_body_contents
37
+ set_node(@template, 'bxd|ApplicationRequest', @ar.to_base64)
38
+ set_node(@template, 'bxd|SenderId', @customer_id)
39
+ set_node(@template, 'bxd|RequestId', request_id)
40
+ set_node(@template, 'bxd|Timestamp', iso_time)
41
+ set_node(@template, 'bxd|Language', @language)
42
+ set_node(@template, 'bxd|UserAgent', "Sepa Transfer Library version #{VERSION}")
43
+ set_node(@template, 'bxd|ReceiverId', @target_id)
44
+ end
45
+
46
+ def request_id
47
+ SecureRandom.hex(17)
48
+ end
49
+
50
+ end
51
+ end
data/lib/sepa/client.rb CHANGED
@@ -1,79 +1,91 @@
1
1
  module Sepa
2
2
  class Client
3
- # Check that parameters are valid, initialize savon client with them and
4
- # construct soap message
5
- def initialize(params)
6
- check_params_hash(params)
7
- check_bank(params.fetch(:bank))
8
- bank = params.fetch(:bank)
9
-
10
- wsdl = find_proper_wsdl(bank, params.fetch(:command))
11
-
12
- @client = Savon.client(wsdl: wsdl) #log_level: :info
13
- @command = params.fetch(:command)
14
- # SoapBuilder creates a complete SOAP message structure
15
- @soap = SoapBuilder.new(params).to_xml
3
+ include ActiveModel::Validations
4
+ include Utilities
5
+ include ErrorMessages
6
+ include AttributeChecks
7
+
8
+ attr_accessor :bank, :cert, :command, :content, :customer_id, :enc_cert,
9
+ :encryption_cert_pkcs10, :environment, :file_reference,
10
+ :file_type, :key_generator_type, :language, :pin, :private_key,
11
+ :signing_cert_pkcs10, :status, :target_id, :csr, :service, :bank_root_cert_serial
12
+
13
+ BANKS = [:nordea, :danske]
14
+ LANGUAGES = ['FI', 'SE', 'EN']
15
+
16
+ validates :bank, inclusion: { in: BANKS }
17
+ validates :language, inclusion: { in: LANGUAGES }, allow_nil: true
18
+
19
+ validate :check_status
20
+ validate :check_customer_id
21
+ validate :check_file_type
22
+ validate :check_environment
23
+ validate :check_target_id
24
+ validate :check_content
25
+ validate :check_pin
26
+ validate :check_command
27
+ validate :check_wsdl
28
+ validate :check_keys
29
+ validate :check_enc_cert
30
+ validate :check_encryption_cert_request
31
+ validate :check_signing_cert
32
+ validate :check_bank_root_cert_serial
33
+ validate :check_file_reference
34
+
35
+ def initialize(hash = {})
36
+ self.attributes hash
37
+ self.environment ||= 'PRODUCTION'
16
38
  end
17
39
 
18
- # Call savon to make the soap request with the correct command and the
19
- # the constructed soap. The returned object will be a savon response.
20
- def send
21
- @client.call(@command, xml: @soap)
40
+ def bank=(value)
41
+ @bank = value.to_sym
22
42
  end
23
43
 
24
- private
44
+ def command=(value)
45
+ @command = value.to_sym
46
+ end
25
47
 
26
- def check_bank(bank)
27
- unless [:nordea, :danske].include?(bank)
28
- fail ArgumentError, "You didn't provide a proper bank. " \
29
- "Acceptable values are nordea OR danske."
30
- end
48
+ def attributes(hash)
49
+ hash.each do |name, value|
50
+ send("#{name}=", value)
31
51
  end
52
+ end
32
53
 
33
- def find_proper_wsdl(bank, command)
34
- wsdlpath = File.expand_path('../../../lib/sepa/wsdl', __FILE__)
35
- case bank
36
- when :nordea
37
- if command == :get_certificate
38
- path = "#{wsdlpath}/wsdl_nordea_cert.xml"
39
- else
40
- path = "#{wsdlpath}/wsdl_nordea.xml"
41
- end
42
- when :danske
43
- if command == :get_bank_certificate
44
- path = "#{wsdlpath}/wsdl_danske_cert.xml"
45
- else
46
- path = "#{wsdlpath}/wsdl_danske.xml"
47
- end
48
- end
49
- check_wsdl(path)
50
- path
51
- end
54
+ def send_request
55
+ raise ArgumentError, errors.messages unless valid?
52
56
 
53
- def check_params_hash(params)
54
- unless params.respond_to?(:each_pair)
55
- fail ArgumentError, "You didn't provide a proper hash"
56
- end
57
+ soap = SoapBuilder.new(create_hash).to_xml
58
+ client = Savon.client(wsdl: wsdl)
59
+ response = client.call(command, xml: soap).doc
60
+
61
+ case bank
62
+ when :nordea
63
+ NordeaResponse.new response, command: command
64
+ when :danske
65
+ DanskeResponse.new response, command: command
57
66
  end
67
+ end
68
+
69
+ private
70
+
71
+ def create_hash
72
+ initialize_private_key
73
+ iv = {}
58
74
 
59
- def check_wsdl(wsdl)
60
- schema_file = File.expand_path('../../../lib/sepa/xml_schemas/wsdl.xml',
61
- __FILE__)
62
- xsd = Nokogiri::XML::Schema(File.read(schema_file))
75
+ # Create hash of all instance variables
76
+ instance_variables.map do |name|
77
+ key = name[1..-1].to_sym
78
+ value = instance_variable_get(name)
63
79
 
64
- begin
65
- wsdl_file = File.read(wsdl)
66
- rescue
67
- fail ArgumentError, "You didn't provide a wsdl file or the path is " \
68
- "invalid"
80
+ iv[key] = value
69
81
  end
70
82
 
71
- wsdl = Nokogiri::XML(wsdl_file)
83
+ iv
84
+ end
72
85
 
73
- unless xsd.valid?(wsdl)
74
- fail ArgumentError, "The wsdl file provided doesn't validate " \
75
- "against the wsdl schema and thus was rejected."
76
- end
86
+ def initialize_private_key
87
+ @private_key = OpenSSL::PKey::RSA.new(@private_key) if @private_key
77
88
  end
89
+
78
90
  end
79
91
  end
@@ -0,0 +1,15 @@
1
+ module Sepa
2
+ module ErrorMessages
3
+ CUSTOMER_ID_ERROR_MESSAGE = 'Customer Id needs to be present and needs to have a length of less than 17 characters'
4
+ ENVIRONMENT_ERROR_MESSAGE = 'Environment needs to be either PRODUCTION, TEST or customertest'
5
+ TARGET_ID_ERROR_MESSAGE = 'Target Id needs to be present and under 80 characters'
6
+ FILE_TYPE_ERROR_MESSAGE = 'File type needs to be present and under 35 characters'
7
+ CONTENT_ERROR_MESSAGE = 'Content needs to be present for this command'
8
+ SIGNING_CERT_REQUEST_ERROR_MESSAGE = 'Invalid signing certificate request'
9
+ ENCRYPTION_CERT_REQUEST_ERROR_MESSAGE = 'Invalid encryption certificate request'
10
+ PIN_ERROR_MESSAGE = 'Pin needs to be present for this command and cannot be more than 10 characters'
11
+ ENCRYPTION_CERT_ERROR_MESSAGE = 'Invalid encryption certificate'
12
+ STATUS_ERROR_MESSAGE = 'Status is required for this command and must be either NEW, DOWNLOADED or ALL'
13
+ FILE_REFERENCE_ERROR_MESSAGE = 'File reference is required for this command and must be under 33 characters'
14
+ end
15
+ end
data/lib/sepa/response.rb CHANGED
@@ -1,45 +1,28 @@
1
1
  module Sepa
2
2
  class Response
3
- def initialize(response)
4
- @response = response
5
-
6
- if !@response.respond_to?(:canonicalize)
7
- fail ArgumentError,
8
- "The response you provided is not a valid Nokogiri::XML file."
9
- elsif !valid_against_schema?(@response)
10
- fail ArgumentError,
11
- "The response you provided doesn't validate against soap schema."
12
- end
13
- end
3
+ include ActiveModel::Validations
4
+ include Utilities
14
5
 
15
- # Returns the x509 certificate embedded in the soap as an
16
- # OpenSSL::X509::Certificate
17
- def certificate
18
- cert_value = @response.at_css(
19
- 'wsse|BinarySecurityToken',
20
- 'wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-ws' \
21
- 'security-secext-1.0.xsd'
22
- ).content.gsub(/\s+/, "")
23
-
24
- cert = process_cert_value(cert_value)
25
-
26
- begin
27
- cert = OpenSSL::X509::Certificate.new(cert)
28
- rescue => e
29
- fail OpenSSL::X509::CertificateError,
30
- "The certificate embedded to the soap response could not be process" \
31
- "ed. It's most likely corrupted. OpenSSL had this to say: #{e}."
32
- end
33
- end
6
+ attr_reader :soap, :application_response, :certificate, :content
7
+
8
+ validates :soap, presence: true
9
+ validate :validate_document_format
10
+ validate :document_must_validate_against_schema
11
+
12
+ GENERIC_COMMANDS = [:get_user_info, :download_file_list, :download_file, :upload_file]
13
+
14
+ def initialize(response, command: nil)
15
+ @soap = response
16
+ @command = command
34
17
 
35
- # Verifies that the soap's certificate is trusted.
36
- def cert_is_trusted?(root_cert)
37
- if root_cert.subject == certificate.issuer
38
- certificate.verify(root_cert.public_key)
39
- else
40
- fail SecurityError,
41
- "The issuer of the certificate doesn't match the subject of the roo" \
42
- "t certificate."
18
+ # Check if command is one of the generic commands which should behave the same way across
19
+ # different banks
20
+ if GENERIC_COMMANDS.include? command
21
+ xsd = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
22
+
23
+ @application_response = extract_application_response('http://model.bxd.fi')
24
+ @certificate = extract_cert(soap, 'BinarySecurityToken', xsd)
25
+ @content = extract_content
43
26
  end
44
27
  end
45
28
 
@@ -47,42 +30,39 @@ module Sepa
47
30
  # Takes an optional verbose parameter to show which digests didn't match
48
31
  # i.e. verbose: true
49
32
  def hashes_match?(options = {})
50
- digests = find_digest_values(@response)
51
- nodes = find_nodes_to_verify(@response, digests)
33
+ digests = find_digest_values
34
+ nodes = find_nodes_to_verify(soap, digests)
52
35
 
53
36
  verified_digests = digests.select do |uri, digest|
54
37
  uri = uri.sub(/^#/, '')
55
38
  digest == nodes[uri]
56
39
  end
57
40
 
58
- if digests == verified_digests
59
- true
60
- else
61
- unverified_digests = digests.select do |uri, digest|
62
- uri = uri.sub(/^#/, '')
63
- digest != nodes[uri]
64
- end
41
+ return true if digests == verified_digests
65
42
 
66
- if options[:verbose]
67
- puts "These digests failed to verify: #{unverified_digests}."
68
- end
43
+ unverified_digests = digests.select do |uri, digest|
44
+ uri = uri.sub(/^#/, '')
45
+ digest != nodes[uri]
46
+ end
69
47
 
70
- false
48
+ if options[:verbose]
49
+ puts "These digests failed to verify: #{unverified_digests}."
71
50
  end
51
+
52
+ false
72
53
  end
73
54
 
74
55
  # Verifies the signature by extracting the public key from the certificate
75
56
  # embedded in the soap header and verifying the signature value with that.
76
57
  def signature_is_valid?
77
- node = @response.at_css('xmlns|SignedInfo',
78
- 'xmlns' => 'http://www.w3.org/2000/09/xmldsig#')
58
+ node = soap.at_css('xmlns|SignedInfo', 'xmlns' => 'http://www.w3.org/2000/09/xmldsig#')
79
59
 
80
60
  node = node.canonicalize(
81
- mode=Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0,
82
- inclusive_namespaces=nil,with_comments=false
61
+ mode = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0,
62
+ inclusive_namespaces = nil, with_comments = false
83
63
  )
84
64
 
85
- signature = @response.at_css(
65
+ signature = soap.at_css(
86
66
  'xmlns|SignatureValue',
87
67
  'xmlns' => 'http://www.w3.org/2000/09/xmldsig#'
88
68
  ).content
@@ -95,18 +75,28 @@ module Sepa
95
75
  # Gets the application response from the response as an Nokogiri::XML
96
76
  # document
97
77
  def application_response
98
- ar = @response.at_css('mod|ApplicationResponse').content
78
+ ar = soap.at_css('mod|ApplicationResponse').content
99
79
  ar = Base64.decode64(ar)
100
80
  Nokogiri::XML(ar)
101
81
  end
102
82
 
83
+ def file_references
84
+ return unless @command == :download_file_list
85
+
86
+ @file_references ||= begin
87
+ content = Nokogiri::XML @content
88
+ descriptors = content.css('FileDescriptor')
89
+ descriptors.map { |descriptor| descriptor.at('FileReference').content }
90
+ end
91
+ end
92
+
103
93
  private
104
94
 
105
95
  # Finds all reference nodes with digest values in the document and returns
106
96
  # a hash with uri as the key and digest as the value.
107
- def find_digest_values(doc)
97
+ def find_digest_values
108
98
  references = {}
109
- reference_nodes = @response.css(
99
+ reference_nodes = soap.css(
110
100
  'xmlns|Reference',
111
101
  'xmlns' => 'http://www.w3.org/2000/09/xmldsig#'
112
102
  )
@@ -128,12 +118,14 @@ module Sepa
128
118
  # references hash.
129
119
  def find_nodes_to_verify(doc, references)
130
120
  nodes = {}
121
+
131
122
  references.each do |uri, digest_value|
132
123
  uri = uri.sub(/^#/, '')
124
+ wsu = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
125
+
133
126
  node = doc.at_css(
134
- "[wsu|Id='" + uri + "']",
135
- 'wsu' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss' \
136
- '-wssecurity-utility-1.0.xsd'
127
+ "[wsu|Id='#{uri}']",
128
+ 'wsu' => wsu
137
129
  )
138
130
 
139
131
  nodes[uri] = calculate_digest(node)
@@ -142,36 +134,47 @@ module Sepa
142
134
  nodes
143
135
  end
144
136
 
145
- def calculate_digest(node)
146
- sha1 = OpenSSL::Digest::SHA1.new
137
+ def validate_document_format
138
+ unless soap.respond_to?(:canonicalize)
139
+ errors.add(:base, 'Document must be a Nokogiri XML file')
140
+ end
141
+ end
147
142
 
148
- canon_node = node.canonicalize(
149
- mode=Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0,
150
- inclusive_namespaces=nil,with_comments=false
151
- )
143
+ def document_must_validate_against_schema
144
+ check_validity_against_schema(soap, 'soap.xsd')
145
+ end
152
146
 
153
- Base64.encode64(sha1.digest(canon_node)).gsub(/\s+/, "")
147
+ def extract_content
148
+ xml = Nokogiri::XML(@application_response)
149
+ xmlns = 'http://bxd.fi/xmldata/'
150
+
151
+ case @command
152
+ when :download_file
153
+ content_node = xml.at('xmlns|Content', xmlns: xmlns)
154
+ content_node.content if content_node
155
+ when :download_file_list
156
+ content_node = xml.remove_namespaces!.at('FileDescriptors')
157
+ content_node.to_xml if content_node
158
+ when :get_user_info
159
+ canonicalized_node(xml, xmlns, 'UserFileTypes')
160
+ when :upload_file
161
+ signature_node = xml.at('xmlns|Signature', xmlns: 'http://www.w3.org/2000/09/xmldsig#')
162
+ if signature_node
163
+ signature_node.remove
164
+ xml.canonicalize
165
+ end
166
+ end
154
167
  end
155
168
 
156
- # Checks that the response is valid against soap schema.
157
- def valid_against_schema?(doc)
158
- schemas_path = File.expand_path('../../../lib/sepa/xml_schemas',
159
- __FILE__)
169
+ def extract_application_response(namespace)
170
+ if soap.respond_to? :at_css
171
+ ar_node = soap.at_css('xmlns|ApplicationResponse', xmlns: namespace)
172
+ end
160
173
 
161
- Dir.chdir(schemas_path) do
162
- xsd = Nokogiri::XML::Schema(IO.read('soap.xsd'))
163
- xsd.valid?(doc)
174
+ if ar_node
175
+ Base64.decode64(ar_node.content)
164
176
  end
165
177
  end
166
178
 
167
- # Takes the certificate from the response, adds begin and end
168
- # certificate texts and splits it into multiple lines so that OpenSSL
169
- # can read it.
170
- def process_cert_value(cert_value)
171
- cert = "-----BEGIN CERTIFICATE-----\n"
172
- cert += cert_value.to_s.gsub(/\s+/, "").scan(/.{1,64}/).join("\n")
173
- cert += "\n"
174
- cert += "-----END CERTIFICATE-----"
175
- end
176
179
  end
177
180
  end