Skip to content

Commit b947e17

Browse files
bellebaumanakinj
authored andcommitted
JWKS: Set Tests
1 parent 9870871 commit b947e17

File tree

6 files changed

+151
-9
lines changed

6 files changed

+151
-9
lines changed

lib/jwt/jwk/ec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class EC < KeyBase # rubocop:disable Metrics/ClassLength
99
def_delegators :keypair, :public_key
1010

1111
KTY = 'EC'
12-
KTYS = [KTY, OpenSSL::PKey::EC].freeze
12+
KTYS = [KTY, OpenSSL::PKey::EC, JWT::JWK::EC].freeze
1313
BINARY = 2
1414
EC_PUBLIC_KEY_ELEMENTS = %i[kty crv x y].freeze
1515
EC_PRIVATE_KEY_ELEMENTS = %i[d].freeze

lib/jwt/jwk/hmac.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module JWT
44
module JWK
55
class HMAC < KeyBase
66
KTY = 'oct'
7-
KTYS = [KTY, String].freeze
7+
KTYS = [KTY, String, JWT::JWK::HMAC].freeze
88
HMAC_PUBLIC_KEY_ELEMENTS = %i[kty].freeze
99
HMAC_PRIVATE_KEY_ELEMENTS = %i[k].freeze
1010
HMAC_KEY_ELEMENTS = (HMAC_PRIVATE_KEY_ELEMENTS + HMAC_PUBLIC_KEY_ELEMENTS).freeze

lib/jwt/jwk/key_base.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ def ==(other)
3737
self[:kid] == other[:kid]
3838
end
3939

40+
alias eql? ==
41+
4042
def <=>(other)
4143
self[:kid] <=> other[:kid]
4244
end

lib/jwt/jwk/rsa.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module JWK
55
class RSA < KeyBase
66
BINARY = 2
77
KTY = 'RSA'
8-
KTYS = [KTY, OpenSSL::PKey::RSA].freeze
8+
KTYS = [KTY, OpenSSL::PKey::RSA, JWT::JWK::RSA].freeze
99
RSA_PUBLIC_KEY_ELEMENTS = %i[kty n e].freeze
1010
RSA_PRIVATE_KEY_ELEMENTS = %i[d p q dp dq qi].freeze
1111
RSA_KEY_ELEMENTS = (RSA_PRIVATE_KEY_ELEMENTS + RSA_PUBLIC_KEY_ELEMENTS).freeze

lib/jwt/jwk/set.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# frozen_string_literal: true
2+
23
require 'forwardable'
34

45
module JWT
@@ -9,7 +10,7 @@ class Set
910

1011
attr_reader :keys
1112

12-
def initialize(jwks)
13+
def initialize(jwks = nil, options = {})
1314
jwks ||= {}
1415

1516
@keys = case jwks
@@ -19,9 +20,9 @@ def initialize(jwks)
1920
[jwks]
2021
when Hash
2122
jwks = jwks.transform_keys(&:to_sym)
22-
[*jwks[:keys]].map { |k| JWT::JWK.new k }
23+
[*jwks[:keys]].map { |k| JWT::JWK.new(k, nil, options) }
2324
when Array
24-
jwks.map { |k| JWT::JWK.new k }
25+
jwks.map { |k| JWT::JWK.new(k, nil, options) }
2526
else
2627
raise ArgumentError, 'Can only create new JWKS from Hash, Array and JWK'
2728
end
@@ -46,13 +47,11 @@ def reject!(&block)
4647
end
4748

4849
def uniq!(&block)
49-
return @keys.uniq! unless block
50-
5150
self if @keys.uniq!(&block)
5251
end
5352

5453
def merge(enum)
55-
@keys += JWT::JWK::Set.new(enum.collect)
54+
@keys += JWT::JWK::Set.new(enum.to_a).keys
5655
self
5756
end
5857

@@ -69,6 +68,7 @@ def ==(other)
6968
other.is_a?(JWT::JWK::Set) && keys.sort == other.keys.sort
7069
end
7170

71+
alias eql? ==
7272
alias filter! select!
7373
alias length size
7474
# For symbolic manipulation

