omniauth-swedbank 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08dd6ff19fc5206753cdbaa5d52bd6e996bdc010c6a5ee5a2a1a3326c3b0377a'
4
- data.tar.gz: e5f10b84a4bf54f2103b4b7ff2562e820934be35e390bae1e85dd4bd47703400
3
+ metadata.gz: 528ab8c0fad20b14c0e37c6c5ed9a027e801aebd8c365d88d3278a450f3f008b
4
+ data.tar.gz: afd969593c671e47716fe4ab9a8aec56f2d4af37ba2422e6f8c898830d6bd7fd
5
5
  SHA512:
6
- metadata.gz: 624179716be3b0ffc26462e1fd11cdc9b532cde86ec059d2ba2fb927ec4ac06e1df436ee5dd43a6b1643bb3daffa522b1b3b8822090805ba514dd6acbfebaffd
7
- data.tar.gz: a7f24969ca3acf4c424c2b65af68b5c46687ef4a722e95ffc9ae0bb971bc03cee8e31f08d5a8e27408bedc5652957624d2b57947adc77b7f9a800fe25b80b6c6
6
+ metadata.gz: c4cd649797d99a39ae36c9874866a37c0622091232059723196eecfb39be7b1002e38fcde0d762f053e5031df280118a4ecc914bd10c45bb3e7fec75d87737f2
7
+ data.tar.gz: a27486b76f6f6dcd109d86efa93756df72694a146c0fa962821a6cdb4a160fcef89897839a2b12c97b66e5cbdf2f12e34600ee792e209430241d6614a1e220f8
@@ -11,14 +11,14 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- ruby-version: ['2.7', '3.0', '3.1', '3.2']
14
+ ruby-version: ["3.1", "3.2", "3.3", "3.4", "4.0"]
15
15
 
16
16
  steps:
17
- - uses: actions/checkout@v4
18
- - name: Set up Ruby
19
- uses: ruby/setup-ruby@v1
20
- with:
21
- ruby-version: ${{ matrix.ruby-version }}
22
- bundler-cache: true # runs 'bundle install' and caches installed gems automatically
23
- - name: Run tests
24
- run: bundle exec rspec
17
+ - uses: actions/checkout@v4
18
+ - name: Set up Ruby
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby-version }}
22
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
23
+ - name: Run tests
24
+ run: bundle exec rspec
data/README.md CHANGED
@@ -26,6 +26,10 @@ Or install it yourself as:
26
26
 
27
27
  $ gem install omniauth-rails_csrf_protection omniauth-swedbank
28
28
 
29
+ ## v009 Migration
30
+
31
+ **Swedbank will shut down banklink protocol v008 on 2026-06-02.** See [Migration Guide](docs/migration_008_to_009.md) for details.
32
+
29
33
  ## Usage
30
34
 
31
35
  Here's a quick example, adding the middleware to a Rails app
@@ -37,13 +41,47 @@ Rails.application.config.middleware.use OmniAuth::Builder do
37
41
  File.read("path/to/private.key"),
38
42
  File.read("path/to/bank.crt"),
39
43
  ENV['SWEDBANK_SND_ID'],
40
- ENV['SWEDBANK_REC_ID']
44
+ ENV['SWEDBANK_REC_ID'],
45
+ version: '009'
41
46
  end
