inst_access 0.4.1 → 0.4.3

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: 3725e7d832fcddd123708c45dadc6541942fbd9dd01828a5b263aa76f9f048ef
4
- data.tar.gz: aa296914f1621d6b2f54829b3659b8ede1c719a89ea6357b9c65a8b4ce825990
3
+ metadata.gz: a559457afcb20a5ef4814c6320932e837a2c40713956ca89c72dbae487e41a47
4
+ data.tar.gz: a075d8e49d0244b3a2a36b7246500f4bbd833cdacc954b5320e066164824a822
5
5
  SHA512:
6
- metadata.gz: 3fb2fbe2884970be8de2a6a82b6a8b7011631f1243ab3accb9b547396591ada3a6f2741644ee4d1e2bf2cd598a434b2e978ec5ce93998ef0ff35ee69f5f2dabd
7
- data.tar.gz: 8d64e7490c1a4ae99216a94c6cd4b18227daec66d96111ba4497713ab8fb3300ee176318f24eb3dd18eab89efe8afef67949a35facca09103e9cd36c8c6d1f31
6
+ metadata.gz: d0de198d5e310d3262dbe9bb558b2fee295dba2977e50b83cfdd4cb5e54d8582f8de9d209e2c0c3d590a84456c367a40b030e574cf0ccbcd5ee99a7572a33322
7
+ data.tar.gz: 97d977d4b0a4f83b93702d3b71635629ba710046f23dcd71f3f7ff333c15ed5eca5263dfccd667adc2f2397fb35e95e0cc7d25482276870d2fda6d87899d3ec3
@@ -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
@@ -62,6 +62,10 @@ module InstAccess
62
62
  jwt_payload[:instructure_service] == true
63
63
  end
64
64
 
65
+ def canvas_shard_id
66
+ jwt_payload[:canvas_shard_id]
67
+ end
68
+
65
69
  def jti
66
70
  jwt_payload[:jti]
67
71
  end
@@ -101,14 +105,16 @@ module InstAccess
101
105
  real_user_global_id: nil,
102
106
  region: nil,
103
107
  client_id: nil,
104
- instructure_service: nil
108
+ instructure_service: nil,
109
+ canvas_shard_id: nil,
110
+ issuer: nil
105
111
  )
106
112
  raise ArgumentError, 'Must provide user uuid and account uuid' if user_uuid.blank? || account_uuid.blank?
107
113
 
108
114
  now = Time.now.to_i
109
115
 
110
116
  payload = {
111
- iss: ISSUER,
117
+ iss: issuer || ISSUER,
112
118
  jti: SecureRandom.uuid,
113
119
  iat: now,
114
120
  exp: now + 1.hour.to_i,
@@ -121,7 +127,8 @@ module InstAccess
121
127
  debug_masq_global_id: real_user_global_id&.to_s,
122
128
  region: region,
123
129
  client_id: client_id,
124
- instructure_service: instructure_service
130
+ instructure_service: instructure_service,
131
+ canvas_shard_id: canvas_shard_id
125
132
  }.compact
126
133
 
127
134
  new(payload)
@@ -130,8 +137,17 @@ module InstAccess
130
137
 
131
138
  # Takes an unencrypted (but signed) token string
132
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
+
133
149
  sig_key = InstAccess.config.signing_key
134
- jwt = begin
150
+ jwt ||= begin
135
151
  JSON::JWT.decode(jws, sig_key)
136
152
  rescue StandardError => e
137
153
  raise InvalidToken, e
@@ -143,7 +159,8 @@ module InstAccess
143
159
 
144
160
  def token?(string)
145
161
  jwt = JSON::JWT.decode(string, :skip_verification)
146
- jwt[:iss] == ISSUER
162
+ issuers = InstAccess.configured? && (issuers = InstAccess.config.issuers)
163
+ issuers ? issuers.include?(jwt[:iss]) : jwt[:iss] == ISSUER
147
164
  rescue StandardError
148
165
  false
149
166
  end
@@ -19,5 +19,5 @@
19
19
  #
20
20
 
21
21
  module InstAccess
22
- VERSION = '0.4.1'
22
+ VERSION = '0.4.3'
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,19 @@ 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
+ end
67
+
53
68
  it 'returns true for an expired InstAccess token' do
54
69
  token = unencrypted_token # instantiate it to set the expiration
55
70
  Timecop.travel(3601) do
@@ -87,7 +102,8 @@ describe InstAccess::Token do
87
102
  real_user_shard_id: 5,
88
103
  region: 'us-west-2',
89
104
  client_id: 'client-id',
90
- instructure_service: true
105
+ instructure_service: true,
106
+ canvas_shard_id: 3
91
107
  )
92
108
  expect(id.canvas_domain).to eq('z.instructure.com')
93
109
  expect(id.masquerading_user_uuid).to eq('masq-id')
@@ -95,6 +111,7 @@ describe InstAccess::Token do
95
111
  expect(id.region).to eq('us-west-2')
96
112
  expect(id.client_id).to eq('client-id')
97
113
  expect(id.instructure_service?).to eq true
114
+ expect(id.canvas_shard_id).to eq(3)
98
115
  end
99
116
 
100
117
  it 'generates a unique jti' do
@@ -110,7 +127,8 @@ describe InstAccess::Token do
110
127
  real_user_shard_id: 5,
111
128
  region: 'us-west-2',
112
129
  client_id: 'client-id',
113
- instructure_service: true
130
+ instructure_service: true,
131
+ canvas_shard_id: 3
114
132
  )
115
133
 
116
134
  expect(id.jti).to eq uuid
@@ -186,6 +204,17 @@ describe InstAccess::Token do
186
204
  end.to raise_error(InstAccess::InvalidToken)
187
205
  end
188
206
  end
207
+
208
+ it '.from_token_string decodes a token signed by a key in a configured JSON::JWK::Set' do
209
+ jwk = JSON::JWK.new(kid: 'token_from_other_service/file_authorization', k: 'hmac_secret', kty: 'oct')
210
+ payload = { sub: 'user-uuid', account_uuid: 'acct-uuid', issuer: 'other_service', exp: 5.minutes.from_now }
211
+ jws = JSON::JWT.new(payload).sign(jwk).to_s
212
+ jwk_set = JSON::JWK::Set.new([jwk])
213
+ InstAccess.with_config(signing_key: signing_priv_key, issuers: ['other_service'], service_jwks: jwk_set) do
214
+ token = described_class.from_token_string(jws)
215
+ expect(token.user_uuid).to eq('user-uuid')
216
+ end
217
+ end
189
218
  end
190
219
 
191
220
  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.1
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Ziwisky
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-06 00:00:00.000000000 Z
11
+ date: 2024-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport