inst_access 0.1.0
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 +7 -0
- data/lib/inst_access/config.rb +41 -0
- data/lib/inst_access/errors.rb +29 -0
- data/lib/inst_access/token.rb +128 -0
- data/lib/inst_access.rb +52 -0
- data/spec/initialize_coverage.rb +28 -0
- data/spec/inst_access/inst_access_spec.rb +47 -0
- data/spec/inst_access/token_spec.rb +172 -0
- data/spec/spec_helper.rb +26 -0
- data/test.sh +4 -0
- metadata +181 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8e81a1fd534b4927401037a4865828b0f402ee0b81a18c5d44e4ddeaf67205a8
|
4
|
+
data.tar.gz: d2a3cd8174208a3b943d20f9b57c0126585f7dd6bb1516821f2fa959771b6063
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6ec033d6636a968926163b1ba44e4168efa78520a108c3ba0809e309d2564fedba1de0c41740a7d545cf424a3bd91225e929588ecf1e62fa31f527091eb01935
|
7
|
+
data.tar.gz: cf5b9d6012d3fec913dffa5195b088baa5c42140c1e78bfb8cdaffb55d623f1d44cfb5d9d91fa066d961e42b8661cddb5ce5a38af92d2e87eb51812537be6ee2
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2021 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'openssl'
|
22
|
+
|
23
|
+
module InstAccess
|
24
|
+
class Config
|
25
|
+
attr_reader :signing_key
|
26
|
+
|
27
|
+
def initialize(raw_signing_key, raw_encryption_key = nil)
|
28
|
+
@signing_key = OpenSSL::PKey::RSA.new(raw_signing_key)
|
29
|
+
if raw_encryption_key
|
30
|
+
@encryption_key = OpenSSL::PKey::RSA.new(raw_encryption_key)
|
31
|
+
raise ArgumentError, 'the encryption key should be a public RSA key' if @encryption_key.private?
|
32
|
+
end
|
33
|
+
rescue OpenSSL::PKey::RSAError => e
|
34
|
+
raise ArgumentError, e
|
35
|
+
end
|
36
|
+
|
37
|
+
def encryption_key
|
38
|
+
@encryption_key || raise(ConfigError, 'Encryption key is not configured')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2021 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
module InstAccess
|
22
|
+
class Error < StandardError; end
|
23
|
+
|
24
|
+
class ConfigError < Error; end
|
25
|
+
|
26
|
+
class InvalidToken < Error; end
|
27
|
+
|
28
|
+
class TokenExpired < Error; end
|
29
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2021 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
module InstAccess
|
22
|
+
class Token
|
23
|
+
ISSUER = 'instructure:inst_access'
|
24
|
+
ENCRYPTION_ALGO = :'RSA-OAEP'
|
25
|
+
ENCRYPTION_METHOD = :'A128CBC-HS256'
|
26
|
+
|
27
|
+
attr_reader :jwt_payload
|
28
|
+
|
29
|
+
def initialize(jwt_payload)
|
30
|
+
@jwt_payload = jwt_payload.symbolize_keys
|
31
|
+
end
|
32
|
+
|
33
|
+
def user_uuid
|
34
|
+
jwt_payload[:sub]
|
35
|
+
end
|
36
|
+
|
37
|
+
def account_uuid
|
38
|
+
jwt_payload[:acct]
|
39
|
+
end
|
40
|
+
|
41
|
+
def canvas_domain
|
42
|
+
jwt_payload[:canvas_domain]
|
43
|
+
end
|
44
|
+
|
45
|
+
def masquerading_user_uuid
|
46
|
+
jwt_payload[:masq_sub]
|
47
|
+
end
|
48
|
+
|
49
|
+
def masquerading_user_shard_id
|
50
|
+
jwt_payload[:masq_shard]
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_token_string
|
54
|
+
jwe = to_jws.encrypt(InstAccess.config.encryption_key, ENCRYPTION_ALGO, ENCRYPTION_METHOD)
|
55
|
+
jwe.to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
# only for testing purposes, or to do local dev w/o running a decrypting
|
59
|
+
# service. unencrypted tokens should not be released into the wild!
|
60
|
+
def to_unencrypted_token_string
|
61
|
+
to_jws.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def to_jws
|
67
|
+
key = InstAccess.config.signing_key
|
68
|
+
raise ConfigError, 'Private signing key needed to produce tokens' unless key.private?
|
69
|
+
|
70
|
+
jwt = JSON::JWT.new(jwt_payload)
|
71
|
+
jwt.sign(key)
|
72
|
+
end
|
73
|
+
|
74
|
+
class << self
|
75
|
+
private :new
|
76
|
+
|
77
|
+
# rubocop:disable Metrics/ParameterLists, Metrics/CyclomaticComplexity
|
78
|
+
def for_user(
|
79
|
+
user_uuid: nil,
|
80
|
+
account_uuid: nil,
|
81
|
+
canvas_domain: nil,
|
82
|
+
real_user_uuid: nil,
|
83
|
+
real_user_shard_id: nil,
|
84
|
+
user_global_id: nil,
|
85
|
+
real_user_global_id: nil
|
86
|
+
)
|
87
|
+
raise ArgumentError, 'Must provide user uuid and account uuid' if user_uuid.blank? || account_uuid.blank?
|
88
|
+
|
89
|
+
now = Time.now.to_i
|
90
|
+
payload = {
|
91
|
+
iss: ISSUER,
|
92
|
+
iat: now,
|
93
|
+
exp: now + 1.hour.to_i,
|
94
|
+
sub: user_uuid,
|
95
|
+
acct: account_uuid
|
96
|
+
}
|
97
|
+
payload[:canvas_domain] = canvas_domain if canvas_domain
|
98
|
+
payload[:masq_sub] = real_user_uuid if real_user_uuid
|
99
|
+
payload[:masq_shard] = real_user_shard_id if real_user_shard_id
|
100
|
+
payload[:debug_user_global_id] = user_global_id.to_s if user_global_id
|
101
|
+
payload[:debug_masq_global_id] = real_user_global_id.to_s if real_user_global_id
|
102
|
+
|
103
|
+
new(payload)
|
104
|
+
end
|
105
|
+
# rubocop:enable Metrics/ParameterLists, Metrics/CyclomaticComplexity
|
106
|
+
|
107
|
+
# Takes an unencrypted (but signed) token string
|
108
|
+
def from_token_string(jws)
|
109
|
+
sig_key = InstAccess.config.signing_key
|
110
|
+
jwt = begin
|
111
|
+
JSON::JWT.decode(jws, sig_key)
|
112
|
+
rescue StandardError => e
|
113
|
+
raise InvalidToken, e
|
114
|
+
end
|
115
|
+
raise TokenExpired if jwt[:exp] < Time.now.to_i
|
116
|
+
|
117
|
+
new(jwt.to_hash)
|
118
|
+
end
|
119
|
+
|
120
|
+
def token?(string)
|
121
|
+
jwt = JSON::JWT.decode(string, :skip_verification)
|
122
|
+
jwt[:iss] == ISSUER
|
123
|
+
rescue StandardError
|
124
|
+
false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/inst_access.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2021 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'active_support'
|
22
|
+
require 'json/jwt'
|
23
|
+
require 'inst_access/errors'
|
24
|
+
require 'inst_access/config'
|
25
|
+
require 'inst_access/token'
|
26
|
+
|
27
|
+
module InstAccess
|
28
|
+
class << self
|
29
|
+
# signing_key is required. if you are going to be producing (and therefore
|
30
|
+
# signing) tokens, this needs to be an RSA private key. if you're just
|
31
|
+
# consuming tokens, it can be the RSA public key corresponding to the
|
32
|
+
# private key that signed them.
|
33
|
+
# encryption_key is only required if you are going to be producing tokens.
|
34
|
+
def configure(signing_key:, encryption_key: nil)
|
35
|
+
@config = Config.new(signing_key, encryption_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
# set a configuration only for the duration of the given block, then revert
|
39
|
+
# it. useful for testing.
|
40
|
+
def with_config(signing_key:, encryption_key: nil)
|
41
|
+
old_config = @config
|
42
|
+
configure(signing_key: signing_key, encryption_key: encryption_key)
|
43
|
+
yield
|
44
|
+
ensure
|
45
|
+
@config = old_config
|
46
|
+
end
|
47
|
+
|
48
|
+
def config
|
49
|
+
@config || raise(ConfigError, 'InstAccess is not configured!')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'simplecov'
|
5
|
+
require 'simplecov-json'
|
6
|
+
|
7
|
+
SimpleCov.formatters = [
|
8
|
+
SimpleCov::Formatter::HTMLFormatter,
|
9
|
+
SimpleCov::Formatter::JSONFormatter
|
10
|
+
]
|
11
|
+
|
12
|
+
SimpleCov.start do
|
13
|
+
enable_coverage :branch
|
14
|
+
|
15
|
+
add_filter '/spec/'
|
16
|
+
end
|
17
|
+
|
18
|
+
unless ENV.key?('TEST_ENV_NUMBER') || ENV['DISABLE_MINIMUM_COVERAGE']
|
19
|
+
# SimpleCov supports merging coverage results from parallel test runs,
|
20
|
+
# but it doesn't support a global minimum coverage check when running in
|
21
|
+
# parallel, so builds fail because subsets of the tests return < min%.
|
22
|
+
SimpleCov.minimum_coverage(98)
|
23
|
+
SimpleCov.maximum_coverage_drop(3)
|
24
|
+
SimpleCov.minimum_coverage_by_file(78)
|
25
|
+
end
|
26
|
+
rescue LoadError => e
|
27
|
+
puts "Error: #{e}"
|
28
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2021 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'spec_helper'
|
22
|
+
|
23
|
+
describe InstAccess do
|
24
|
+
let(:signing_keypair) { OpenSSL::PKey::RSA.new(2048) }
|
25
|
+
let(:encryption_keypair) { OpenSSL::PKey::RSA.new(2048) }
|
26
|
+
let(:signing_priv_key) { signing_keypair.to_s }
|
27
|
+
let(:signing_pub_key) { signing_keypair.public_key.to_s }
|
28
|
+
let(:encryption_priv_key) { encryption_keypair.to_s }
|
29
|
+
let(:encryption_pub_key) { encryption_keypair.public_key.to_s }
|
30
|
+
|
31
|
+
describe '.configure' do
|
32
|
+
it 'blows up if you try to pass a private key for encryption' do
|
33
|
+
expect do
|
34
|
+
described_class.configure(
|
35
|
+
signing_key: signing_priv_key,
|
36
|
+
encryption_key: encryption_priv_key
|
37
|
+
)
|
38
|
+
end.to raise_error(ArgumentError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "blows up if you pass it something that isn't an RSA key" do
|
42
|
+
expect do
|
43
|
+
described_class.configure(signing_key: 'asdf123')
|
44
|
+
end.to raise_error(ArgumentError)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2021 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'spec_helper'
|
22
|
+
require 'timecop'
|
23
|
+
|
24
|
+
describe InstAccess::Token do
|
25
|
+
let(:signing_keypair) { OpenSSL::PKey::RSA.new(2048) }
|
26
|
+
let(:encryption_keypair) { OpenSSL::PKey::RSA.new(2048) }
|
27
|
+
let(:signing_priv_key) { signing_keypair.to_s }
|
28
|
+
let(:signing_pub_key) { signing_keypair.public_key.to_s }
|
29
|
+
let(:encryption_priv_key) { encryption_keypair.to_s }
|
30
|
+
let(:encryption_pub_key) { encryption_keypair.public_key.to_s }
|
31
|
+
|
32
|
+
let(:a_token) { described_class.for_user(user_uuid: 'user-uuid', account_uuid: 'acct-uuid') }
|
33
|
+
let(:unencrypted_token) do
|
34
|
+
InstAccess.with_config(signing_key: signing_priv_key) do
|
35
|
+
a_token.to_unencrypted_token_string
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '.token?' do
|
40
|
+
it 'returns false for non-JWTs' do
|
41
|
+
expect(described_class.token?('asdf1234stuff')).to eq(false)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns false for JWTs from a different issuer' do
|
45
|
+
jwt = JSON::JWT.new(iss: 'bridge').to_s
|
46
|
+
expect(described_class.token?(jwt)).to eq(false)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'returns true for an InstAccess token' do
|
50
|
+
expect(described_class.token?(unencrypted_token)).to eq(true)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns true for an expired InstAccess token' do
|
54
|
+
token = unencrypted_token # instantiate it to set the expiration
|
55
|
+
Timecop.travel(3601) do
|
56
|
+
expect(described_class.token?(token)).to eq(true)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '.for_user' do
|
62
|
+
it 'blows up without a user uuid' do
|
63
|
+
expect do
|
64
|
+
described_class.for_user(user_uuid: '', account_uuid: 'acct-uuid')
|
65
|
+
end.to raise_error(ArgumentError)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'blows up without an account uuid' do
|
69
|
+
expect do
|
70
|
+
described_class.for_user(user_uuid: 'user-uuid', account_uuid: '')
|
71
|
+
end.to raise_error(ArgumentError)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'creates an instance for the given uuids' do
|
75
|
+
id = described_class.for_user(user_uuid: 'user-uuid', account_uuid: 'acct-uuid')
|
76
|
+
expect(id.user_uuid).to eq('user-uuid')
|
77
|
+
expect(id.masquerading_user_uuid).to be_nil
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'accepts other details' do
|
81
|
+
id = described_class.for_user(
|
82
|
+
user_uuid: 'user-uuid',
|
83
|
+
account_uuid: 'acct-uuid',
|
84
|
+
canvas_domain: 'z.instructure.com',
|
85
|
+
real_user_uuid: 'masq-id',
|
86
|
+
real_user_shard_id: 5
|
87
|
+
)
|
88
|
+
expect(id.canvas_domain).to eq('z.instructure.com')
|
89
|
+
expect(id.masquerading_user_uuid).to eq('masq-id')
|
90
|
+
expect(id.masquerading_user_shard_id).to eq(5)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'without being configured' do
|
95
|
+
it '#to_token_string blows up' do
|
96
|
+
id = described_class.for_user(user_uuid: 'user-uuid', account_uuid: 'acct-uuid')
|
97
|
+
expect do
|
98
|
+
id.to_token_string
|
99
|
+
end.to raise_error(InstAccess::ConfigError)
|
100
|
+
end
|
101
|
+
|
102
|
+
it '.from_token_string blows up' do
|
103
|
+
expect do
|
104
|
+
described_class.from_token_string(unencrypted_token)
|
105
|
+
end.to raise_error(InstAccess::ConfigError)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'when configured only for signature verification' do
|
110
|
+
around do |example|
|
111
|
+
InstAccess.with_config(signing_key: signing_pub_key) do
|
112
|
+
example.run
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it '#to_token_string blows up' do
|
117
|
+
id = described_class.for_user(user_uuid: 'user-uuid', account_uuid: 'acct-uuid')
|
118
|
+
expect do
|
119
|
+
id.to_token_string
|
120
|
+
end.to raise_error(InstAccess::ConfigError)
|
121
|
+
end
|
122
|
+
|
123
|
+
it '.from_token_string decodes the given token' do
|
124
|
+
id = described_class.from_token_string(unencrypted_token)
|
125
|
+
expect(id.user_uuid).to eq('user-uuid')
|
126
|
+
end
|
127
|
+
|
128
|
+
it '.from_token_string blows up if the token is expired' do
|
129
|
+
token = unencrypted_token # instantiate it to set the expiration
|
130
|
+
Timecop.travel(3601) do
|
131
|
+
expect do
|
132
|
+
described_class.from_token_string(token)
|
133
|
+
end.to raise_error(InstAccess::TokenExpired)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it '.from_token_string blows up if the token has a bad signature' do
|
138
|
+
# reconfigure with the wrong signing key so the signature doesn't match
|
139
|
+
InstAccess.with_config(signing_key: encryption_pub_key) do
|
140
|
+
expect do
|
141
|
+
described_class.from_token_string(unencrypted_token)
|
142
|
+
end.to raise_error(InstAccess::InvalidToken)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'when configured for token generation' do
|
148
|
+
around do |example|
|
149
|
+
InstAccess.with_config(
|
150
|
+
signing_key: signing_priv_key, encryption_key: encryption_pub_key
|
151
|
+
) do
|
152
|
+
example.run
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
it '#to_token_string signs and encrypts the payload, returning a JWE' do
|
157
|
+
id_token = a_token.to_token_string
|
158
|
+
# JWEs have 5 base64-encoded sections, each separated by a dot
|
159
|
+
expect(id_token).to match(/[\w-]+\.[\w-]+\.[\w-]+\.[\w-]+\.[\w-]+/)
|
160
|
+
# normally another service would need to decrypt this, but we'll do it
|
161
|
+
# here ourselves to ensure it's been encrypted properly
|
162
|
+
jws = JSON::JWT.decode(id_token, encryption_keypair)
|
163
|
+
jwt = JSON::JWT.decode(jws.plain_text, signing_keypair)
|
164
|
+
expect(jwt[:sub]).to eq('user-uuid')
|
165
|
+
end
|
166
|
+
|
167
|
+
it '.from_token_string still decodes the given token' do
|
168
|
+
id = described_class.from_token_string(unencrypted_token)
|
169
|
+
expect(id.user_uuid).to eq('user-uuid')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (C) 2021 - present Instructure, Inc.
|
5
|
+
#
|
6
|
+
# This file is part of Canvas.
|
7
|
+
#
|
8
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
10
|
+
# Software Foundation, version 3 of the License.
|
11
|
+
#
|
12
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
13
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
14
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License along
|
18
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
require 'initialize_coverage'
|
21
|
+
require 'byebug'
|
22
|
+
require 'inst_access'
|
23
|
+
|
24
|
+
RSpec.configure do |config|
|
25
|
+
config.order = 'random'
|
26
|
+
end
|
data/test.sh
ADDED
metadata
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: inst_access
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Ziwisky
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-11-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json-jwt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.13.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.13.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: byebug
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.8.1
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.8.1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov-json
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: timecop
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description:
|
140
|
+
email:
|
141
|
+
- mziwisky@instructure.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- lib/inst_access.rb
|
147
|
+
- lib/inst_access/config.rb
|
148
|
+
- lib/inst_access/errors.rb
|
149
|
+
- lib/inst_access/token.rb
|
150
|
+
- spec/initialize_coverage.rb
|
151
|
+
- spec/inst_access/inst_access_spec.rb
|
152
|
+
- spec/inst_access/token_spec.rb
|
153
|
+
- spec/spec_helper.rb
|
154
|
+
- test.sh
|
155
|
+
homepage: http://github.com/instructure/inst_access
|
156
|
+
licenses: []
|
157
|
+
metadata: {}
|
158
|
+
post_install_message:
|
159
|
+
rdoc_options: []
|
160
|
+
require_paths:
|
161
|
+
- lib
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '2.5'
|
167
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
requirements: []
|
173
|
+
rubygems_version: 3.0.3
|
174
|
+
signing_key:
|
175
|
+
specification_version: 4
|
176
|
+
summary: Generation, parsing, and validation of Instructure access tokens
|
177
|
+
test_files:
|
178
|
+
- spec/initialize_coverage.rb
|
179
|
+
- spec/inst_access/inst_access_spec.rb
|
180
|
+
- spec/inst_access/token_spec.rb
|
181
|
+
- spec/spec_helper.rb
|