inst_access 0.4.2 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
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