inst_access 0.4.2 → 0.4.4

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: cbec71c0ed08813dd78884db0a007ea381e8aa6c66eed9f716e2f98979034e2b
4
- data.tar.gz: 49f346a1e2caedcd79e0e68e9ab141ce2ec38a6f4f8936181296af43a318d6b9
3
+ metadata.gz: d5ba1db50c0d9738f9845b2f553e2c3906c9e8bcad0bfb1fbf7d50027b5357f8
4
+ data.tar.gz: 226f184ea24923d9eb95a836ddb6ee7b03258eed74d070bb2686db85c8075984
5
5
  SHA512:
6
- metadata.gz: d6884908d27623419e8ac893fd9f101081303c6f0977cb1ef6a7bbdcd38a50f963235620bb59c39b738c67b63d968493b4e555e8d09b9b6e7e7c3e8d151dc6d4
7
- data.tar.gz: 6de8f23b7d042a166d4f60b9ff57bfc9d855cc69b2ad9436ec1e278a88908b614f264370f2cde6fc93480e15bdb0bb0a91c5f111c67c680661bf1f077f9a8a7f
6
+ metadata.gz: 7ab6da2741107934b9d52f168a7d88821b850ee60850d7ab2e190531d08c4e3390822faf25f03a0619e3c311f91215315d51d0eac35e67e7d28562b2f8a690ec
7
+ data.tar.gz: 0c2058387c8f73dea54577a38691fc5eca384dbaf9cac6d04398fac3ebf49b2d0a239c4dc72b9fa49d5cca6953660102b155bb2e11fb0347cb76e0fb3be08fa2
@@ -22,14 +22,16 @@ require 'openssl'
22
22
 
23
23
  module InstAccess
24
24
  class Config
25
- attr_reader :signing_key
25
+ attr_reader :signing_key, :issuers, :service_jwks
26
26
 
27
- def initialize(raw_signing_key, raw_encryption_key = nil)
27
+ def initialize(raw_signing_key, raw_encryption_key = nil, issuers: nil, service_jwks: [])
28
28
  @signing_key = OpenSSL::PKey::RSA.new(raw_signing_key)
29
29
  if raw_encryption_key
30
30
  @encryption_key = OpenSSL::PKey::RSA.new(raw_encryption_key)
31
31
  raise ArgumentError, 'the encryption key should be a public RSA key' if @encryption_key.private?
32
32
  end
33
+ @issuers = issuers
34
+ @service_jwks = service_jwks
33
35
  rescue OpenSSL::PKey::RSAError => e
34
36
  raise ArgumentError, e
35
37
  end
@@ -106,14 +106,15 @@ module InstAccess
106
106
  region: nil,
107
107
  client_id: nil,
108
108
  instructure_service: nil,
109
- canvas_shard_id: nil
109
+ canvas_shard_id: nil,
110
+ issuer: nil
110
111
  )
111
112
  raise ArgumentError, 'Must provide user uuid and account uuid' if user_uuid.blank? || account_uuid.blank?
112
113
 
113
114
  now = Time.now.to_i
114
115
 
