fridge 0.2.4 → 0.4.2
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 +5 -5
- data/.github/CODEOWNERS +1 -0
- data/.travis.yml +4 -2
- data/Gemfile +3 -0
- data/README.md +1 -1
- data/fridge.gemspec +7 -6
- data/lib/fridge.rb +4 -1
- data/lib/fridge/access_token.rb +82 -32
- data/lib/fridge/expired_token.rb +4 -0
- data/lib/fridge/rails_helpers.rb +18 -7
- data/lib/fridge/version.rb +1 -1
- data/spec/fixtures/app.rb +2 -5
- data/spec/fridge/access_token_spec.rb +62 -4
- data/spec/fridge/rails_helpers_spec.rb +161 -147
- data/spec/spec_helper.rb +11 -3
- metadata +47 -48
- data/spec/fixtures/controller.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 769896062879cb0dc6c7c69f095a5cedab11beaa0c30cb3e302dfc15eb842343
|
4
|
+
data.tar.gz: 657ca035209d5be3fcb78aaa74cc418bf764726622ccc3d90e0cab900ad4cf13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1500d9599ef57f700c52c2362086a3c9e0ac1853c2182e72e1f24ffe0583ccdc38bcaca4d7c9e53b792d93b164ae7ac1510af721e7b1359e2e07998da44388b2
|
7
|
+
data.tar.gz: e18539fb6ae2d73dac73005348bc78015dc2a708e6d4e3f10b7b430ed8368f16f73d58d876ca6afdfb3dcf41d41b165d4291eac3ef6cb11c03c61ff87574fc72
|
data/.github/CODEOWNERS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* @dawenster
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
data/fridge.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
|
@@ -16,17 +17,17 @@ Gem::Specification.new do |spec|
|
|
16
17
|
spec.license = 'MIT'
|
17
18
|
|
18
19
|
spec.files = `git ls-files`.split($RS)
|
19
|
-
spec.test_files = spec.files.grep(
|
20
|
+
spec.test_files = spec.files.grep(%r{^spec/})
|
20
21
|
spec.require_paths = ['lib']
|
21
22
|
|
22
23
|
spec.add_dependency 'gem_config'
|
23
|
-
spec.add_dependency 'jwt', '~>
|
24
|
+
spec.add_dependency 'jwt', '~> 1.5.6'
|
24
25
|
|
25
|
-
spec.add_development_dependency 'bundler', '~> 1.5'
|
26
26
|
spec.add_development_dependency 'aptible-tasks'
|
27
|
-
spec.add_development_dependency '
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
28
|
+
spec.add_development_dependency 'pry'
|
28
29
|
spec.add_development_dependency 'rails'
|
29
|
-
spec.add_development_dependency '
|
30
|
+
spec.add_development_dependency 'rake'
|
31
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
30
32
|
spec.add_development_dependency 'rspec-rails'
|
31
|
-
spec.add_development_dependency 'pry'
|
32
33
|
end
|
data/lib/fridge.rb
CHANGED
@@ -4,6 +4,7 @@ require 'fridge/version'
|
|
4
4
|
require 'fridge/access_token'
|
5
5
|
require 'fridge/serialization_error'
|
6
6
|
require 'fridge/invalid_token'
|
7
|
+
require 'fridge/expired_token'
|
7
8
|
|
8
9
|
require 'fridge/railtie' if defined?(Rails)
|
9
10
|
|
@@ -14,7 +15,9 @@ module Fridge
|
|
14
15
|
has :private_key, classes: [String]
|
15
16
|
has :public_key, classes: [String]
|
16
17
|
|
17
|
-
|
18
|
+
# rubocop:disable Style/PercentLiteralDelimiters
|
19
|
+
has :signing_algorithm, values: %w[RS512 RS256], default: 'RS512'
|
20
|
+
# rubocop:enable Style/PercentLiteralDelimiters
|
18
21
|
|
19
22
|
# A validator must raise an exception or return a false value for an
|
20
23
|
# invalid token
|
data/lib/fridge/access_token.rb
CHANGED
@@ -2,10 +2,9 @@ require 'jwt'
|
|
2
2
|
|
3
3
|
module Fridge
|
4
4
|
class AccessToken
|
5
|
-
attr_accessor :id, :issuer, :subject, :scope, :expires_at,
|
5
|
+
attr_accessor :id, :issuer, :subject, :scope, :expires_at, :actor,
|
6
6
|
:jwt, :attributes
|
7
7
|
|
8
|
-
# rubocop:disable MethodLength
|
9
8
|
def initialize(jwt_or_options = nil)
|
10
9
|
options = case jwt_or_options
|
11
10
|
when String
|
@@ -15,13 +14,12 @@ module Fridge
|
|
15
14
|
when Hash then jwt_or_options
|
16
15
|
else {}
|
17
16
|
end
|
18
|
-
|
17
|
+
|
18
|
+
[:id, :issuer, :subject, :scope, :expires_at, :actor].each do |key|
|
19
19
|
send "#{key}=", options.delete(key)
|
20
20
|
end
|
21
|
-
self.attributes = options
|
22
|
-
self.attributes = Hash[attributes.map { |k, v| [k.to_sym, v] }]
|
21
|
+
self.attributes = options
|
23
22
|
end
|
24
|
-
# rubocop:enable MethodLength
|
25
23
|
|
26
24
|
def to_s
|
27
25
|
serialize
|
@@ -29,38 +27,36 @@ module Fridge
|
|
29
27
|
|
30
28
|
def serialize
|
31
29
|
return jwt if jwt
|
30
|
+
|
32
31
|
validate_parameters!
|
33
32
|
validate_private_key!
|
34
33
|
encode_and_sign
|
35
34
|
end
|
36
35
|
|
37
36
|
def encode_and_sign
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
rescue
|
37
|
+
h = {}
|
38
|
+
[:id, :issuer, :subject, :scope, :expires_at, :actor].each do |key|
|
39
|
+
h[key] = send(key)
|
40
|
+
end
|
41
|
+
h.merge!(attributes)
|
42
|
+
h = encode_for_jwt(h)
|
43
|
+
JWT.encode(h, private_key, algorithm)
|
44
|
+
rescue StandardError
|
46
45
|
raise SerializationError, 'Invalid private key or signing algorithm'
|
47
46
|
end
|
48
47
|
|
49
|
-
# rubocop:disable MethodLength
|
50
48
|
def decode_and_verify(jwt)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
raise InvalidToken, 'Invalid access token'
|
49
|
+
payload, _header = JWT.decode(jwt, public_key, true, algorithm: algorithm)
|
50
|
+
decode_from_jwt(payload)
|
51
|
+
rescue JWT::ExpiredSignature => e
|
52
|
+
raise ExpiredToken, e.message
|
53
|
+
rescue JWT::DecodeError => e
|
54
|
+
raise InvalidToken, e.message
|
55
|
+
end
|
56
|
+
|
57
|
+
def downgrade
|
58
|
+
self.scope = 'read'
|
62
59
|
end
|
63
|
-
# rubocop:enable MethodLength
|
64
60
|
|
65
61
|
def valid?
|
66
62
|
!expired?
|
@@ -72,8 +68,9 @@ module Fridge
|
|
72
68
|
|
73
69
|
def private_key
|
74
70
|
return unless config.private_key
|
71
|
+
|
75
72
|
@private_key ||= OpenSSL::PKey::RSA.new(config.private_key)
|
76
|
-
rescue
|
73
|
+
rescue StandardError
|
77
74
|
nil
|
78
75
|
end
|
79
76
|
|
@@ -83,7 +80,7 @@ module Fridge
|
|
83
80
|
elsif config.public_key
|
84
81
|
@public_key ||= OpenSSL::PKey::RSA.new(config.public_key)
|
85
82
|
end
|
86
|
-
rescue
|
83
|
+
rescue StandardError
|
87
84
|
nil
|
88
85
|
end
|
89
86
|
|
@@ -105,19 +102,72 @@ module Fridge
|
|
105
102
|
end
|
106
103
|
end
|
107
104
|
|
105
|
+
def respond_to_missing?(method, include_private = false)
|
106
|
+
attributes.key?(method) || super
|
107
|
+
end
|
108
|
+
|
108
109
|
def validate_parameters!
|
109
110
|
[:subject, :expires_at].each do |attribute|
|
110
111
|
next if send(attribute)
|
111
|
-
|
112
|
+
|
113
|
+
raise SerializationError, "Missing attribute: #{attribute}"
|
112
114
|
end
|
113
115
|
end
|
114
116
|
|
115
117
|
def validate_private_key!
|
116
|
-
|
118
|
+
raise SerializationError, 'No private key configured' unless private_key
|
117
119
|
end
|
118
120
|
|
119
121
|
def validate_public_key!
|
120
|
-
|
122
|
+
raise SerializationError, 'No public key configured' unless public_key
|
123
|
+
end
|
124
|
+
|
125
|
+
# Internally, we use "subject" to refer to "sub", and so on. We also
|
126
|
+
# represent some objects (expiry) differently. These functions do the
|
127
|
+
# mapping from Fridge to JWT and vice-versa.
|
128
|
+
|
129
|
+
def encode_for_jwt(hash)
|
130
|
+
hash = hash.dup
|
131
|
+
|
132
|
+
out = {
|
133
|
+
id: hash.delete(:id),
|
134
|
+
iss: hash.delete(:issuer),
|
135
|
+
sub: hash.delete(:subject),
|
136
|
+
scope: hash.delete(:scope)
|
137
|
+
}.delete_if { |_, v| v.nil? }
|
138
|
+
|
139
|
+
# Unfortunately, nil.to_i returns 0, which means we can't
|
140
|
+
# easily clean out exp if we include it although it wasn't passed
|
141
|
+
# in like we do for other keys. So, we only include it if it's
|
142
|
+
# actually passed in and non-nil. Either way, we delete the keys.
|
143
|
+
hash.delete(:expires_at).tap { |e| out[:exp] = e.to_i if e }
|
144
|
+
hash.delete(:actor).tap { |a| out[:act] = encode_for_jwt(a) if a }
|
145
|
+
|
146
|
+
# Extra attributes passed through as-is
|
147
|
+
out.merge!(hash)
|
148
|
+
|
149
|
+
out
|
150
|
+
end
|
151
|
+
|
152
|
+
def decode_from_jwt(hash)
|
153
|
+
hash = hash.dup
|
154
|
+
|
155
|
+
out = {
|
156
|
+
id: hash.delete('id'),
|
157
|
+
issuer: hash.delete('iss'),
|
158
|
+
subject: hash.delete('sub'),
|
159
|
+
scope: hash.delete('scope')
|
160
|
+
}.delete_if { |_, v| v.nil? }
|
161
|
+
|
162
|
+
hash.delete('exp').tap { |e| out[:expires_at] = Time.at(e) if e }
|
163
|
+
hash.delete('act').tap { |a| out[:actor] = decode_from_jwt(a) if a }
|
164
|
+
|
165
|
+
# Extra attributes
|
166
|
+
hash.delete_if { |_, v| v.nil? }
|
167
|
+
hash = Hash[hash.map { |k, v| [k.to_sym, v] }]
|
168
|
+
out.merge!(hash)
|
169
|
+
|
170
|
+
out
|
121
171
|
end
|
122
172
|
end
|
123
173
|
end
|
data/lib/fridge/rails_helpers.rb
CHANGED
@@ -15,8 +15,13 @@ module Fridge
|
|
15
15
|
current_token.subject if current_token
|
16
16
|
end
|
17
17
|
|
18
|
+
def token_actor
|
19
|
+
current_token.actor if current_token
|
20
|
+
end
|
21
|
+
|
18
22
|
def current_token
|
19
23
|
return unless bearer_token
|
24
|
+
|
20
25
|
@current_token ||= AccessToken.new(bearer_token).tap do |token|
|
21
26
|
validate_token!(token)
|
22
27
|
end
|
@@ -31,12 +36,17 @@ module Fridge
|
|
31
36
|
session_token.subject if session_token
|
32
37
|
end
|
33
38
|
|
39
|
+
def session_actor
|
40
|
+
session_token.actor if session_token
|
41
|
+
end
|
42
|
+
|
34
43
|
def session_token
|
35
44
|
return unless session_cookie
|
45
|
+
|
36
46
|
@session_token ||= AccessToken.new(session_cookie).tap do |token|
|
37
|
-
validate_token!(token)
|
47
|
+
validate_token!(token).downgrade
|
38
48
|
end
|
39
|
-
rescue
|
49
|
+
rescue StandardError
|
40
50
|
clear_session_cookie
|
41
51
|
end
|
42
52
|
|
@@ -44,7 +54,7 @@ module Fridge
|
|
44
54
|
def validate_token(access_token)
|
45
55
|
validator = Fridge.configuration.validator
|
46
56
|
validator.call(access_token) && access_token
|
47
|
-
rescue
|
57
|
+
rescue StandardError
|
48
58
|
false
|
49
59
|
end
|
50
60
|
|
@@ -54,7 +64,7 @@ module Fridge
|
|
54
64
|
if validator.call(access_token)
|
55
65
|
access_token
|
56
66
|
else
|
57
|
-
|
67
|
+
raise InvalidToken, 'Rejected by validator'
|
58
68
|
end
|
59
69
|
end
|
60
70
|
|
@@ -83,7 +93,7 @@ module Fridge
|
|
83
93
|
end
|
84
94
|
|
85
95
|
def write_shared_cookie(name, value, options = {})
|
86
|
-
|
96
|
+
raise 'Can only write string cookie values' unless value.is_a?(String)
|
87
97
|
|
88
98
|
cookies[name] = {
|
89
99
|
value: value,
|
@@ -95,9 +105,10 @@ module Fridge
|
|
95
105
|
cookies[name]
|
96
106
|
end
|
97
107
|
|
98
|
-
def fetch_shared_cookie(name
|
108
|
+
def fetch_shared_cookie(name)
|
99
109
|
return read_shared_cookie(name) if read_shared_cookie(name)
|
100
|
-
|
110
|
+
|
111
|
+
write_shared_cookie(yield)
|
101
112
|
end
|
102
113
|
|
103
114
|
def delete_shared_cookie(name)
|
data/lib/fridge/version.rb
CHANGED
data/spec/fixtures/app.rb
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
require 'active_support/all'
|
2
|
-
require 'action_controller'
|
3
|
-
require 'action_dispatch'
|
4
|
-
|
5
1
|
module Rails
|
6
2
|
class App
|
7
3
|
def env_config
|
@@ -10,6 +6,7 @@ module Rails
|
|
10
6
|
|
11
7
|
def routes
|
12
8
|
return @routes if defined?(@routes)
|
9
|
+
|
13
10
|
@routes = ActionDispatch::Routing::RouteSet.new
|
14
11
|
@routes.draw do
|
15
12
|
resources :posts
|
@@ -19,6 +16,6 @@ module Rails
|
|
19
16
|
end
|
20
17
|
|
21
18
|
def self.application
|
22
|
-
@
|
19
|
+
@application ||= App.new
|
23
20
|
end
|
24
21
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'json'
|
2
3
|
|
3
4
|
describe Fridge::AccessToken do
|
4
5
|
describe '#initialize' do
|
@@ -13,19 +14,39 @@ describe Fridge::AccessToken do
|
|
13
14
|
end
|
14
15
|
|
15
16
|
it 'should accept a JWT' do
|
16
|
-
jwt = JWT.encode(
|
17
|
+
jwt = JWT.encode(
|
18
|
+
{ id: 'foobar', exp: Time.now.to_i + 10 },
|
19
|
+
private_key, 'RS512'
|
20
|
+
)
|
17
21
|
access_token = described_class.new(jwt)
|
18
22
|
expect(access_token.id).to eq 'foobar'
|
19
23
|
end
|
20
24
|
|
21
25
|
it 'should raise an error on an invalid JWT' do
|
22
|
-
expect { described_class.new('foobar') }
|
26
|
+
expect { described_class.new('foobar') }
|
27
|
+
.to raise_error Fridge::InvalidToken
|
23
28
|
end
|
24
29
|
|
25
30
|
it 'should raise an error on an incorrectly signed JWT' do
|
26
31
|
jwt = JWT.encode({ id: 'foobar' }, OpenSSL::PKey::RSA.new(1024), 'RS512')
|
27
32
|
expect { described_class.new(jwt) }.to raise_error Fridge::InvalidToken
|
28
33
|
end
|
34
|
+
|
35
|
+
it 'should raise an error on an expired JWT' do
|
36
|
+
jwt = JWT.encode(
|
37
|
+
{ id: 'foobar', exp: Time.now.to_i - 10 },
|
38
|
+
private_key, 'RS512'
|
39
|
+
)
|
40
|
+
expect { described_class.new(jwt) }.to raise_error(Fridge::ExpiredToken)
|
41
|
+
end
|
42
|
+
|
43
|
+
# http://bit.ly/jwt-none-vulnerability
|
44
|
+
it 'should raise an error with { "alg": "none" }' do
|
45
|
+
jwt = "#{Base64.encode64({ typ: 'JWT', alg: 'none' }.to_json).chomp}." \
|
46
|
+
"#{Base64.encode64({ id: 'foobar' }.to_json).chomp}"
|
47
|
+
expect(JWT.decode(jwt, nil, false)[0]).to eq('id' => 'foobar')
|
48
|
+
expect { described_class.new(jwt) }.to raise_error Fridge::InvalidToken
|
49
|
+
end
|
29
50
|
end
|
30
51
|
|
31
52
|
describe '#serialize' do
|
@@ -72,8 +93,8 @@ describe Fridge::AccessToken do
|
|
72
93
|
end
|
73
94
|
|
74
95
|
it 'should represent :exp in seconds since the epoch' do
|
75
|
-
hash = JWT.decode(subject.serialize, public_key)
|
76
|
-
expect(hash['exp']).to be_a
|
96
|
+
hash, = JWT.decode(subject.serialize, public_key)
|
97
|
+
expect(hash['exp']).to be_a Integer
|
77
98
|
end
|
78
99
|
|
79
100
|
it 'should be deterministic' do
|
@@ -93,12 +114,43 @@ describe Fridge::AccessToken do
|
|
93
114
|
|
94
115
|
expect(copy.attributes[:foo]).to eq 'bar'
|
95
116
|
expect(copy.foo).to eq 'bar'
|
117
|
+
expect(copy.respond_to?(:foo)).to be_truthy
|
118
|
+
expect(copy.respond_to?(:bar)).to be_falsey
|
96
119
|
end
|
97
120
|
|
98
121
|
it 'should raise an error if required attributes are missing' do
|
99
122
|
subject.subject = nil
|
100
123
|
expect { subject.serialize }.to raise_error Fridge::SerializationError
|
101
124
|
end
|
125
|
+
|
126
|
+
it 'should encode and decode :actor as :act' do
|
127
|
+
# The `act` field can recursively encode additional
|
128
|
+
# claims, so we check those too.
|
129
|
+
actor = { subject: 'foo', username: 'test', actor: { subject: 'bar' } }
|
130
|
+
subject = described_class.new(options.merge(actor: actor))
|
131
|
+
|
132
|
+
# The JWT lib will return everything as strings, so we'll
|
133
|
+
# test that, although eventually we'll want to see symbols back.
|
134
|
+
actor_s = { 'sub' => 'foo', 'username' => 'test',
|
135
|
+
'act' => { 'sub' => 'bar' } }
|
136
|
+
hash, = JWT.decode(subject.serialize, public_key)
|
137
|
+
expect(hash['act']).to eq(actor_s)
|
138
|
+
|
139
|
+
# Now, check that we properly get symbols back
|
140
|
+
new = described_class.new(subject.serialize)
|
141
|
+
expect(new.actor).to eq(actor)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should be idempotent' do
|
145
|
+
subject = described_class.new(options)
|
146
|
+
expect(subject.serialize).to eq(subject.serialize)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'should be idempotent with an actor' do
|
150
|
+
actor = { subject: 'foo', username: 'test', actor: { subject: 'bar' } }
|
151
|
+
subject = described_class.new(options.merge(actor: actor))
|
152
|
+
expect(subject.serialize).to eq(subject.serialize)
|
153
|
+
end
|
102
154
|
end
|
103
155
|
|
104
156
|
describe '#expired?' do
|
@@ -117,4 +169,10 @@ describe Fridge::AccessToken do
|
|
117
169
|
expect(subject).not_to be_expired
|
118
170
|
end
|
119
171
|
end
|
172
|
+
|
173
|
+
describe '#downgrade' do
|
174
|
+
it 'sets the token scope to :read' do
|
175
|
+
expect { subject.downgrade }.to change(subject, :scope).to('read')
|
176
|
+
end
|
177
|
+
end
|
120
178
|
end
|
@@ -1,204 +1,218 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'fixtures/app'
|
3
|
-
require 'fixtures/controller'
|
4
|
-
require 'rspec/rails'
|
5
3
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
describe Fridge::RailsHelpers do
|
5
|
+
include RSpec::Rails::ControllerExampleGroup
|
6
|
+
|
7
|
+
controller(ActionController::Base) { include Fridge::RailsHelpers }
|
8
|
+
|
9
|
+
let(:organization_url) do
|
10
|
+
"https://auth.aptible.com/users/#{SecureRandom.uuid}"
|
11
|
+
end
|
12
|
+
let(:private_key) { OpenSSL::PKey::RSA.new(1024) }
|
13
|
+
let(:public_key) { OpenSSL::PKey::RSA.new(private_key.public_key) }
|
14
|
+
|
15
|
+
let(:options) do
|
16
|
+
{
|
17
|
+
subject: "https://auth.aptible.com/users/#{SecureRandom.uuid}",
|
18
|
+
expires_at: Time.now + 3600
|
19
|
+
}
|
20
|
+
end
|
21
|
+
let(:access_token) { Fridge::AccessToken.new(options) }
|
22
|
+
|
23
|
+
let(:cookies) { controller.send(:cookies) }
|
24
|
+
|
25
|
+
before { Fridge.configuration.private_key = private_key.to_s }
|
26
|
+
before { Fridge.configuration.public_key = public_key.to_s }
|
27
|
+
|
28
|
+
describe '#bearer_token' do
|
29
|
+
it 'returns the bearer token from the Authorization: header' do
|
30
|
+
request.env['HTTP_AUTHORIZATION'] = 'Bearer foobar'
|
31
|
+
expect(controller.bearer_token).to eq 'foobar'
|
11
32
|
end
|
12
|
-
let(:private_key) { OpenSSL::PKey::RSA.new(1024) }
|
13
|
-
let(:public_key) { OpenSSL::PKey::RSA.new(private_key.public_key) }
|
14
33
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
expires_at: Time.now + 3600
|
19
|
-
}
|
34
|
+
it 'returns nil in the absence of an Authorization: header' do
|
35
|
+
request.env['HTTP_AUTHORIZATION'] = nil
|
36
|
+
expect(controller.bearer_token).to be_nil
|
20
37
|
end
|
21
|
-
|
38
|
+
end
|
22
39
|
|
23
|
-
|
40
|
+
describe '#token_subject' do
|
41
|
+
it 'returns the subject encoded in the token' do
|
42
|
+
controller.stub(:current_token) { access_token }
|
43
|
+
expect(controller.token_subject).to eq access_token.subject
|
44
|
+
end
|
24
45
|
|
25
|
-
|
26
|
-
|
46
|
+
it 'returns nil if no token is present' do
|
47
|
+
controller.stub(:current_token) { nil }
|
48
|
+
expect(controller.token_subject).to be_nil
|
49
|
+
end
|
50
|
+
end
|
27
51
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
52
|
+
describe '#token_scope' do
|
53
|
+
it 'returns the scope encoded in the token' do
|
54
|
+
controller.stub(:current_token) { access_token }
|
55
|
+
expect(controller.token_scope).to eq access_token.scope
|
56
|
+
end
|
33
57
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
58
|
+
it 'returns nil if no token is present' do
|
59
|
+
controller.stub(:current_token) { nil }
|
60
|
+
expect(controller.token_scope).to be_nil
|
38
61
|
end
|
62
|
+
end
|
39
63
|
|
40
|
-
|
41
|
-
|
42
|
-
controller.stub(:current_token) { access_token }
|
43
|
-
expect(controller.token_subject).to eq access_token.subject
|
44
|
-
end
|
64
|
+
describe '#current_token' do
|
65
|
+
before { controller.stub(:bearer_token) { access_token.serialize } }
|
45
66
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
67
|
+
it 'should raise an error if the token is not a valid JWT' do
|
68
|
+
controller.stub(:bearer_token) { 'foobar' }
|
69
|
+
expect { controller.current_token }.to raise_error Fridge::InvalidToken
|
50
70
|
end
|
51
71
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
expect(controller.token_scope).to eq access_token.scope
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'returns nil if no token is present' do
|
59
|
-
controller.stub(:current_token) { nil }
|
60
|
-
expect(controller.token_scope).to be_nil
|
61
|
-
end
|
72
|
+
it 'should raise an error if the token has expired' do
|
73
|
+
access_token.expires_at = Time.now - 3600
|
74
|
+
expect { controller.current_token }.to raise_error Fridge::InvalidToken
|
62
75
|
end
|
63
76
|
|
64
|
-
|
65
|
-
|
77
|
+
it 'should raise an error if custom validation fails' do
|
78
|
+
Fridge.configuration.validator = ->(_) { false }
|
79
|
+
expect { controller.current_token }.to raise_error Fridge::InvalidToken
|
80
|
+
end
|
66
81
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
82
|
+
it 'should not raise an error if a valid token is passed' do
|
83
|
+
expect { controller.current_token }.not_to raise_error
|
84
|
+
end
|
71
85
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
86
|
+
it 'should return the token if a valid token is passed' do
|
87
|
+
expect(controller.current_token.id).to eq access_token.id
|
88
|
+
end
|
89
|
+
end
|
76
90
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
91
|
+
describe '#session_subject' do
|
92
|
+
it 'returns the subject encoded in the session' do
|
93
|
+
controller.stub(:session_token) { access_token }
|
94
|
+
expect(controller.session_subject).to eq access_token.subject
|
95
|
+
end
|
81
96
|
|
82
|
-
|
83
|
-
|
84
|
-
|
97
|
+
it 'returns nil if no session is present' do
|
98
|
+
controller.stub(:session_token) { nil }
|
99
|
+
expect(controller.session_subject).to be_nil
|
100
|
+
end
|
101
|
+
end
|
85
102
|
|
86
|
-
|
87
|
-
|
88
|
-
|
103
|
+
describe '#session_token' do
|
104
|
+
it 'should delete all cookies on error' do
|
105
|
+
cookies[:fridge_session] = 'foobar'
|
106
|
+
controller.session_token
|
107
|
+
expect(cookies.deleted?(:fridge_session, domain: :all)).to be true
|
89
108
|
end
|
90
109
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
110
|
+
it 'should return nil on error' do
|
111
|
+
cookies[:fridge_session] = 'foobar'
|
112
|
+
expect(controller.session_token).to be_nil
|
113
|
+
end
|
96
114
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
115
|
+
it 'should return the token stored in :fridge_session' do
|
116
|
+
cookies[:fridge_session] = access_token.serialize
|
117
|
+
expect(controller.session_token.id).to eq access_token.id
|
101
118
|
end
|
102
119
|
|
103
|
-
|
104
|
-
|
105
|
-
cookies[:fridge_session] = 'foobar'
|
106
|
-
controller.session_token
|
107
|
-
expect(cookies.deleted?(:fridge_session, domain: :all)).to be_true
|
108
|
-
end
|
120
|
+
context 'with a non-:read scope' do
|
121
|
+
before { options.merge!(scope: 'manage') }
|
109
122
|
|
110
|
-
it 'should
|
111
|
-
cookies[:fridge_session] =
|
112
|
-
expect(controller.session_token).to
|
123
|
+
it 'should downgrade the token' do
|
124
|
+
cookies[:fridge_session] = access_token.serialize
|
125
|
+
expect(controller.session_token.scope).to eq 'read'
|
113
126
|
end
|
114
127
|
|
115
|
-
it 'should
|
128
|
+
it 'should not change the validity of a token' do
|
116
129
|
cookies[:fridge_session] = access_token.serialize
|
117
|
-
expect(controller.session_token
|
130
|
+
expect(controller.session_token).to be_valid
|
118
131
|
end
|
119
132
|
end
|
133
|
+
end
|
120
134
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
135
|
+
describe '#validate_token' do
|
136
|
+
it 'should return false if the token is invalid' do
|
137
|
+
Fridge.configuration.validator = ->(_) { false }
|
138
|
+
expect(controller.validate_token(access_token)).to be false
|
139
|
+
end
|
126
140
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
141
|
+
it 'should return false if the token validator fails' do
|
142
|
+
Fridge.configuration.validator = ->(_) { raise 'Foobar' }
|
143
|
+
expect(controller.validate_token(access_token)).to be false
|
144
|
+
end
|
131
145
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
end
|
146
|
+
it 'should return the token if valid' do
|
147
|
+
Fridge.configuration.validator = ->(_) { true }
|
148
|
+
expect(controller.validate_token(access_token)).to eq access_token
|
136
149
|
end
|
150
|
+
end
|
137
151
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
152
|
+
describe '#validate_token' do
|
153
|
+
it 'should raise an exception if the token is invalid' do
|
154
|
+
Fridge.configuration.validator = ->(_) { false }
|
155
|
+
expect { controller.validate_token!(access_token) }
|
156
|
+
.to raise_error Fridge::InvalidToken
|
157
|
+
end
|
143
158
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
end
|
159
|
+
it 'should return the token if valid' do
|
160
|
+
Fridge.configuration.validator = ->(_) { true }
|
161
|
+
expect(controller.validate_token!(access_token)).to eq access_token
|
148
162
|
end
|
163
|
+
end
|
149
164
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
end
|
165
|
+
describe '#sessionize_token' do
|
166
|
+
it 'should set a session cookie' do
|
167
|
+
Rails.stub_chain(:env, :development?) { false }
|
168
|
+
controller.sessionize_token(access_token)
|
169
|
+
expect(cookies[:fridge_session]).to eq access_token.serialize
|
156
170
|
end
|
171
|
+
end
|
157
172
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
end
|
173
|
+
describe '#fridge_cookie_name' do
|
174
|
+
it 'is configurable' do
|
175
|
+
Fridge.configuration.cookie_name = 'foobar'
|
176
|
+
expect(controller.fridge_cookie_name).to eq 'foobar'
|
163
177
|
end
|
178
|
+
end
|
164
179
|
|
165
|
-
|
166
|
-
|
180
|
+
describe '#write_shared_cookie' do
|
181
|
+
before { Rails.stub_chain(:env, :development?) { false } }
|
167
182
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
end
|
183
|
+
it 'should save cookie' do
|
184
|
+
controller.write_shared_cookie(:organization_url, organization_url)
|
185
|
+
expect(cookies[:organization_url]).to eq organization_url
|
172
186
|
end
|
187
|
+
end
|
173
188
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
end
|
189
|
+
describe '#read_shared_cookie' do
|
190
|
+
it 'should read cookie' do
|
191
|
+
cookies[:organization_url] = { value: organization_url }
|
192
|
+
expect(controller.read_shared_cookie(:organization_url)).to(
|
193
|
+
eq organization_url
|
194
|
+
)
|
181
195
|
end
|
196
|
+
end
|
182
197
|
|
183
|
-
|
184
|
-
|
198
|
+
describe '#delete_shared_cookie' do
|
199
|
+
before { Rails.stub_chain(:env, :development?) { false } }
|
185
200
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
end
|
201
|
+
it 'should delete cookie' do
|
202
|
+
controller.write_shared_cookie(:organization_url, organization_url)
|
203
|
+
controller.delete_shared_cookie(:organization_url)
|
204
|
+
expect(cookies[:organization_url]).to be_nil
|
191
205
|
end
|
206
|
+
end
|
192
207
|
|
193
|
-
|
194
|
-
|
208
|
+
describe '#fridge_cookie_options' do
|
209
|
+
before { Rails.stub_chain(:env, :development?) { false } }
|
195
210
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
end
|
211
|
+
it 'are configurable' do
|
212
|
+
Fridge.configuration.cookie_options = { foobar: true }
|
213
|
+
options = controller.fridge_cookie_options
|
214
|
+
expect(options[:domain]).to eq :all
|
215
|
+
expect(options[:foobar]).to eq true
|
202
216
|
end
|
203
217
|
end
|
204
218
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,14 +1,22 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
3
|
|
4
|
+
require 'active_support/all'
|
5
|
+
require 'action_controller'
|
6
|
+
require 'action_dispatch'
|
7
|
+
require 'action_view'
|
8
|
+
|
9
|
+
require 'fridge'
|
10
|
+
require 'fridge/rails_helpers'
|
11
|
+
|
12
|
+
require 'rspec'
|
13
|
+
require 'rspec/rails'
|
14
|
+
|
4
15
|
# Load shared spec files
|
5
16
|
Dir["#{File.dirname(__FILE__)}/shared/**/*.rb"].each do |file|
|
6
17
|
require file
|
7
18
|
end
|
8
19
|
|
9
|
-
# Require library up front
|
10
|
-
require 'fridge'
|
11
|
-
|
12
20
|
RSpec.configure do |config|
|
13
21
|
config.before { Fridge.configuration.reset }
|
14
22
|
end
|
metadata
CHANGED
@@ -1,139 +1,139 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fridge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Frank Macreery
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gem_config
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: jwt
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 1.5.6
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 1.5.6
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: aptible-tasks
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: bundler
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '1.5'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '1.5'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: pry
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rails
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: rake
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- -
|
101
|
+
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
103
|
+
version: '0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- -
|
108
|
+
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
110
|
+
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name: rspec
|
112
|
+
name: rspec
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- -
|
115
|
+
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
117
|
+
version: '3.0'
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- -
|
122
|
+
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
124
|
+
version: '3.0'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
126
|
+
name: rspec-rails
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- -
|
129
|
+
- - ">="
|
130
130
|
- !ruby/object:Gem::Version
|
131
131
|
version: '0'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- -
|
136
|
+
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
139
|
description: Token validation for distributed resource servers
|
@@ -143,9 +143,10 @@ executables: []
|
|
143
143
|
extensions: []
|
144
144
|
extra_rdoc_files: []
|
145
145
|
files:
|
146
|
-
- .
|
147
|
-
- .
|
148
|
-
- .
|
146
|
+
- ".github/CODEOWNERS"
|
147
|
+
- ".gitignore"
|
148
|
+
- ".rspec"
|
149
|
+
- ".travis.yml"
|
149
150
|
- Gemfile
|
150
151
|
- LICENSE.md
|
151
152
|
- README.md
|
@@ -153,13 +154,13 @@ files:
|
|
153
154
|
- fridge.gemspec
|
154
155
|
- lib/fridge.rb
|
155
156
|
- lib/fridge/access_token.rb
|
157
|
+
- lib/fridge/expired_token.rb
|
156
158
|
- lib/fridge/invalid_token.rb
|
157
159
|
- lib/fridge/rails_helpers.rb
|
158
160
|
- lib/fridge/railtie.rb
|
159
161
|
- lib/fridge/serialization_error.rb
|
160
162
|
- lib/fridge/version.rb
|
161
163
|
- spec/fixtures/app.rb
|
162
|
-
- spec/fixtures/controller.rb
|
163
164
|
- spec/fridge/access_token_spec.rb
|
164
165
|
- spec/fridge/rails_helpers_spec.rb
|
165
166
|
- spec/spec_helper.rb
|
@@ -167,29 +168,27 @@ homepage: https://github.com/aptible/fridge
|
|
167
168
|
licenses:
|
168
169
|
- MIT
|
169
170
|
metadata: {}
|
170
|
-
post_install_message:
|
171
|
+
post_install_message:
|
171
172
|
rdoc_options: []
|
172
173
|
require_paths:
|
173
174
|
- lib
|
174
175
|
required_ruby_version: !ruby/object:Gem::Requirement
|
175
176
|
requirements:
|
176
|
-
- -
|
177
|
+
- - ">="
|
177
178
|
- !ruby/object:Gem::Version
|
178
179
|
version: '0'
|
179
180
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
181
|
requirements:
|
181
|
-
- -
|
182
|
+
- - ">="
|
182
183
|
- !ruby/object:Gem::Version
|
183
184
|
version: '0'
|
184
185
|
requirements: []
|
185
|
-
|
186
|
-
|
187
|
-
signing_key:
|
186
|
+
rubygems_version: 3.0.3
|
187
|
+
signing_key:
|
188
188
|
specification_version: 4
|
189
189
|
summary: Token validation for distributed resource servers
|
190
190
|
test_files:
|
191
191
|
- spec/fixtures/app.rb
|
192
|
-
- spec/fixtures/controller.rb
|
193
192
|
- spec/fridge/access_token_spec.rb
|
194
193
|
- spec/fridge/rails_helpers_spec.rb
|
195
194
|
- spec/spec_helper.rb
|