42
47
  ```
43
48
 
49
+ The `version` option defaults to `'008'` for backward compatibility. Set it to `'009'` when you're ready to migrate (requires a new bank certificate from [banklink.swedbank.com](https://banklink.swedbank.com/public/resources/bank-certificates/009)).
50
+
44
51
  ## Auth Hash
45
52
 
46
- Here's an example Auth Hash available in `request.env['omniauth.auth']`:
53
+ ### v009
54
+
55
+ ```ruby
56
+ {
57
+ provider: 'swedbank',
58
+ uid: '374042-80367',
59
+ info: {
60
+ full_name: 'ARNIS RAITUMS',
61
+ country: 'LV'
62
+ },
63
+ extra: {
64
+ raw_info: {
65
+ VK_SERVICE: '3013',
66
+ VK_VERSION: '009',
67
+ VK_DATETIME: '2026-04-29T12:00:00+0300',
68
+ VK_SND_ID: 'SWEDBANK_LV',
69
+ VK_REC_ID: 'MPLMT',
70
+ VK_NONCE: '20170425114529204413',
71
+ VK_USER_NAME: 'ARNIS RAITUMS',
72
+ VK_USER_ID: '374042-80367',
73
+ VK_COUNTRY: 'LV',
74
+ VK_OTHER: '',
75
+ VK_TOKEN: '7',
76
+ VK_RID: '',
77
+ VK_MAC: 'qrEMRf6YV...',
78
+ VK_ENCODING: 'UTF-8'
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### v008 (deprecated)
47
85
 
48
86
  ```ruby
49
87
  {
@@ -61,7 +99,7 @@ Here's an example Auth Hash available in `request.env['omniauth.auth']`:
61
99
  VK_NONCE: '20170425114529204413',
62
100
  VK_INFO: 'ISIK:090482-12549;NIMI:DACE ĀBOLA',
63
101
  VK_MAC: 'qrEMRf6YV...',
64
- VK_ENCODING: 'UTF-8
102
+ VK_ENCODING: 'UTF-8'
65
103
  }
66
104
  }
67
105
  }
@@ -0,0 +1,89 @@
1
+ # Migration Guide: v008 to v009
2
+
3
+ ## Deadline
4
+
5
+ Swedbank will shut down banklink protocol v008 on **2026-06-02**. After this date, all v008 authentication requests will be rejected by the bank.
6
+
7
+ ## What Changed in v009
8
+
9
+ | Aspect | v008 | v009 |
10
+ |--------|------|------|
11
+ | Signing algorithm | SHA-1 | SHA-512 |
12
+ | Request service code | 4002 | 4012 |
13
+ | Response service code | 3003 | 3013 |
14
+ | VK_SND_ID in response | HP | SWEDBANK_LV |
15
+ | Bank certificate | Country-specific (LV) | Unified Baltic |
16
+ | Min. key strength | 1024 bits | 2048 bits (recommended 4096) |
17
+ | Response user data | VK_INFO (combined string) | VK_USER_NAME, VK_USER_ID, VK_COUNTRY, VK_OTHER, VK_TOKEN |
18
+ | New request fields | - | VK_DATETIME, VK_RID |
19
+
20
+ ## Migration Steps
21
+
22
+ ### 1. Update the gem
23
+
24
+ ```bash
25
+ bundle update omniauth-swedbank
26
+ ```
27
+
28
+ With the default configuration (`version: '008'`), everything continues to work as before. You will see a deprecation warning in logs.
29
+
30
+ ### 2. Download the new bank certificate
31
+
32
+ The v009 protocol uses a new unified Baltic certificate. Download it from:
33
+
34
+ https://banklink.swedbank.com/public/resources/bank-certificates/009
35
+
36
+ Replace your existing Swedbank public certificate file with the new one.
37
+
38
+ ### 3. Check your private key
39
+
40
+ Your RSA private key must be at least **2048 bits** (recommended: **4096 bits**). Keys must be regenerated every **24 months**. Check your key size with:
41
+
42
+ ```bash
43
+ openssl rsa -in your_private_key.pem -text -noout | head -1
44
+ ```
45
+
46
+ If it shows less than 2048 bits or is older than 2 years, generate a new keypair. See [Keypair Change Instruction (PDF)](https://swedbank.lv/static/pdf/business/d2d/collection/keypair_change_instruction_LV.pdf) for detailed steps. Two options:
47
+
48
+ - **Option A:** Use Swedbank's built-in keypair generator via the [support page](https://www.swedbank.lv/business/cash/banklink/integrate) (recommended). Log in, go to "My agreements", click "Update key" -> "Generate new key". Download and save the private key immediately — the bank does not store it.
49
+ - **Option B:** Generate your own keypair and upload the public key via self-service or email it to cashmanagement@swedbank.lv.
50
+
51
+ Note: When replacing keys, you can choose a transition period (up to 7 days) during which both old and new keys are valid.
52
+
53
+ ### 4. Update your provider configuration
54
+
55
+ ```ruby
56
+ # Before (v008 - default)
57
+ provider :swedbank,
58
+ File.read("path/to/private.key"),
59
+ File.read("path/to/old_bank.crt"),
60
+ ENV['SWEDBANK_SND_ID'],
61
+ ENV['SWEDBANK_REC_ID']
62
+
63
+ # After (v009)
64
+ provider :swedbank,
65
+ File.read("path/to/private.key"),
66
+ File.read("path/to/new_baltic_bank.crt"),
67
+ ENV['SWEDBANK_SND_ID'],
68
+ ENV['SWEDBANK_REC_ID'],
69
+ version: '009'
70
+ ```
71
+
72
+ ### 5. Update your callback handling (if applicable)
73
+
74
+ If your application reads user data from the auth hash, note these changes:
75
+
76
+ **v008:** User ID and name were parsed from the `VK_INFO` field (`ISIK:123456-12345;NIMI:John Doe`).
77
+
78
+ **v009:** User data comes in separate fields:
79
+ - `auth.uid` - reads from `VK_USER_ID` directly
80
+ - `auth.info.full_name` - reads from `VK_USER_NAME` directly
81
+ - `auth.info.country` - new field from `VK_COUNTRY` (e.g., `LV`)
82
+ - `auth.extra.raw_info` - contains all response parameters including `VK_TOKEN`, `VK_OTHER`, `VK_RID`
83
+
84
+ If you only use `auth.uid` and `auth.info.full_name`, no changes are needed in your application code.
85
+
86
+ ## Reference
87
+
88
+ - [Swedbank comparison PDF](https://www.swedbank.lv/static/business/banklink/LV_Authentication_008_vs_009_instruction.pdf)
89
+ - [Bank certificates for v009](https://banklink.swedbank.com/public/resources/bank-certificates/009)
@@ -6,8 +6,10 @@ module OmniAuth
6
6
  class Swedbank
7
7
  include OmniAuth::Strategy
8
8
 
9
- AUTH_SERVICE = '4002'
10
- AUTH_VERSION = '008'
9
+ V008_AUTH_SERVICE = '4002'
10
+ V008_RESPONSE_SERVICE = '3003'
11
+ V009_AUTH_SERVICE = '4012'
12
+ V009_RESPONSE_SERVICE = '3013'
11
13
 
12
14
  def self.render_nonce?
13
15
  defined?(ActionDispatch::ContentSecurityPolicy::Request) != nil
@@ -26,40 +28,96 @@ module OmniAuth
26
28
 
27
29
  option :name, 'swedbank'
28
30
  option :site, 'https://www.swedbank.lv/banklink'
31
+ option :version, '008'
32
+
33
+ SUPPORTED_VERSIONS = %w[008 009].freeze
34
+
35
+ def version_009?
36
+ options.version == '009'
37
+ end
38
+
39
+ def invalid_version?
40
+ !SUPPORTED_VERSIONS.include?(options.version)
41
+ end
42
+
43
+ def auth_service
44
+ version_009? ? V009_AUTH_SERVICE : V008_AUTH_SERVICE
45
+ end
46
+
47
+ def response_service
48
+ version_009? ? V009_RESPONSE_SERVICE : V008_RESPONSE_SERVICE
49
+ end
50
+
51
+ def digest
52
+ version_009? ? OpenSSL::Digest::SHA512.new : OpenSSL::Digest::SHA1.new
53
+ end
29
54
 
30
55
  def stamp
31
56
  return @stamp if @stamp
32
57
  @stamp = Time.now.strftime('%Y%m%d%H%M%S') + SecureRandom.random_number(999999).to_s.rjust(6, '0')
33
58
  end
34
59
 
60
+ def datetime
61
+ @datetime ||= Time.now.strftime('%Y-%m-%dT%H:%M:%S%z')
62
+ end
63
+
64
+ def rid
65
+ ''
66
+ end
67
+
35
68
  def prepend_length(value)
36
69
  # prepend length to string in 0xx format
37
70
  [ value.to_s.length.to_s.rjust(3, '0'), value.dup.to_s.force_encoding('ascii')].join
38
71
  end
39
72
 
40
73
  def signature_input
41
- [
42
- AUTH_SERVICE, # VK_SERVICE
43
- AUTH_VERSION, # VK_VERSION
44
- options.snd_id, # VK_SND_ID
45
- options.rec_id, # VK_REC_ID
46
- stamp, # VK_NONCE
47
- callback_url # VK_RETURN
48
- ].map{|v| prepend_length(v)}.join
74
+ fields = if version_009?
75
+ [
76
+ auth_service, # VK_SERVICE
77
+ options.version, # VK_VERSION
78
+ options.snd_id, # VK_SND_ID
79
+ options.rec_id, # VK_REC_ID
80
+ stamp, # VK_NONCE
81
+ callback_url, # VK_RETURN
82
+ datetime, # VK_DATETIME
83
+ rid # VK_RID
84
+ ]
85
+ else
86
+ [
87
+ auth_service, # VK_SERVICE
88
+ options.version, # VK_VERSION
89
+ options.snd_id, # VK_SND_ID
90
+ options.rec_id, # VK_REC_ID
91
+ stamp, # VK_NONCE
92
+ callback_url # VK_RETURN
93
+ ]
94
+ end
95
+ fields.map{|v| prepend_length(v)}.join
49
96
  end
50
97
 
51
98
  def signature(priv_key)
52
- Base64.encode64(priv_key.sign(OpenSSL::Digest::SHA1.new, signature_input))
99
+ Base64.encode64(priv_key.sign(digest, signature_input))
53
100
  end
54
101
 
55
102
  uid do
56
- request.params['VK_INFO'].match(/ISIK:(\d{6}\-\d{5})/)[1]
103
+ if version_009?
104
+ request.params['VK_USER_ID']
105
+ else
106
+ request.params['VK_INFO'].match(/ISIK:(\d{6}\-\d{5})/)[1]
107
+ end
57
108
  end
58
109
 
59
110
  info do
60
- {
61
- full_name: request.params['VK_INFO'].match(/NIMI:(.+)/)[1]
62
- }
111
+ if version_009?
112
+ {
113
+ full_name: request.params['VK_USER_NAME'],
114
+ country: request.params['VK_COUNTRY']
115
+ }
116
+ else
117
+ {
118
+ full_name: request.params['VK_INFO'].match(/NIMI:(.+)/)[1]
119
+ }
120
+ end
63
121
  end
64
122
 
65
123
  extra do
@@ -67,17 +125,22 @@ module OmniAuth
67
125
  end
68
126
 
69
127
  def callback_phase
128
+ if invalid_version?
129
+ return fail!(:unsupported_version_err,
130
+ ArgumentError.new("Unsupported banklink version '#{options.version}'. Supported: #{SUPPORTED_VERSIONS.join(', ')}"))
131
+ end
132
+
70
133
  begin
71
134
  pub_key = OpenSSL::X509::Certificate.new(options.public_key).public_key
72
135
  rescue => e
73
136
  return fail!(:public_key_load_err, e)
74
137
  end
75
138
 
76
- if request.params['VK_SERVICE'] != '3003'
139
+ if request.params['VK_SERVICE'] != response_service
77
140
  return fail!(:unsupported_response_service_err)
78
141
  end
79
142
 
80
- if request.params['VK_VERSION'] != '008'
143
+ if request.params['VK_VERSION'] != options.version
81
144
  return fail!(:unsupported_response_version_err)
82
145
  end
83
146
 
@@ -85,18 +148,35 @@ module OmniAuth
85
148
  return fail!(:unsupported_response_encoding_err)
86
149
  end
87
150
 
88
- sig_str = [
89
- request.params['VK_SERVICE'],
90
- request.params['VK_VERSION'],
91
- request.params['VK_SND_ID'],
92
- request.params['VK_REC_ID'],
93
- request.params['VK_NONCE'],
94
- request.params['VK_INFO']
95
- ].map{|v| prepend_length(v)}.join
151
+ sig_str = if version_009?
152
+ [
153
+ request.params['VK_SERVICE'],
154
+ request.params['VK_VERSION'],
155
+ request.params['VK_DATETIME'],
156
+ request.params['VK_SND_ID'],
157
+ request.params['VK_REC_ID'],
158
+ request.params['VK_NONCE'],
159
+ request.params['VK_USER_NAME'],
160
+ request.params['VK_USER_ID'],
161
+ request.params['VK_COUNTRY'],
162
+ request.params['VK_OTHER'],
163
+ request.params['VK_TOKEN'],
164
+ request.params['VK_RID']
165
+ ].map{|v| prepend_length(v)}.join
166
+ else
167
+ [
168
+ request.params['VK_SERVICE'],
169
+ request.params['VK_VERSION'],
170
+ request.params['VK_SND_ID'],
171
+ request.params['VK_REC_ID'],
172
+ request.params['VK_NONCE'],
173
+ request.params['VK_INFO']
174
+ ].map{|v| prepend_length(v)}.join
175
+ end
96
176
 
97
177
  raw_signature = Base64.decode64(request.params['VK_MAC'])
98
178
 
99
- if !pub_key.verify(OpenSSL::Digest::SHA1.new, raw_signature, sig_str)
179
+ if !pub_key.verify(digest, raw_signature, sig_str)
100
180
  return fail!(:invalid_response_signature_err)
101
181
  end
102
182
 
@@ -104,19 +184,30 @@ module OmniAuth
104
184
  end
105
185
 
106
186
  def request_phase
187
+ if invalid_version?
188
+ return fail!(:unsupported_version_err,
189
+ ArgumentError.new("Unsupported banklink version '#{options.version}'. Supported: #{SUPPORTED_VERSIONS.join(', ')}"))
190
+ end
191
+
107
192
  begin
108
193
  priv_key = OpenSSL::PKey::RSA.new(options.private_key)
109
194
  rescue => e
110
195
  return fail!(:private_key_load_err, e)
111
196
  end
112
197
 
198
+ unless version_009?
199
+ warn "[DEPRECATION] omniauth-swedbank: Swedbank banklink v008 will be shut down on 2026-06-02. " \
200
+ "Please migrate to v009 by setting `version: '009'` in your provider config. " \
201
+ "See https://www.swedbank.lv/static/business/banklink/LV_Authentication_008_vs_009_instruction.pdf"
202
+ end
203
+
113
204
  set_locale_from_query_param
114
205
 
115
206
  form = OmniAuth::Form.new(:title => I18n.t('omniauth.swedbank.please_wait'), :url => options.site)
116
207
 
117
- {
118
- 'VK_SERVICE' => AUTH_SERVICE,
119
- 'VK_VERSION' => AUTH_VERSION,
208
+ params = {
209
+ 'VK_SERVICE' => auth_service,
210
+ 'VK_VERSION' => options.version,
120
211
  'VK_SND_ID' => options.snd_id,
121
212
  'VK_REC_ID' => options.rec_id,
122
213
  'VK_NONCE' => stamp,
@@ -124,7 +215,14 @@ module OmniAuth
124
215
  'VK_MAC' => signature(priv_key),
125
216
  'VK_LANG' => resolve_bank_ui_language,
126
217
  'VK_ENCODING' => 'UTF-8'
127
- }.each do |name, val|
218
+ }
219
+
220
+ if version_009?
221
+ params['VK_DATETIME'] = datetime
222
+ params['VK_RID'] = rid
223
+ end
224
+
225
+ params.each do |name, val|
128
226
  form.html "<input type=\"hidden\" name=\"#{name}\" value=\"#{escape(val)}\" />"
129
227
  end
130
228
 
@@ -1,5 +1,5 @@
1
1
  module Omniauth
2
2
  module Swedbank
3
- VERSION = '0.3.0'
3
+ VERSION = '0.4.0'
4
4
  end
5
5
  end
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.required_ruby_version = '>= 2.7'
21
+ spec.required_ruby_version = '>= 3.1'
22
22
 
23
23
  spec.add_runtime_dependency 'omniauth', '~> 2.1'
24
24
  spec.add_runtime_dependency 'i18n'
@@ -0,0 +1,17 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICoTCCAYmgAwIBAgIBATANBgkqhkiG9w0BAQ0FADAUMRIwEAYDVQQDDAlUZXN0
3
+ IFYwMDkwHhcNMjYwNDI5MDkyNjQ5WhcNMjcwNDI5MDkyNjQ5WjAUMRIwEAYDVQQD
4
+ DAlUZXN0IFYwMDkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtZLYp
5
+ KJNe16Fp2S/EVyyz0aFdPhBwgn+707JW/a8EM1m0rryNUbwZpQqZ2wzmPy6r/D13
6
+ UssJpGVg74+LQ8GZI+mgiL+U7nY95111XhOQ/B258i40HAwf3WFVqDKmigYxXU+O
7
+ 0hbuUwXmT7qP3sRNgNLChn6BFDV6c1f/TYkiSZrSj45HQsqDekCIZdy6CvzV8m5I
8
+ JV3TJWshoEj4HPJI7YEkgjc3nzwUsKiLi9k6FsLSiveFBaky+mhYBlJUtET7KEwu
9
+ raHw7LkCn1FoCSodgxeHF9IFCCyKTI7VKETB4w0TYLKCbMOjtON0ifCx+H/w8LwP
10
+ YrhXTF6rjcdL3A9jAgMBAAEwDQYJKoZIhvcNAQENBQADggEBAErV4sBKLwmUvjOT
11
+ S8Kjw+BwTf0NU/yOpQS1kCSTD5gn9OOqDirOQe7Yj1dyGWMDmfGy4Jw7xcMSRTTq
12
+ TCKS7poTkFa8mXRPm+kFqw1Hy3U6/MsswZCBxiIkGEltKytXe+AdLVY2uM46/j6W
13
+ D6Rt0vgcBcj//h2et4f8GDMs4s1ndKp8o0rnggSQCcgC2yLMNL8AdSxwHj7eGDiv
14
+ cCWspZCXq7SGOoMC3jhhNOYt9WjH+/Aj/FXDRu2iguaG4I4qBGJSmY2i4WQT/XJv
15
+ sXPCrHoN0oGrmzX/V5/20AEOhS6oBLCIMfBeR4iRAicUW9mUnVj+PqigZkXGSArB
16
+ tkkI44U=
17
+ -----END CERTIFICATE-----
@@ -6,210 +6,434 @@ describe OmniAuth::Strategies::Swedbank do
6
6
 
7
7
  PRIVATE_KEY = File.read(File.join(RSpec.configuration.cert_folder, 'request.private.pem'))
8
8
  PUBLIC_KEY = File.read(File.join(RSpec.configuration.cert_folder, 'response.public.pem'))
9
-
10
- let(:app){ Rack::Builder.new do |b|
11
- b.use Rack::Session::Cookie, {secret: '5242e6bd9daf0e9645c2d4e22b11ba8cee0bed44439906d5f1bd5dad409d8637'}
12
- b.use(OmniAuth::Strategies::Swedbank, PRIVATE_KEY, PUBLIC_KEY, 'MY_SND_ID', 'MY_REC_ID')
13
- b.run lambda{|env| [404, {}, ['Not Found']]}
14
- end.to_app }
9
+ PUBLIC_KEY_V009 = File.read(File.join(RSpec.configuration.cert_folder, 'response.v009.public.pem'))
15
10
 
16
11
  let(:token){ Rack::Protection::AuthenticityToken.random_token }
17
12
 
18
13
  let(:last_response_nonce) { last_response.body.match(/name="VK_NONCE" value="([^"]*)"/)[1] }
19
14
  let(:last_response_mac) { last_response.body.match(/name="VK_MAC" value="([^"]*)"/)[1] }
20
15
 
21
- context 'request phase' do
22
- EXPECTED_VALUES = {
23
- 'VK_SERVICE' => '4002',
24
- 'VK_VERSION' => '008',
25
- 'VK_SND_ID' => 'MY_SND_ID',
26
- 'VK_REC_ID' => 'MY_REC_ID',
27
- 'VK_RETURN' => 'http://example.org/auth/swedbank/callback'
28
- }
29
-
30
- before(:each) do
31
- post(
32
- '/auth/swedbank',
33
- {},
34
- 'rack.session' => {csrf: token},
35
- 'HTTP_X_CSRF_TOKEN' => token
36
- )
37
- end
16
+ context 'v008 (default)' do
17
+ let(:app){ Rack::Builder.new do |b|
18
+ b.use Rack::Session::Cookie, {secret: '5242e6bd9daf0e9645c2d4e22b11ba8cee0bed44439906d5f1bd5dad409d8637'}
19
+ b.use(OmniAuth::Strategies::Swedbank, PRIVATE_KEY, PUBLIC_KEY, 'MY_SND_ID', 'MY_REC_ID')
20
+ b.run lambda{|env| [404, {}, ['Not Found']]}
21
+ end.to_app }
22
+
23
+ context 'request phase' do
24
+ EXPECTED_VALUES_V008 = {
25
+ 'VK_SERVICE' => '4002',
26
+ 'VK_VERSION' => '008',
27
+ 'VK_SND_ID' => 'MY_SND_ID',
28
+ 'VK_REC_ID' => 'MY_REC_ID',
29
+ 'VK_RETURN' => 'http://example.org/auth/swedbank/callback'
30
+ }
31
+
32
+ before(:each) do
33
+ post(
34
+ '/auth/swedbank',
35
+ {},
36
+ 'rack.session' => {csrf: token},
37
+ 'HTTP_X_CSRF_TOKEN' => token
38
+ )
39
+ end
38
40
 
39
- it 'displays a single form' do
40
- expect(last_response.status).to eq(200)
41
- expect(last_response.body.scan('<form').size).to eq(1)
42
- end
41
+ it 'displays a single form' do
42
+ expect(last_response.status).to eq(200)
43
+ expect(last_response.body.scan('<form').size).to eq(1)
44
+ end
43
45
 
44
- it 'has JavaScript code to submit the form after it is created' do
45
- expect(last_response.body).to be_include('</form><script type="text/javascript">document.forms[0].submit();</script>')
46
- end
46
+ it 'has JavaScript code to submit the form after it is created' do
47
+ expect(last_response.body).to be_include('</form><script type="text/javascript">document.forms[0].submit();</script>')
48
+ end
47
49
 
48
- EXPECTED_VALUES.each_pair do |k,v|
49
- it "has hidden input field #{k} => #{v}" do
50
- expect(last_response.body.scan(
51
- "<input type=\"hidden\" name=\"#{k}\" value=\"#{v}\"").size).to eq(1)
50
+ EXPECTED_VALUES_V008.each_pair do |k,v|
51
+ it "has hidden input field #{k} => #{v}" do
52
+ expect(last_response.body.scan(
53
+ "<input type=\"hidden\" name=\"#{k}\" value=\"#{v}\"").size).to eq(1)
54
+ end
52
55
  end
53
- end
54
56
 
55
- it 'has a 50 byte long nonce field value' do
56
- expect(last_response_nonce.bytesize).to eq(20)
57
- end
57
+ it 'has a 20 byte long nonce field value' do
58
+ expect(last_response_nonce.bytesize).to eq(20)
59
+ end
58
60
 
59
- it 'has a correct VK_MAC signature' do
60
- sig_str =
61
- '0044002' + # VK_SERVICE
62
- '003008' + # VK_VERSION
63
- '009MY_SND_ID' + # VK_SND_ID
64
- '009MY_REC_ID' + # VK_REC_ID
65
- "020#{last_response_nonce}" + # VK_NONCE
66
- "041#{EXPECTED_VALUES['VK_RETURN']}" # V_RETURN
67
-
68
- private_key = OpenSSL::PKey::RSA.new(PRIVATE_KEY)
69
- expected_mac = Base64.encode64(private_key.sign(OpenSSL::Digest::SHA1.new, sig_str))
70
- expect(last_response_mac).to eq(expected_mac)
71
- end
61
+ it 'has a correct VK_MAC signature' do
62
+ sig_str =
63
+ '0044002' + # VK_SERVICE
64
+ '003008' + # VK_VERSION
65
+ '009MY_SND_ID' + # VK_SND_ID
66
+ '009MY_REC_ID' + # VK_REC_ID
67
+ "020#{last_response_nonce}" + # VK_NONCE
68
+ "041#{EXPECTED_VALUES_V008['VK_RETURN']}" # V_RETURN
69
+
70
+ private_key = OpenSSL::PKey::RSA.new(PRIVATE_KEY)
71
+ expected_mac = Base64.encode64(private_key.sign(OpenSSL::Digest::SHA1.new, sig_str))
72
+ expect(last_response_mac).to eq(expected_mac)
73
+ end
72
74
 
73
- context 'with default options' do
74
- it 'has the default action tag value' do
75
- expect(last_response.body).to be_include("action='https://www.swedbank.lv/banklink'")
75
+ it 'does not include VK_DATETIME field' do
76
+ expect(last_response.body).not_to include('name="VK_DATETIME"')
76
77
  end
77
78
 
78
- it 'has the default VK_LANG value' do
79
- expect(last_response.body).to be_include("action='https://www.swedbank.lv/banklink'")
79
+ it 'does not include VK_RID field' do
80
+ expect(last_response.body).not_to include('name="VK_RID"')
80
81
  end
81
- end
82
82
 
83
- context 'with custom options' do
84
- let(:app){ Rack::Builder.new do |b|
85
- b.use Rack::Session::Cookie, {secret: '5242e6bd9daf0e9645c2d4e22b11ba8cee0bed44439906d5f1bd5dad409d8637'}
86
- b.use(OmniAuth::Strategies::Swedbank, PRIVATE_KEY, PUBLIC_KEY, 'MY_SND_ID', 'MY_REC_ID',
87
- site: 'https://test.lv/banklink')
88
- b.run lambda{|env| [404, {}, ['Not Found']]}
89
- end.to_app }
83
+ it 'outputs a deprecation warning' do
84
+ expect { post('/auth/swedbank', {}, 'rack.session' => {csrf: token}, 'HTTP_X_CSRF_TOKEN' => token) }
85
+ .to output(/DEPRECATION.*v008.*2026-06-02/).to_stderr
86
+ end
87
+
88
+ context 'with default options' do
89
+ it 'has the default action tag value' do
90
+ expect(last_response.body).to be_include("action='https://www.swedbank.lv/banklink'")
91
+ end
92
+
93
+ it 'has the default VK_LANG value' do
94
+ expect(last_response.body).to be_include("action='https://www.swedbank.lv/banklink'")
95
+ end
96
+ end
97
+
98
+ context 'with custom options' do
99
+ let(:app){ Rack::Builder.new do |b|
100
+ b.use Rack::Session::Cookie, {secret: '5242e6bd9daf0e9645c2d4e22b11ba8cee0bed44439906d5f1bd5dad409d8637'}
101
+ b.use(OmniAuth::Strategies::Swedbank, PRIVATE_KEY, PUBLIC_KEY, 'MY_SND_ID', 'MY_REC_ID',
102
+ site: 'https://test.lv/banklink')
103
+ b.run lambda{|env| [404, {}, ['Not Found']]}
104
+ end.to_app }
105
+
106
+ it 'has the custom action tag value' do
107
+ expect(last_response.body).to be_include("action='https://test.lv/banklink'")
108
+ end
109
+ end
110
+
111
+ context 'with non-existant private key files' do
112
+ let(:app){ Rack::Builder.new do |b|
113
+ b.use Rack::Session::Cookie, {secret: '5242e6bd9daf0e9645c2d4e22b11ba8cee0bed44439906d5f1bd5dad409d8637'}
114
+ b.use(OmniAuth::Strategies::Swedbank, 'missing-private-key-file.pem', PUBLIC_KEY, 'MY_SND_ID', 'MY_REC_ID')
115
+ b.run lambda{|env| [404, {}, ['Not Found']]}
116
+ end.to_app }
117
+
118
+ it 'redirects to /auth/failure with appropriate query params' do
119
+ expect(last_response.status).to eq(302)
120
+ expect(last_response.headers['Location']).to eq('/auth/failure?message=private_key_load_err&strategy=swedbank')
121
+ end
122
+ end
90
123
 
91
- it 'has the custom action tag value' do
92
- expect(last_response.body).to be_include("action='https://test.lv/banklink'")
124
+ context 'with invalid version' do
125
+ let(:app){ Rack::Builder.new do |b|
126
+ b.use Rack::Session::Cookie, {secret: '5242e6bd9daf0e9645c2d4e22b11ba8cee0bed44439906d5f1bd5dad409d8637'}
127
+ b.use(OmniAuth::Strategies::Swedbank, PRIVATE_KEY, PUBLIC_KEY, 'MY_SND_ID', 'MY_REC_ID',
128
+ version: '010')
129
+ b.run lambda{|env| [404, {}, ['Not Found']]}
130
+ end.to_app }
131
+
132
+ it 'fails with unsupported_version_err on request phase' do
133
+ post('/auth/swedbank', {}, 'rack.session' => {csrf: token}, 'HTTP_X_CSRF_TOKEN' => token)
134
+ expect(last_response.status).to eq(302)
135
+ expect(last_response.headers['Location']).to eq('/auth/failure?message=unsupported_version_err&strategy=swedbank')
136
+ end
137
+
138
+ it 'fails with unsupported_version_err on callback phase' do
139
+ post '/auth/swedbank/callback', 'VK_SERVICE' => '3003'
140
+ expect(last_response.status).to eq(302)
141
+ expect(last_response.headers['Location']).to eq('/auth/failure?message=unsupported_version_err&strategy=swedbank')
142
+ end
93
143
  end
94
144
  end
95
145
 
96
- context 'with non-existant private key files' do
97
- let(:app){ Rack::Builder.new do |b|
98
- b.use Rack::Session::Cookie, {secret: '5242e6bd9daf0e9645c2d4e22b11ba8cee0bed44439906d5f1bd5dad409d8637'}
99
- b.use(OmniAuth::Strategies::Swedbank, 'missing-private-key-file.pem', PUBLIC_KEY, 'MY_SND_ID', 'MY_REC_ID')
100
- b.run lambda{|env| [404, {}, ['Not Found']]}
101
- end.to_app }
146
+ context 'callback phase' do
147
+ let(:auth_hash){ last_request.env['omniauth.auth'] }
148
+
149
+ context 'with valid response' do
150
+ before do
151
+ post '/auth/swedbank/callback',
152
+ 'VK_SERVICE' => '3003',
153
+ 'VK_VERSION' => '008',
154
+ 'VK_SND_ID' => 'HP',
155
+ 'VK_REC_ID' => 'MY_REC_ID',
156
+ 'VK_NONCE' => 'pXXXlocalhostX3000b41292810c0345a7b3770b1c807bed7a',
157
+ 'VK_INFO' => 'ISIK:123456-12345;NIMI:Example User',
158
+ 'VK_MAC' => 'cmXyp2My7P9pTgrzqJeg7qH+NPCuyaiGNpQIrcCr6S44w0bH+Ao4WDViqytaPH2vENooVPXDSgOcBqHTg44gJ9FlrhI5StiouHVhjpCcWg+h/ERcyc8w58PjsEmdsd4BIpaGXNyhvcIKdWfNwYA1UCIrmFsPAPWfVeorNxp81E7pvY4p4zsqMF80YZ7/RdOpjrtuXJ4nYJ7d+2fXJKKmUlqArCc786DJdb/z8wVDSNA9BZxnf8EE6s//p9gzqLPAg/T9Xp/2024n2JtC6kwsWF614bn64LEZz5c8owZth6FV+2fjnzHxOiifOe+jc9SRstCLITK6Y0j+6n8auiEZ5g==',
159
+ 'VK_LANG' => 'LAT',
160
+ 'VK_ENCODING' => 'UTF-8'
161
+ end
162
+
163
+ it 'sets the correct uid value in the auth hash' do
164
+ expect(auth_hash.uid).to eq('123456-12345')
165
+ end
166
+
167
+ it 'sets the correct info.full_name value in the auth hash' do
168
+ expect(auth_hash.info.full_name).to eq('Example User')
169
+ end
170
+ end
171
+
172
+ context 'with non-existant public key file' do
173
+ let(:app){ Rack::Builder.new do |b|
174
+ b.use Rack::Session::Cookie, {secret: '5242e6bd9daf0e9645c2d4e22b11ba8cee0bed44439906d5f1bd5dad409d8637'}
175
+ b.use(OmniAuth::Strategies::Swedbank, PRIVATE_KEY, 'missing-public-key-file.pem', 'MY_SND_ID', 'MY_REC_ID')
176
+ b.run lambda{|env| [404, {}, ['Not Found']]}
177
+ end.to_app }
178
+
179
+ it 'redirects to /auth/failure with appropriate query params' do
180
+ post '/auth/swedbank/callback'
181
+ expect(last_response.status).to eq(302)
182
+ expect(last_response.headers['Location']).to eq('/auth/failure?message=public_key_load_err&strategy=swedbank')
183
+ end
184
+ end
102
185
 
103
- it 'redirects to /auth/failure with appropriate query params' do
104
- expect(last_response.status).to eq(302)
105
- expect(last_response.headers['Location']).to eq('/auth/failure?message=private_key_load_err&strategy=swedbank')
186
+ context 'with invalid response' do
187
+ it 'detects invalid signature' do
188
+ post '/auth/swedbank/callback',
189
+ 'VK_SERVICE' => '3003',
190
+ 'VK_VERSION' => '008',
191
+ 'VK_SND_ID' => 'HP',
192
+ 'VK_REC_ID' => 'MY_REC_ID',
193
+ 'VK_NONCE' => 'pXXXlocalhostX3000b41292810c0345a7b3770b1c807bed7a',
194
+ 'VK_INFO' => 'ISIK:123456-12345;NIMI:Example User',
195
+ 'VK_MAC' => 'invalid signature',
196
+ 'VK_LANG' => 'LAT',
197
+ 'VK_ENCODING' => 'UTF-8'
198
+
199
+ expect(last_response.status).to eq(302)
200
+ expect(last_response.headers['Location']).to eq('/auth/failure?message=invalid_response_signature_err&strategy=swedbank')
201
+ end
202
+
203
+ it 'detects unsupported VK_SERVICE values' do
204
+ post '/auth/swedbank/callback',
205
+ 'VK_SERVICE' => '3004',
206
+ 'VK_VERSION' => '008',
207
+ 'VK_SND_ID' => 'HP',
208
+ 'VK_REC_ID' => 'MY_REC_ID',
209
+ 'VK_NONCE' => 'pXXXlocalhostX3000b41292810c0345a7b3770b1c807bed7a',
210
+ 'VK_INFO' => 'ISIK:123456-12345;NIMI:Example User',
211
+ 'VK_MAC' => 'cmXyp2My7P9pTgrzqJeg7qH+NPCuyaiGNpQIrcCr6S44w0bH+Ao4WDViqytaPH2vENooVPXDSgOcBqHTg44gJ9FlrhI5StiouHVhjpCcWg+h/ERcyc8w58PjsEmdsd4BIpaGXNyhvcIKdWfNwYA1UCIrmFsPAPWfVeorNxp81E7pvY4p4zsqMF80YZ7/RdOpjrtuXJ4nYJ7d+2fXJKKmUlqArCc786DJdb/z8wVDSNA9BZxnf8EE6s//p9gzqLPAg/T9Xp/2024n2JtC6kwsWF614bn64LEZz5c8owZth6FV+2fjnzHxOiifOe+jc9SRstCLITK6Y0j+6n8auiEZ5g==',
212
+ 'VK_LANG' => 'LAT',
213
+ 'VK_ENCODING' => 'UTF-8'
214
+
215
+ expect(last_response.status).to eq(302)
216
+ expect(last_response.headers['Location']).to eq('/auth/failure?message=unsupported_response_service_err&strategy=swedbank')
217
+ end
218
+
219
+ it 'detects unsupported VK_VERSION values' do
220
+ post '/auth/swedbank/callback',
221
+ 'VK_SERVICE' => '3003',
222
+ 'VK_VERSION' => '009',
223
+ 'VK_SND_ID' => 'HP',
224
+ 'VK_REC_ID' => 'MY_REC_ID',
225
+ 'VK_NONCE' => 'pXXXlocalhostX3000b41292810c0345a7b3770b1c807bed7a',
226
+ 'VK_INFO' => 'ISIK:123456-12345;NIMI:Example User',
227
+ 'VK_MAC' => 'cmXyp2My7P9pTgrzqJeg7qH+NPCuyaiGNpQIrcCr6S44w0bH+Ao4WDViqytaPH2vENooVPXDSgOcBqHTg44gJ9FlrhI5StiouHVhjpCcWg+h/ERcyc8w58PjsEmdsd4BIpaGXNyhvcIKdWfNwYA1UCIrmFsPAPWfVeorNxp81E7pvY4p4zsqMF80YZ7/RdOpjrtuXJ4nYJ7d+2fXJKKmUlqArCc786DJdb/z8wVDSNA9BZxnf8EE6s//p9gzqLPAg/T9Xp/2024n2JtC6kwsWF614bn64LEZz5c8owZth6FV+2fjnzHxOiifOe+jc9SRstCLITK6Y0j+6n8auiEZ5g==',
228
+ 'VK_LANG' => 'LAT',
229
+ 'VK_ENCODING' => 'UTF-8'
230
+
231
+ expect(last_response.status).to eq(302)
232
+ expect(last_response.headers['Location']).to eq('/auth/failure?message=unsupported_response_version_err&strategy=swedbank')
233
+ end
234
+
235
+ it 'detects unsupported VK_ENCODING values' do
236
+ post '/auth/swedbank/callback',
237
+ 'VK_SERVICE' => '3003',
238
+ 'VK_VERSION' => '008',
239
+ 'VK_SND_ID' => 'HP',
240
+ 'VK_REC_ID' => 'MY_REC_ID',
241
+ 'VK_NONCE' => 'pXXXlocalhostX3000b41292810c0345a7b3770b1c807bed7a',
242
+ 'VK_INFO' => 'ISIK:123456-12345;NIMI:Example User',
243
+ 'VK_MAC' => 'cmXyp2My7P9pTgrzqJeg7qH+NPCuyaiGNpQIrcCr6S44w0bH+Ao4WDViqytaPH2vENooVPXDSgOcBqHTg44gJ9FlrhI5StiouHVhjpCcWg+h/ERcyc8w58PjsEmdsd4BIpaGXNyhvcIKdWfNwYA1UCIrmFsPAPWfVeorNxp81E7pvY4p4zsqMF80YZ7/RdOpjrtuXJ4nYJ7d+2fXJKKmUlqArCc786DJdb/z8wVDSNA9BZxnf8EE6s//p9gzqLPAg/T9Xp/2024n2JtC6kwsWF614bn64LEZz5c8owZth6FV+2fjnzHxOiifOe+jc9SRstCLITK6Y0j+6n8auiEZ5g==',
244
+ 'VK_LANG' => 'LAT',
245
+ 'VK_ENCODING' => 'ASCII'
246
+
247
+ expect(last_response.status).to eq(302)
248
+ expect(last_response.headers['Location']).to eq('/auth/failure?message=unsupported_response_encoding_err&strategy=swedbank')
249
+ end
106
250
  end
107
251
  end
108
252
  end
109
253
 
110
- context 'callback phase' do
111
- let(:auth_hash){ last_request.env['omniauth.auth'] }
254
+ context 'v009' do
255
+ let(:app){ Rack::Builder.new do |b|
256
+ b.use Rack::Session::Cookie, {secret: '5242e6bd9daf0e9645c2d4e22b11ba8cee0bed44439906d5f1bd5dad409d8637'}
257
+ b.use(OmniAuth::Strategies::Swedbank, PRIVATE_KEY, PUBLIC_KEY_V009, 'MY_SND_ID', 'MY_REC_ID',
258
+ version: '009')
259
+ b.run lambda{|env| [404, {}, ['Not Found']]}
260
+ end.to_app }
261
+
262
+ context 'request phase' do
263
+ EXPECTED_VALUES_V009 = {
264
+ 'VK_SERVICE' => '4012',
265
+ 'VK_VERSION' => '009',
266
+ 'VK_SND_ID' => 'MY_SND_ID',
267
+ 'VK_REC_ID' => 'MY_REC_ID',
268
+ 'VK_RETURN' => 'http://example.org/auth/swedbank/callback'
269
+ }
270
+
271
+ let(:last_response_datetime) { last_response.body.match(/name="VK_DATETIME" value="([^"]*)"/)[1] }
272
+ let(:last_response_rid) { last_response.body.match(/name="VK_RID" value="([^"]*)"/)[1] }
273
+
274
+ before(:each) do
275
+ post(
276
+ '/auth/swedbank',
277
+ {},
278
+ 'rack.session' => {csrf: token},
279
+ 'HTTP_X_CSRF_TOKEN' => token
280
+ )
281
+ end
112
282
 
113
- context 'with valid response' do
114
- before do
115
- post '/auth/swedbank/callback',
116
- 'VK_SERVICE' => '3003',
117
- 'VK_VERSION' => '008',
118
- 'VK_SND_ID' => 'HP',
119
- 'VK_REC_ID' => 'MY_REC_ID',
120
- 'VK_NONCE' => 'pXXXlocalhostX3000b41292810c0345a7b3770b1c807bed7a',
121
- 'VK_INFO' => 'ISIK:123456-12345;NIMI:Example User',
122
- 'VK_MAC' => 'cmXyp2My7P9pTgrzqJeg7qH+NPCuyaiGNpQIrcCr6S44w0bH+Ao4WDViqytaPH2vENooVPXDSgOcBqHTg44gJ9FlrhI5StiouHVhjpCcWg+h/ERcyc8w58PjsEmdsd4BIpaGXNyhvcIKdWfNwYA1UCIrmFsPAPWfVeorNxp81E7pvY4p4zsqMF80YZ7/RdOpjrtuXJ4nYJ7d+2fXJKKmUlqArCc786DJdb/z8wVDSNA9BZxnf8EE6s//p9gzqLPAg/T9Xp/2024n2JtC6kwsWF614bn64LEZz5c8owZth6FV+2fjnzHxOiifOe+jc9SRstCLITK6Y0j+6n8auiEZ5g==',
123
- 'VK_LANG' => 'LAT',
124
- 'VK_ENCODING' => 'UTF-8'
283
+ it 'displays a single form' do
284
+ expect(last_response.status).to eq(200)
285
+ expect(last_response.body.scan('<form').size).to eq(1)
125
286
  end
126
287
 
127
- it 'sets the correct uid value in the auth hash' do
128
- expect(auth_hash.uid).to eq('123456-12345')
288
+ EXPECTED_VALUES_V009.each_pair do |k,v|
289
+ it "has hidden input field #{k} => #{v}" do
290
+ expect(last_response.body.scan(
291
+ "<input type=\"hidden\" name=\"#{k}\" value=\"#{v}\"").size).to eq(1)
292
+ end
129
293
  end
130
294
 
131
- it 'sets the correct info.full_name value in the auth hash' do
132
- expect(auth_hash.info.full_name).to eq('Example User')
295
+ it 'has a VK_DATETIME field' do
296
+ expect(last_response_datetime).to match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{4}/)
133
297
  end
134
- end
135
298
 
136
- context 'with non-existant public key file' do
137
- let(:app){ Rack::Builder.new do |b|
138
- b.use Rack::Session::Cookie, {secret: '5242e6bd9daf0e9645c2d4e22b11ba8cee0bed44439906d5f1bd5dad409d8637'}
139
- b.use(OmniAuth::Strategies::Swedbank, PRIVATE_KEY, 'missing-public-key-file.pem', 'MY_SND_ID', 'MY_REC_ID')
140
- b.run lambda{|env| [404, {}, ['Not Found']]}
141
- end.to_app }
299
+ it 'has a VK_RID field' do
300
+ expect(last_response.body).to include('name="VK_RID"')
301
+ end
142
302
 
143
- it 'redirects to /auth/failure with appropriate query params' do
144
- post '/auth/swedbank/callback' # Params are not important, because we're testing public key loading
145
- expect(last_response.status).to eq(302)
146
- expect(last_response.headers['Location']).to eq('/auth/failure?message=public_key_load_err&strategy=swedbank')
303
+ it 'has a 20 byte long nonce field value' do
304
+ expect(last_response_nonce.bytesize).to eq(20)
305
+ end
306
+
307
+ it 'has a correct VK_MAC signature using SHA-512' do
308
+ sig_str =
309
+ '0044012' + # VK_SERVICE
310
+ '003009' + # VK_VERSION
311
+ '009MY_SND_ID' + # VK_SND_ID
312
+ '009MY_REC_ID' + # VK_REC_ID
313
+ "020#{last_response_nonce}" + # VK_NONCE
314
+ "041#{EXPECTED_VALUES_V009['VK_RETURN']}" + # VK_RETURN
315
+ "#{'%03d' % last_response_datetime.length}#{last_response_datetime}" + # VK_DATETIME
316
+ "000" # VK_RID (empty)
317
+
318
+ private_key = OpenSSL::PKey::RSA.new(PRIVATE_KEY)
319
+ expected_mac = Base64.encode64(private_key.sign(OpenSSL::Digest::SHA512.new, sig_str))
320
+ expect(last_response_mac).to eq(expected_mac)
321
+ end
322
+
323
+ it 'does not output a deprecation warning' do
324
+ expect { post('/auth/swedbank', {}, 'rack.session' => {csrf: token}, 'HTTP_X_CSRF_TOKEN' => token) }
325
+ .not_to output(/DEPRECATION/).to_stderr
147
326
  end
148
327
  end
149
328
 
150
- context 'with invalid response' do
151
- it 'detects invalid signature' do
152
- post '/auth/swedbank/callback',
153
- 'VK_SERVICE' => '3003',
154
- 'VK_VERSION' => '008',
155
- 'VK_SND_ID' => 'HP',
156
- 'VK_REC_ID' => 'MY_REC_ID',
157
- 'VK_NONCE' => 'pXXXlocalhostX3000b41292810c0345a7b3770b1c807bed7a',
158
- 'VK_INFO' => 'ISIK:123456-12345;NIMI:Example User',
159
- 'VK_MAC' => 'invalid signature',
160
- 'VK_LANG' => 'LAT',
161
- 'VK_ENCODING' => 'UTF-8'
162
-
163
- expect(last_response.status).to eq(302)
164
- expect(last_response.headers['Location']).to eq('/auth/failure?message=invalid_response_signature_err&strategy=swedbank')
165
- end
166
-
167
- it 'detects unsupported VK_SERVICE values' do
168
- post '/auth/swedbank/callback',
169
- 'VK_SERVICE' => '3004',
170
- 'VK_VERSION' => '008',
171
- 'VK_SND_ID' => 'HP',
172
- 'VK_REC_ID' => 'MY_REC_ID',
173
- 'VK_NONCE' => 'pXXXlocalhostX3000b41292810c0345a7b3770b1c807bed7a',
174
- 'VK_INFO' => 'ISIK:123456-12345;NIMI:Example User',
175
- 'VK_MAC' => 'cmXyp2My7P9pTgrzqJeg7qH+NPCuyaiGNpQIrcCr6S44w0bH+Ao4WDViqytaPH2vENooVPXDSgOcBqHTg44gJ9FlrhI5StiouHVhjpCcWg+h/ERcyc8w58PjsEmdsd4BIpaGXNyhvcIKdWfNwYA1UCIrmFsPAPWfVeorNxp81E7pvY4p4zsqMF80YZ7/RdOpjrtuXJ4nYJ7d+2fXJKKmUlqArCc786DJdb/z8wVDSNA9BZxnf8EE6s//p9gzqLPAg/T9Xp/2024n2JtC6kwsWF614bn64LEZz5c8owZth6FV+2fjnzHxOiifOe+jc9SRstCLITK6Y0j+6n8auiEZ5g==',
176
- 'VK_LANG' => 'LAT',
177
- 'VK_ENCODING' => 'UTF-8'
178
-
179
- expect(last_response.status).to eq(302)
180
- expect(last_response.headers['Location']).to eq('/auth/failure?message=unsupported_response_service_err&strategy=swedbank')
181
- end
182
-
183
- it 'detects unsupported VK_VERSION values' do
184
- post '/auth/swedbank/callback',
185
- 'VK_SERVICE' => '3003',
186
- 'VK_VERSION' => '009',
187
- 'VK_SND_ID' => 'HP',
188
- 'VK_REC_ID' => 'MY_REC_ID',
189
- 'VK_NONCE' => 'pXXXlocalhostX3000b41292810c0345a7b3770b1c807bed7a',
190
- 'VK_INFO' => 'ISIK:123456-12345;NIMI:Example User',
191
- 'VK_MAC' => 'cmXyp2My7P9pTgrzqJeg7qH+NPCuyaiGNpQIrcCr6S44w0bH+Ao4WDViqytaPH2vENooVPXDSgOcBqHTg44gJ9FlrhI5StiouHVhjpCcWg+h/ERcyc8w58PjsEmdsd4BIpaGXNyhvcIKdWfNwYA1UCIrmFsPAPWfVeorNxp81E7pvY4p4zsqMF80YZ7/RdOpjrtuXJ4nYJ7d+2fXJKKmUlqArCc786DJdb/z8wVDSNA9BZxnf8EE6s//p9gzqLPAg/T9Xp/2024n2JtC6kwsWF614bn64LEZz5c8owZth6FV+2fjnzHxOiifOe+jc9SRstCLITK6Y0j+6n8auiEZ5g==',
192
- 'VK_LANG' => 'LAT',
193
- 'VK_ENCODING' => 'UTF-8'
194
-
195
- expect(last_response.status).to eq(302)
196
- expect(last_response.headers['Location']).to eq('/auth/failure?message=unsupported_response_version_err&strategy=swedbank')
197
- end
198
-
199
- it 'detects unsupported VK_ENCODING values' do
200
- post '/auth/swedbank/callback',
201
- 'VK_SERVICE' => '3003',
202
- 'VK_VERSION' => '008',
203
- 'VK_SND_ID' => 'HP',
204
- 'VK_REC_ID' => 'MY_REC_ID',
205
- 'VK_NONCE' => 'pXXXlocalhostX3000b41292810c0345a7b3770b1c807bed7a',
206
- 'VK_INFO' => 'ISIK:123456-12345;NIMI:Example User',
207
- 'VK_MAC' => 'cmXyp2My7P9pTgrzqJeg7qH+NPCuyaiGNpQIrcCr6S44w0bH+Ao4WDViqytaPH2vENooVPXDSgOcBqHTg44gJ9FlrhI5StiouHVhjpCcWg+h/ERcyc8w58PjsEmdsd4BIpaGXNyhvcIKdWfNwYA1UCIrmFsPAPWfVeorNxp81E7pvY4p4zsqMF80YZ7/RdOpjrtuXJ4nYJ7d+2fXJKKmUlqArCc786DJdb/z8wVDSNA9BZxnf8EE6s//p9gzqLPAg/T9Xp/2024n2JtC6kwsWF614bn64LEZz5c8owZth6FV+2fjnzHxOiifOe+jc9SRstCLITK6Y0j+6n8auiEZ5g==',
208
- 'VK_LANG' => 'LAT',
209
- 'VK_ENCODING' => 'ASCII'
210
-
211
- expect(last_response.status).to eq(302)
212
- expect(last_response.headers['Location']).to eq('/auth/failure?message=unsupported_response_encoding_err&strategy=swedbank')
329
+ context 'callback phase' do
330
+ let(:auth_hash){ last_request.env['omniauth.auth'] }
331
+
332
+ # Generate a valid v009 MAC for test data
333
+ let(:v009_response_params) do
334
+ nonce = 'pXXXlocalhostX3000b41292810c0345a7b3770b1c807bed7a'
335
+ datetime = '2026-04-29T12:00:00+0300'
336
+ user_name = 'Example User'
337
+ user_id = '123456-12345'
338
+ country = 'LV'
339
+ other = ''
340
+ token_val = '7'
341
+ rid = ''
342
+
343
+ sig_str = [
344
+ '3013', '009', datetime, 'SWEDBANK_LV', 'MY_REC_ID',
345
+ nonce, user_name, user_id, country, other, token_val, rid
346
+ ].map{|v| '%03d' % v.to_s.length + v.to_s.dup.force_encoding('ascii')}.join
347
+
348
+ private_key = OpenSSL::PKey::RSA.new(PRIVATE_KEY)
349
+ mac = Base64.encode64(private_key.sign(OpenSSL::Digest::SHA512.new, sig_str))
350
+
351
+ {
352
+ 'VK_SERVICE' => '3013',
353
+ 'VK_VERSION' => '009',
354
+ 'VK_DATETIME' => datetime,
355
+ 'VK_SND_ID' => 'SWEDBANK_LV',
356
+ 'VK_REC_ID' => 'MY_REC_ID',
357
+ 'VK_NONCE' => nonce,
358
+ 'VK_USER_NAME' => user_name,
359
+ 'VK_USER_ID' => user_id,
360
+ 'VK_COUNTRY' => country,
361
+ 'VK_OTHER' => other,
362
+ 'VK_TOKEN' => token_val,
363
+ 'VK_RID' => rid,
364
+ 'VK_MAC' => mac,
365
+ 'VK_LANG' => 'LAT',
366
+ 'VK_ENCODING' => 'UTF-8'
367
+ }
368
+ end
369
+
370
+ context 'with valid response' do
371
+ before do
372
+ post '/auth/swedbank/callback', v009_response_params
373
+ end
374
+
375
+ it 'sets the correct uid value in the auth hash' do
376
+ expect(auth_hash.uid).to eq('123456-12345')
377
+ end
378
+
379
+ it 'sets the correct info.full_name value in the auth hash' do
380
+ expect(auth_hash.info.full_name).to eq('Example User')
381
+ end
382
+
383
+ it 'sets the correct info.country value in the auth hash' do
384
+ expect(auth_hash.info.country).to eq('LV')
385
+ end
386
+
387
+ it 'includes all v009 params in extra.raw_info' do
388
+ expect(auth_hash.extra.raw_info).to include(
389
+ 'VK_SERVICE' => '3013',
390
+ 'VK_VERSION' => '009',
391
+ 'VK_SND_ID' => 'SWEDBANK_LV',
392
+ 'VK_USER_NAME' => 'Example User',
393
+ 'VK_USER_ID' => '123456-12345',
394
+ 'VK_COUNTRY' => 'LV',
395
+ 'VK_TOKEN' => '7',
396
+ 'VK_RID' => ''
397
+ )
398
+ end
399
+
400
+ it 'does not include VK_INFO in extra.raw_info' do
401
+ expect(auth_hash.extra.raw_info).not_to have_key('VK_INFO')
402
+ end
403
+ end
404
+
405
+ context 'with invalid response' do
406
+ it 'detects invalid signature' do
407
+ params = v009_response_params.merge('VK_MAC' => 'invalid signature')
408
+ post '/auth/swedbank/callback', params
409
+
410
+ expect(last_response.status).to eq(302)
411
+ expect(last_response.headers['Location']).to eq('/auth/failure?message=invalid_response_signature_err&strategy=swedbank')
412
+ end
413
+
414
+ it 'detects unsupported VK_SERVICE values' do
415
+ params = v009_response_params.merge('VK_SERVICE' => '3003')
416
+ post '/auth/swedbank/callback', params
417
+
418
+ expect(last_response.status).to eq(302)
419
+ expect(last_response.headers['Location']).to eq('/auth/failure?message=unsupported_response_service_err&strategy=swedbank')
420
+ end
421
+
422
+ it 'detects unsupported VK_VERSION values' do
423
+ params = v009_response_params.merge('VK_VERSION' => '008')
424
+ post '/auth/swedbank/callback', params
425
+
426
+ expect(last_response.status).to eq(302)
427
+ expect(last_response.headers['Location']).to eq('/auth/failure?message=unsupported_response_version_err&strategy=swedbank')
428
+ end
429
+
430
+ it 'detects unsupported VK_ENCODING values' do
431
+ params = v009_response_params.merge('VK_ENCODING' => 'ASCII')
432
+ post '/auth/swedbank/callback', params
433
+
434
+ expect(last_response.status).to eq(302)
435
+ expect(last_response.headers['Location']).to eq('/auth/failure?message=unsupported_response_encoding_err&strategy=swedbank')
436
+ end
213
437
  end
214
438
  end
215
439
  end
metadata CHANGED
@@ -1,16 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-swedbank
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mitigate
8
8
  - Jānis Kiršteins
9
9
  - Kristaps Ērglis
10
- autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2023-09-27 00:00:00.000000000 Z
12
+ date: 1980-01-02 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: omniauth
@@ -139,6 +138,7 @@ files:
139
138
  - LICENSE.txt
140
139
  - README.md
141
140
  - Rakefile
141
+ - docs/migration_008_to_009.md
142
142
  - lib/omniauth-swedbank.rb
143
143
  - lib/omniauth/locales/omniauth.en.yml
144
144
  - lib/omniauth/locales/omniauth.lv.yml
@@ -148,13 +148,13 @@ files:
148
148
  - omniauth-swedbank.gemspec
149
149
  - spec/certs/request.private.pem
150
150
  - spec/certs/response.public.pem
151
+ - spec/certs/response.v009.public.pem
151
152
  - spec/omniauth/strategies/swedbank_spec.rb
152
153
  - spec/spec_helper.rb
153
154
  homepage: https://github.com/mitigate-dev/omniauth-swedbank
154
155
  licenses:
155
156
  - MIT
156
157
  metadata: {}
157
- post_install_message:
158
158
  rdoc_options: []
159
159
  require_paths:
160
160
  - lib
@@ -162,19 +162,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
162
162
  requirements:
163
163
  - - ">="
164
164
  - !ruby/object:Gem::Version
165
- version: '2.7'
165
+ version: '3.1'
166
166
  required_rubygems_version: !ruby/object:Gem::Requirement
167
167
  requirements:
168
168
  - - ">="
169
169
  - !ruby/object:Gem::Version
170
170
  version: '0'
171
171
  requirements: []
172
- rubygems_version: 3.3.26
173
- signing_key:
172
+ rubygems_version: 3.6.9
174
173
  specification_version: 4
175
174
  summary: OmniAuth strategy for Swedbank Banklink
176
175
  test_files:
177
176
  - spec/certs/request.private.pem
178
177
  - spec/certs/response.public.pem
178
+ - spec/certs/response.v009.public.pem
179
179
  - spec/omniauth/strategies/swedbank_spec.rb
180
180
  - spec/spec_helper.rb