inst_access 0.4.1 → 0.4.3

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: 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