spec/jwk/set_spec.rb

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe JWT::JWK::Set do
4+
describe '.new' do
5+
it 'can create an empty set' do
6+
expect(described_class.new.keys).to eql([])
7+
end
8+
9+
context 'can create a set' do
10+
it 'from a JWK' do
11+
jwk = JWT::JWK.new 'testkey'
12+
expect(described_class.new(jwk).keys).to eql([jwk])
13+
end
14+
15+
it 'from a JWKS hash with symbol keys' do
16+
jwks = { keys: [{ kty: 'oct', k: 'testkey' }] }
17+
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
18+
expect(described_class.new(jwks).keys).to eql([jwk])
19+
end
20+
21+
it 'from a JWKS hash with string keys' do
22+
jwks = { 'keys' => [{ 'kty' => 'oct', 'k' => 'testkey' }] }
23+
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
24+
expect(described_class.new(jwks).keys).to eql([jwk])
25+
end
26+
27+
it 'from an array of keys' do
28+
jwk = JWT::JWK.new 'testkey'
29+
expect(described_class.new([jwk]).keys).to eql([jwk])
30+
end
31+
32+
it 'from an existing JWT::JWK::Set' do
33+
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
34+
jwks = described_class.new(jwk)
35+
expect(described_class.new(jwks)).to eql(jwks)
36+
end
37+
end
38+
39+
it 'raises an error on invalid inputs' do
40+
expect { described_class.new(42) }.to raise_error(ArgumentError)
41+
end
42+
end
43+
44+
describe '.export' do
45+
it 'exports the JWKS to Hash' do
46+
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
47+
jwks = described_class.new(jwk)
48+
exported = jwks.export
49+
expect(exported[:keys].size).to eql(1)
50+
expect(exported[:keys][0]).to eql(jwk.export)
51+
end
52+
end
53+
54+
describe '.eql?' do
55+
it 'correctly classifies equal sets' do
56+
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
57+
jwks1 = described_class.new(jwk)
58+
jwks2 = described_class.new(jwk)
59+
expect(jwks1).to eql(jwks2)
60+
end
61+
62+
it 'correctly classifies different sets' do
63+
jwk1 = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
64+
jwk2 = JWT::JWK.new({ kty: 'oct', k: 'testkex' })
65+
jwks1 = described_class.new(jwk1)
66+
jwks2 = described_class.new(jwk2)
67+
expect(jwks1).not_to eql(jwks2)
68+
end
69+
end
70+
71+
# TODO: No idea why this does not work. eql? returns true for the two elements,
72+
# but Array#uniq! doesn't recognize this, despite the documentation saying otherwise
73+
describe '.uniq!' do
74+
it 'filters out equal keys' do
75+
jwk = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
76+
jwk2 = JWT::JWK.new({ kty: 'oct', k: 'testkey' })
77+
jwks = described_class.new([jwk, jwk2])
78+
jwks.uniq!
79+
expect(jwks.keys.size).to eql(1)
80+
end
81+
end
82+
83+
describe '.select!' do
84+
it 'filters the keyset' do
85+
jwks = described_class.new([])
86+
jwks << JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
87+
jwks << JWT::JWK.new(OpenSSL::PKey::EC.generate('secp384r1'))
88+
jwks.select! { |k| k[:kty] == 'RSA' }
89+
expect(jwks.size).to eql(1)
90+
expect(jwks.keys[0][:kty]).to eql('RSA')
91+
end
92+
end
93+
94+
describe '.reject!' do
95+
it 'filters the keyset' do
96+
jwks = described_class.new([])
97+
jwks << JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
98+
jwks << JWT::JWK.new(OpenSSL::PKey::EC.generate('secp384r1'))
99+
jwks.reject! { |k| k[:kty] == 'RSA' }
100+
expect(jwks.size).to eql(1)
101+
expect(jwks.keys[0][:kty]).to eql('EC')
102+
end
103+
end
104+
105+
describe '.merge' do
106+
context 'merges two JWKSs' do
107+
it 'when called via .union' do
108+
jwks1 = described_class.new(JWT::JWK.new(OpenSSL::PKey::RSA.new(2048)))
109+
jwks2 = described_class.new(JWT::JWK.new(OpenSSL::PKey::EC.generate('secp384r1')))
110+
jwks3 = jwks1.union(jwks2)
111+
expect(jwks1.size).to eql(1)
112+
expect(jwks2.size).to eql(1)
113+
expect(jwks3.size).to eql(2)
114+
expect(jwks3.keys).to include(jwks1.keys[0])
115+
expect(jwks3.keys).to include(jwks2.keys[0])
116+
end
117+
118+
it 'when called via "|" operator' do
119+
jwks1 = described_class.new(JWT::JWK.new(OpenSSL::PKey::RSA.new(2048)))
120+
jwks2 = described_class.new(JWT::JWK.new(OpenSSL::PKey::EC.generate('secp384r1')))
121+
jwks3 = jwks1 | jwks2
122+
expect(jwks1.size).to eql(1)
123+
expect(jwks2.size).to eql(1)
124+
expect(jwks3.size).to eql(2)
125+
expect(jwks3.keys).to include(jwks1.keys[0])
126+
expect(jwks3.keys).to include(jwks2.keys[0])
127+
end
128+
129+
it 'when called directly' do
130+
jwks1 = described_class.new(JWT::JWK.new(OpenSSL::PKey::RSA.new(2048)))
131+
jwks2 = described_class.new(JWT::JWK.new(OpenSSL::PKey::EC.generate('secp384r1')))
132+
jwks3 = jwks1.merge(jwks2)
133+
expect(jwks1.size).to eql(2)
134+
expect(jwks2.size).to eql(1)
135+
expect(jwks3).to eql(jwks1)
136+
expect(jwks3.keys).to include(jwks2.keys[0])
137+
end
138+
end
139+
end
140+
end

0 commit comments

Comments
 (0)