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 +4 -4
- data/lib/inst_access/config.rb +4 -2
- data/lib/inst_access/token.rb +22 -5
- 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 +34 -5
- 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: a559457afcb20a5ef4814c6320932e837a2c40713956ca89c72dbae487e41a47
|
4
|
+
data.tar.gz: a075d8e49d0244b3a2a36b7246500f4bbd833cdacc954b5320e066164824a822
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0de198d5e310d3262dbe9bb558b2fee295dba2977e50b83cfdd4cb5e54d8582f8de9d209e2c0c3d590a84456c367a40b030e574cf0ccbcd5ee99a7572a33322
|
7
|
+
data.tar.gz: 97d977d4b0a4f83b93702d3b71635629ba710046f23dcd71f3f7ff333c15ed5eca5263dfccd667adc2f2397fb35e95e0cc7d25482276870d2fda6d87899d3ec3
|
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
@@ -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
|
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
|
-
|
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
|
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,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.
|
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:
|
11
|
+
date: 2024-09-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|