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 +4 -4
- data/lib/inst_access/config.rb +4 -2
- data/lib/inst_access/token.rb +15 -4
- data/lib/inst_access/version.rb +1 -1
- data/lib/inst_access.rb +8 -4
- data/spec/inst_access/inst_access_spec.rb +12 -0
- data/spec/inst_access/token_spec.rb +41 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5ba1db50c0d9738f9845b2f553e2c3906c9e8bcad0bfb1fbf7d50027b5357f8
|
4
|
+
data.tar.gz: 226f184ea24923d9eb95a836ddb6ee7b03258eed74d070bb2686db85c8075984
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ab6da2741107934b9d52f168a7d88821b850ee60850d7ab2e190531d08c4e3390822faf25f03a0619e3c311f91215315d51d0eac35e67e7d28562b2f8a690ec
|
7
|
+
data.tar.gz: 0c2058387c8f73dea54577a38691fc5eca384dbaf9cac6d04398fac3ebf49b2d0a239c4dc72b9fa49d5cca6953660102b155bb2e11fb0347cb76e0fb3be08fa2
|
data/lib/inst_access/config.rb
CHANGED
@@ -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
|
data/lib/inst_access/token.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
data/lib/inst_access/version.rb
CHANGED
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
|
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.
|
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-
|
11
|
+
date: 2024-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|