inst_access 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+ set -e
3
+ docker-compose run --rm inst_access bundle insall
4
+ docker-compose run --rm inst_access bundle exec rspec
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