115
116
  payload = {
116
- iss: ISSUER,
117
+ iss: issuer || ISSUER,
117
118
  jti: SecureRandom.uuid,
118
119
  iat: now,
119
120
  exp: now + 1.hour.to_i,
@@ -136,8 +137,17 @@ module InstAccess
136
137
 
137
138
  # Takes an unencrypted (but signed) token string
138
139
  def from_token_string(jws)
140
+ service_jwks = InstAccess.config.service_jwks
141
+ jwt = if service_jwks.present?
142
+ begin
143
+ JSON::JWT.decode(jws, service_jwks)
144
+ rescue StandardError
145
+ nil
146
+ end
147
+ end
148
+
139
149
  sig_key = InstAccess.config.signing_key
140
- jwt = begin
150
+ jwt ||= begin
141
151
  JSON::JWT.decode(jws, sig_key)
142
152
  rescue StandardError => e
143
153
  raise InvalidToken, e
@@ -149,7 +159,8 @@ module InstAccess
149
159
 
150
160
  def token?(string)
151
161
  jwt = JSON::JWT.decode(string, :skip_verification)
152
- jwt[:iss] == ISSUER
162
+ InstAccess.configured? && (issuers = InstAccess.config.issuers)
163
+ issuers&.include?(jwt[:iss]) || jwt[:iss] == ISSUER
153
164
  rescue StandardError
154
165
  false
155
166
  end
@@ -19,5 +19,5 @@
19
19
  #
20
20
 
21
21
  module InstAccess
22
- VERSION = '0.4.2'
22
+ VERSION = '0.4.4'
23
23
  end
data/lib/inst_access.rb CHANGED
@@ -32,15 +32,15 @@ module InstAccess
32
32
  # consuming tokens, it can be the RSA public key corresponding to the
33
33
  # private key that signed them.
34
34
  # encryption_key is only required if you are going to be producing tokens.
35
- def configure(signing_key:, encryption_key: nil)
36
- @config = Config.new(signing_key, encryption_key)
35
+ def configure(signing_key:, encryption_key: nil, issuers: nil, service_jwks: nil)
36
+ @config = Config.new(signing_key, encryption_key, issuers: issuers, service_jwks: service_jwks)
37
37
  end
38
38
 
39
39
  # set a configuration only for the duration of the given block, then revert
40
40
  # it. useful for testing.
41
- def with_config(signing_key:, encryption_key: nil)
41
+ def with_config(signing_key:, encryption_key: nil, issuers: nil, service_jwks: nil)
42
42
  old_config = @config
43
- configure(signing_key: signing_key, encryption_key: encryption_key)
43
+ configure(signing_key: signing_key, encryption_key: encryption_key, issuers: issuers, service_jwks: service_jwks)
44
44
  yield
45
45
  ensure
46
46
  @config = old_config
@@ -49,5 +49,9 @@ module InstAccess
49
49
  def config
50
50
  @config || raise(ConfigError, 'InstAccess is not configured!')
51
51
  end
52
+
53
+ def configured?
54
+ @config.present?
55
+ end
52
56
  end
53
57
  end
@@ -44,4 +44,16 @@ describe InstAccess do
44
44
  end.to raise_error(ArgumentError)
45
45
  end
46
46
  end
47
+
48
+ describe '.configured?' do
49
+ it 'returns false if not configured' do
50
+ expect(described_class.configured?).to eq(false)
51
+ end
52
+
53
+ it 'returns true if configured' do
54
+ InstAccess.with_config(signing_key: signing_priv_key) do
55
+ expect(described_class.configured?).to eq(true)
56
+ end
57
+ end
58
+ end
47
59
  end
@@ -28,10 +28,12 @@ describe InstAccess::Token do
28
28
  let(:signing_pub_key) { signing_keypair.public_key.to_s }
29
29
  let(:encryption_priv_key) { encryption_keypair.to_s }
30
30
  let(:encryption_pub_key) { encryption_keypair.public_key.to_s }
31
+ let(:issuers) {}
32
+ let(:issuer) {}
31
33
 
32
- let(:a_token) { described_class.for_user(user_uuid: 'user-uuid', account_uuid: 'acct-uuid') }
34
+ let(:a_token) { described_class.for_user(user_uuid: 'user-uuid', account_uuid: 'acct-uuid', issuer: issuer) }
33
35
  let(:unencrypted_token) do
34
- InstAccess.with_config(signing_key: signing_priv_key) do
36
+ InstAccess.with_config(signing_key: signing_priv_key, issuers: issuers) do
35
37
  a_token.to_unencrypted_token_string
36
38
  end
37
39
  end
@@ -41,7 +43,7 @@ describe InstAccess::Token do
41
43
  expect(described_class.token?('asdf1234stuff')).to eq(false)
42
44
  end
43
45
 
44
- it 'returns false for JWTs from a different issuer' do
46
+ it 'returns false for JWTs from an issuer not in the list' do
45
47
  jwt = JSON::JWT.new(iss: 'bridge').to_s
46
48
  expect(described_class.token?(jwt)).to eq(false)
47
49
  end
@@ -50,6 +52,31 @@ describe InstAccess::Token do
50
52
  expect(described_class.token?(unencrypted_token)).to eq(true)
51
53
  end
52
54
 
55
+ context 'with issuers configured' do
56
+ let(:issuers) { ['token_from_other_service'] }
57
+ let(:issuer) { 'token_from_other_service' }
58
+
59
+ it 'returns true for JWTs from an issuer in the list' do
60
+ jwt = JSON::JWT.decode(unencrypted_token, :skip_verification)
61
+ expect(jwt[:iss]).to eq('token_from_other_service')
62
+ InstAccess.with_config(signing_key: signing_priv_key, issuers: issuers) do
63
+ expect(described_class.token?(unencrypted_token)).to eq(true)
64
+ end
65
+ end
66
+
67
+ it 'returns true for JWTs from the default issuer when issuers are configured' do
68
+ token = described_class.for_user(user_uuid: 'user-uuid', account_uuid: 'acct-uuid')
69
+ jws = InstAccess.with_config(signing_key: signing_priv_key) do
70
+ token.to_unencrypted_token_string
71
+ end
72
+ jwt = JSON::JWT.decode(jws, :skip_verification)
73
+ expect(jwt[:iss]).to eq(InstAccess::Token::ISSUER)
74
+ InstAccess.with_config(signing_key: signing_priv_key, issuers: issuers) do
75
+ expect(described_class.token?(jws)).to eq(true)
76
+ end
77
+ end
78
+ end
79
+
53
80
  it 'returns true for an expired InstAccess token' do
54
81
  token = unencrypted_token # instantiate it to set the expiration
55
82
  Timecop.travel(3601) do
@@ -189,6 +216,17 @@ describe InstAccess::Token do
189
216
  end.to raise_error(InstAccess::InvalidToken)
190
217
  end
191
218
  end
219
+
220
+ it '.from_token_string decodes a token signed by a key in a configured JSON::JWK::Set' do
221
+ jwk = JSON::JWK.new(kid: 'token_from_other_service/file_authorization', k: 'hmac_secret', kty: 'oct')
222
+ payload = { sub: 'user-uuid', account_uuid: 'acct-uuid', issuer: 'other_service', exp: 5.minutes.from_now }
223
+ jws = JSON::JWT.new(payload).sign(jwk).to_s
224
+ jwk_set = JSON::JWK::Set.new([jwk])
225
+ InstAccess.with_config(signing_key: signing_priv_key, issuers: ['other_service'], service_jwks: jwk_set) do
226
+ token = described_class.from_token_string(jws)
227
+ expect(token.user_uuid).to eq('user-uuid')
228
+ end
229
+ end
192
230
  end
193
231
 
194
232
  context 'when configured for token generation' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inst_access
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Ziwisky
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-09 00:00:00.000000000 Z
11
+ date: 2024-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport