fridge 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.md +22 -0
- data/README.md +83 -0
- data/Rakefile +4 -0
- data/fridge.gemspec +32 -0
- data/lib/fridge/access_token.rb +102 -0
- data/lib/fridge/invalid_token.rb +4 -0
- data/lib/fridge/rails_helpers.rb +63 -0
- data/lib/fridge/railtie.rb +9 -0
- data/lib/fridge/serialization_error.rb +4 -0
- data/lib/fridge/version.rb +3 -0
- data/lib/fridge.rb +24 -0
- data/spec/fixtures/app.rb +24 -0
- data/spec/fixtures/controller.rb +5 -0
- data/spec/fridge/access_token_spec.rb +111 -0
- data/spec/fridge/rails_helpers_spec.rb +126 -0
- data/spec/spec_helper.rb +14 -0
- metadata +195 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 14721d992f4d97d606aa10ea94c061553328292f
|
4
|
+
data.tar.gz: 377c3b2543822ebdeea357a4e9ead8bd45ca4938
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 556a8a8805c2402d1accabe75bcc860a23659f528eaca8cf84868c12524ff0893312e6ef3b02e418705c4bdfe7601a3ab75d68ead2df8fa74b00d07624016cd8
|
7
|
+
data.tar.gz: 33936c9085ebf03959a4a7a6d6ad7d8d53377e3899e3008e56191ef401c1cf3da9a0edbe2799cfdd3e417ae30cc64d5b222a03167348c94251004851728ee532
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Aptible, Inc.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# ![](https://raw.github.com/aptible/straptible/master/lib/straptible/rails/templates/public.api/icon-60px.png) Fridge
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/fridge.png)](https://rubygems.org/gems/fridge)
|
4
|
+
[![Build Status](https://travis-ci.org/aptible/fridge.png?branch=master)](https://travis-ci.org/aptible/fridge)
|
5
|
+
[![Dependency Status](https://gemnasium.com/aptible/fridge.png)](https://gemnasium.com/aptible/fridge)
|
6
|
+
|
7
|
+
Token validation for distributed resource servers.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add the following line to your application's Gemfile.
|
12
|
+
|
13
|
+
gem 'fridge'
|
14
|
+
|
15
|
+
And then run `bundle install`.
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
### Configuration
|
20
|
+
|
21
|
+
| Parameter | Description | Possible Values |
|
22
|
+
| --------- | ----------- | --------------- |
|
23
|
+
| `private_key` | Private token signing key | A PEM-formatted key |
|
24
|
+
| `public_key` | Public token verification key (the private key's complement) | A PEM-formatted key |
|
25
|
+
| `signing_algorithm` | Algorithm to use for sigining and verification | `RS512`, `RS256` |
|
26
|
+
| `validator` | A lambda used to perform custom validation of tokens | Any `Proc` |
|
27
|
+
|
28
|
+
Resource servers must configure a public key corresponding to an authorization server, in order to verify tokens issued by that server. Authorization servers must configure a private key.
|
29
|
+
|
30
|
+
By default, public key-verified tokens are considered valid if and only iff they have not expired (i.e., `expires_at > Time.now`). However, some applications may want to perform additional validations. (For example, an authorization server may allow online revocation of tokens before their natural expiration, and need to check the current ). This is possible by configuring a custom validator:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
Fridge.configure do |config|
|
34
|
+
config.validator = lambda do |access_token|
|
35
|
+
token = Token.find_by(id: access_token.id)
|
36
|
+
token && !token.revoked?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
The validator will be called with a single argument, the `Fridge::AccessToken` instance.
|
42
|
+
|
43
|
+
### Integrating with Fridge from a resource server
|
44
|
+
|
45
|
+
From any of your controllers, you may access the following methods:
|
46
|
+
|
47
|
+
* `current_token`: The `Fridge::AccessToken` passed via `Authorization` header.
|
48
|
+
* `token_subject`: The subject (`:sub`) of the current token.
|
49
|
+
* `token_scope`: The scope (`:scope`) of the current token.
|
50
|
+
* `session_token`: The `Fridge::AccessToken` stored in the user agent's cookies.
|
51
|
+
* `session_subject`: The subject (`:sub`) of the current session token.
|
52
|
+
|
53
|
+
|
54
|
+
### Integrating with Fridge from an authorization server
|
55
|
+
|
56
|
+
A Fridge access token may be constructed a la the following example:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
access_token = Fridge::AccessToken.new(
|
60
|
+
id: '0f1aa5ce-6e93-4812-b3fc-3b7f7b685991',
|
61
|
+
subject: 'https://auth.aptible.com/users/e600a449-b308-4162-ac28-8a2769ad3f05',
|
62
|
+
expires_at: 1.hour.from_now
|
63
|
+
)
|
64
|
+
```
|
65
|
+
|
66
|
+
The only required hash parameters are `:subject` and `:expires_at`. Additionally, you may specify `:id`, `:scope` and `issuer`. To set this token in a cookie that's readable across your entire domain, you may invoke the following command from any Rails controller:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
store_session_cookie(access_token)
|
70
|
+
```
|
71
|
+
|
72
|
+
## Contributing
|
73
|
+
|
74
|
+
1. Fork the project.
|
75
|
+
1. Commit your changes, with specs.
|
76
|
+
1. Ensure that your code passes specs (`rake spec`) and meets Aptible's Ruby style guide (`rake rubocop`).
|
77
|
+
1. Create a new pull request on GitHub.
|
78
|
+
|
79
|
+
## Copyright and License
|
80
|
+
|
81
|
+
MIT License, see [LICENSE](LICENSE.md) for details.
|
82
|
+
|
83
|
+
Copyright (c) 2014 [Aptible](https://www.aptible.com), Frank Macreery, and contributors.
|
data/Rakefile
ADDED
data/fridge.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require 'English'
|
6
|
+
require 'fridge/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = 'fridge'
|
10
|
+
spec.version = Fridge::VERSION
|
11
|
+
spec.authors = ['Frank Macreery']
|
12
|
+
spec.email = ['frank@macreery.com']
|
13
|
+
spec.description = %q{Token validation for distributed resource servers}
|
14
|
+
spec.summary = %q{Token validation for distributed resource servers}
|
15
|
+
spec.homepage = 'https://github.com/aptible/fridge'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($RS)
|
19
|
+
spec.test_files = spec.files.grep(/^spec\//)
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_dependency 'gem_config'
|
23
|
+
spec.add_dependency 'jwt'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
26
|
+
spec.add_development_dependency 'aptible-tasks'
|
27
|
+
spec.add_development_dependency 'rake'
|
28
|
+
spec.add_development_dependency 'rails'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 2.0'
|
30
|
+
spec.add_development_dependency 'rspec-rails'
|
31
|
+
spec.add_development_dependency 'pry'
|
32
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'jwt'
|
2
|
+
|
3
|
+
module Fridge
|
4
|
+
class AccessToken
|
5
|
+
attr_accessor :id, :issuer, :subject, :scope, :expires_at
|
6
|
+
|
7
|
+
def initialize(jwt_or_options = nil)
|
8
|
+
options = case jwt_or_options
|
9
|
+
when String
|
10
|
+
validate_public_key!
|
11
|
+
decode_and_verify(jwt_or_options)
|
12
|
+
when Hash then jwt_or_options
|
13
|
+
else {}
|
14
|
+
end
|
15
|
+
options.each do |key, value|
|
16
|
+
instance_variable_set("@#{key}", value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def serialize
|
21
|
+
validate_parameters!
|
22
|
+
validate_private_key!
|
23
|
+
encode_and_sign
|
24
|
+
end
|
25
|
+
|
26
|
+
def encode_and_sign
|
27
|
+
JWT.encode({
|
28
|
+
id: id,
|
29
|
+
iss: issuer,
|
30
|
+
sub: subject,
|
31
|
+
scope: scope,
|
32
|
+
exp: expires_at.to_i
|
33
|
+
}, private_key, algorithm)
|
34
|
+
rescue
|
35
|
+
raise SerializationError, 'Invalid private key or signing algorithm'
|
36
|
+
end
|
37
|
+
|
38
|
+
def decode_and_verify(jwt)
|
39
|
+
hash = JWT.decode(jwt, public_key)
|
40
|
+
{
|
41
|
+
id: hash['id'],
|
42
|
+
issuer: hash['iss'],
|
43
|
+
subject: hash['sub'],
|
44
|
+
scope: hash['scope'],
|
45
|
+
expires_at: Time.at(hash['exp'])
|
46
|
+
}
|
47
|
+
rescue JWT::DecodeError
|
48
|
+
raise InvalidToken, 'Invalid access token'
|
49
|
+
end
|
50
|
+
|
51
|
+
def valid?
|
52
|
+
!expired?
|
53
|
+
end
|
54
|
+
|
55
|
+
def expired?
|
56
|
+
expires_at.nil? || expires_at < Time.now
|
57
|
+
end
|
58
|
+
|
59
|
+
def private_key
|
60
|
+
return unless config.private_key
|
61
|
+
@private_key ||= OpenSSL::PKey::RSA.new(config.private_key)
|
62
|
+
rescue
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def public_key
|
67
|
+
if config.private_key
|
68
|
+
@public_key ||= OpenSSL::PKey::RSA.new(config.private_key).public_key
|
69
|
+
elsif config.public_key
|
70
|
+
@public_key ||= OpenSSL::PKey::RSA.new(config.public_key)
|
71
|
+
end
|
72
|
+
rescue
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def algorithm
|
77
|
+
config.signing_algorithm
|
78
|
+
end
|
79
|
+
|
80
|
+
def config
|
81
|
+
Fridge.configuration
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
def validate_parameters!
|
87
|
+
[:subject, :expires_at].each do |attribute|
|
88
|
+
unless send(attribute)
|
89
|
+
fail SerializationError, "Missing attribute: #{attribute}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def validate_private_key!
|
95
|
+
fail SerializationError, 'No private key configured' unless private_key
|
96
|
+
end
|
97
|
+
|
98
|
+
def validate_public_key!
|
99
|
+
fail SerializationError, 'No public key configured' unless public_key
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Fridge
|
2
|
+
module RailsHelpers
|
3
|
+
def token_scope
|
4
|
+
current_token.scope if current_token
|
5
|
+
end
|
6
|
+
|
7
|
+
def token_subject
|
8
|
+
current_token.subject if current_token
|
9
|
+
end
|
10
|
+
|
11
|
+
def current_token
|
12
|
+
return unless bearer_token
|
13
|
+
@current_token ||= AccessToken.new(bearer_token).tap do |token|
|
14
|
+
validate_token!(token)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def bearer_token
|
19
|
+
header = request.env['HTTP_AUTHORIZATION']
|
20
|
+
header.gsub(/^Bearer /, '') unless header.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def session_subject
|
24
|
+
session_token.subject if session_token
|
25
|
+
end
|
26
|
+
|
27
|
+
def session_token
|
28
|
+
return unless cookies[:session_token]
|
29
|
+
@session_token = AccessToken.new(cookies[:session_token]).tap do |token|
|
30
|
+
validate_token!(token)
|
31
|
+
end
|
32
|
+
rescue
|
33
|
+
clear_session_token
|
34
|
+
@session_token = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_token!(access_token)
|
38
|
+
validator = Fridge.configuration.validator
|
39
|
+
fail InvalidToken unless validator.call(access_token)
|
40
|
+
end
|
41
|
+
|
42
|
+
def store_session_token(access_token)
|
43
|
+
# Ensure that any cookie-persisted tokens are read-only
|
44
|
+
access_token.scope = 'read'
|
45
|
+
|
46
|
+
jwt = access_token.serialize
|
47
|
+
cookies[:session_token] = cookie_options.merge(
|
48
|
+
value: jwt,
|
49
|
+
expires_at: access_token.expires_at
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def clear_session_token
|
54
|
+
cookies.delete :session_token, domain: :all
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def cookie_options
|
59
|
+
secure = !Rails.env.development?
|
60
|
+
{ domain: :all, secure: secure, httponly: true }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/fridge.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'gem_config'
|
2
|
+
|
3
|
+
require 'fridge/version'
|
4
|
+
require 'fridge/access_token'
|
5
|
+
require 'fridge/serialization_error'
|
6
|
+
require 'fridge/invalid_token'
|
7
|
+
|
8
|
+
require 'fridge/railtie' if defined?(Rails)
|
9
|
+
|
10
|
+
module Fridge
|
11
|
+
include GemConfig::Base
|
12
|
+
|
13
|
+
with_configuration do
|
14
|
+
has :private_key, classes: [String]
|
15
|
+
has :public_key, classes: [String]
|
16
|
+
|
17
|
+
has :signing_algorithm, values: %w(RS512 RS256), default: 'RS512'
|
18
|
+
|
19
|
+
# A validator must raise an exception or return a false value for an
|
20
|
+
# invalid token
|
21
|
+
has :validator, classes: [Proc],
|
22
|
+
default: -> (access_token) { access_token.valid? }
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'action_controller'
|
3
|
+
require 'action_dispatch'
|
4
|
+
|
5
|
+
module Rails
|
6
|
+
class App
|
7
|
+
def env_config
|
8
|
+
{}
|
9
|
+
end
|
10
|
+
|
11
|
+
def routes
|
12
|
+
return @routes if defined?(@routes)
|
13
|
+
@routes = ActionDispatch::Routing::RouteSet.new
|
14
|
+
@routes.draw do
|
15
|
+
resources :posts
|
16
|
+
end
|
17
|
+
@routes
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.application
|
22
|
+
@app ||= App.new
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Fridge::AccessToken do
|
4
|
+
describe '#initialize' do
|
5
|
+
let(:private_key) { OpenSSL::PKey::RSA.new(1024) }
|
6
|
+
let(:public_key) { OpenSSL::PKey::RSA.new(private_key.public_key) }
|
7
|
+
|
8
|
+
before { Fridge.configuration.public_key = public_key.to_s }
|
9
|
+
|
10
|
+
it 'should accept a hash' do
|
11
|
+
access_token = described_class.new(id: 'foobar')
|
12
|
+
expect(access_token.id).to eq 'foobar'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should accept a JWT' do
|
16
|
+
jwt = JWT.encode({ id: 'foobar', exp: 0 }, private_key, 'RS512')
|
17
|
+
access_token = described_class.new(jwt)
|
18
|
+
expect(access_token.id).to eq 'foobar'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should raise an error on an invalid JWT' do
|
22
|
+
expect { described_class.new('foobar') }.to raise_error
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should raise an error on an incorrectly signed JWT' do
|
26
|
+
jwt = JWT.encode({ id: 'foobar' }, OpenSSL::PKey::RSA.new(1024), 'RS512')
|
27
|
+
expect { described_class.new(jwt) }.to raise_error Fridge::InvalidToken
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#serialize' do
|
32
|
+
let(:options) do
|
33
|
+
{
|
34
|
+
id: SecureRandom.uuid,
|
35
|
+
iss: 'https://auth.aptible.com',
|
36
|
+
subject: "https://auth.aptible.com/users/#{SecureRandom.uuid}",
|
37
|
+
scope: 'read',
|
38
|
+
expires_at: Time.now + 3600
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
let(:private_key) { OpenSSL::PKey::RSA.new(1024) }
|
43
|
+
let(:public_key) { OpenSSL::PKey::RSA.new(private_key.public_key) }
|
44
|
+
|
45
|
+
before { Fridge.configuration.private_key = private_key.to_s }
|
46
|
+
|
47
|
+
subject { described_class.new(options) }
|
48
|
+
|
49
|
+
it 'should return a JWT comprised of token attributes' do
|
50
|
+
expect(subject.serialize).to eq JWT.encode({
|
51
|
+
id: subject.id,
|
52
|
+
iss: subject.issuer,
|
53
|
+
sub: subject.subject,
|
54
|
+
scope: subject.scope,
|
55
|
+
exp: subject.expires_at.to_i
|
56
|
+
}, private_key, 'RS512')
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should be verifiable with the application public key' do
|
60
|
+
expect { JWT.decode(subject.serialize, public_key) }.not_to raise_error
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should be tamper-resistant' do
|
64
|
+
header, _, signature = subject.serialize.split('.')
|
65
|
+
tampered_claim = JWT.base64url_encode({ foo: 'bar' }.to_json)
|
66
|
+
tampered_token = [header, tampered_claim, signature].join('.')
|
67
|
+
|
68
|
+
expect do
|
69
|
+
JWT.decode(tampered_token, public_key)
|
70
|
+
end.to raise_error JWT::DecodeError
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should represent :exp in seconds since the epoch' do
|
74
|
+
hash = JWT.decode(subject.serialize, public_key)
|
75
|
+
expect(hash['exp']).to be_a Fixnum
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should be deterministic' do
|
79
|
+
expect(subject.serialize).to eq subject.serialize
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should complement #initialize' do
|
83
|
+
copy = described_class.new(subject.serialize)
|
84
|
+
expect(copy.subject).to eq subject.subject
|
85
|
+
expect(copy.expires_at.to_i).to eq subject.expires_at.to_i
|
86
|
+
expect(copy.scope).to eq subject.scope
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should raise an error if required attributes are missing' do
|
90
|
+
subject.subject = nil
|
91
|
+
expect { subject.serialize }.to raise_error Fridge::SerializationError
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#expired?' do
|
96
|
+
it 'should return true if the access token has expired' do
|
97
|
+
subject.stub(:expires_at) { Time.now - 3600 }
|
98
|
+
expect(subject).to be_expired
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should return true if the access token has no expiration set' do
|
102
|
+
subject.stub(:expires_at) { nil }
|
103
|
+
expect(subject).to be_expired
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should return false otherwise' do
|
107
|
+
subject.stub(:expires_at) { Time.now + 3600 }
|
108
|
+
expect(subject).not_to be_expired
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fixtures/app'
|
3
|
+
require 'fixtures/controller'
|
4
|
+
require 'rspec/rails'
|
5
|
+
|
6
|
+
# http://say26.com/rspec-testing-controllers-outside-of-a-rails-application
|
7
|
+
describe Controller, type: :controller do
|
8
|
+
context Fridge::RailsHelpers do
|
9
|
+
let(:private_key) { OpenSSL::PKey::RSA.new(1024) }
|
10
|
+
let(:public_key) { OpenSSL::PKey::RSA.new(private_key.public_key) }
|
11
|
+
|
12
|
+
let(:options) do
|
13
|
+
{
|
14
|
+
subject: "https://auth.aptible.com/users/#{SecureRandom.uuid}",
|
15
|
+
expires_at: Time.now + 3600
|
16
|
+
}
|
17
|
+
end
|
18
|
+
let(:access_token) { Fridge::AccessToken.new(options) }
|
19
|
+
|
20
|
+
let(:cookies) { controller.send(:cookies) }
|
21
|
+
|
22
|
+
before { Fridge.configuration.private_key = private_key.to_s }
|
23
|
+
before { Fridge.configuration.public_key = public_key.to_s }
|
24
|
+
|
25
|
+
describe '#bearer_token' do
|
26
|
+
it 'returns the bearer token from the Authorization: header' do
|
27
|
+
request.env['HTTP_AUTHORIZATION'] = 'Bearer foobar'
|
28
|
+
expect(controller.bearer_token).to eq 'foobar'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns nil in the absence of an Authorization: header' do
|
32
|
+
request.env['HTTP_AUTHORIZATION'] = nil
|
33
|
+
expect(controller.bearer_token).to be_nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#token_subject' do
|
38
|
+
it 'returns the subject encoded in the token' do
|
39
|
+
controller.stub(:current_token) { access_token }
|
40
|
+
expect(controller.token_subject).to eq access_token.subject
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns nil if no token is present' do
|
44
|
+
controller.stub(:current_token) { nil }
|
45
|
+
expect(controller.token_subject).to be_nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#token_scope' do
|
50
|
+
it 'returns the scope encoded in the token' do
|
51
|
+
controller.stub(:current_token) { access_token }
|
52
|
+
expect(controller.token_scope).to eq access_token.scope
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns nil if no token is present' do
|
56
|
+
controller.stub(:current_token) { nil }
|
57
|
+
expect(controller.token_scope).to be_nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#current_token' do
|
62
|
+
before { controller.stub(:bearer_token) { access_token.serialize } }
|
63
|
+
|
64
|
+
it 'should raise an error if the token is not a valid JWT' do
|
65
|
+
controller.stub(:bearer_token) { 'foobar' }
|
66
|
+
expect { controller.current_token }.to raise_error Fridge::InvalidToken
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should raise an error if the token has expired' do
|
70
|
+
access_token.expires_at = Time.now - 3600
|
71
|
+
expect { controller.current_token }.to raise_error Fridge::InvalidToken
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should raise an error if custom validation fails' do
|
75
|
+
Fridge.configuration.validator = -> (token) { false }
|
76
|
+
expect { controller.current_token }.to raise_error Fridge::InvalidToken
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should not raise an error if a valid token is passed' do
|
80
|
+
expect { controller.current_token }.not_to raise_error
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should return the token if a valid token is passed' do
|
84
|
+
expect(controller.current_token.id).to eq access_token.id
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#session_subject' do
|
89
|
+
it 'returns the subject encoded in the session' do
|
90
|
+
controller.stub(:session_token) { access_token }
|
91
|
+
expect(controller.session_subject).to eq access_token.subject
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'returns nil if no session is present' do
|
95
|
+
controller.stub(:session_token) { nil }
|
96
|
+
expect(controller.session_subject).to be_nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#session_token' do
|
101
|
+
it 'should delete all cookies on error' do
|
102
|
+
cookies[:session_token] = 'foobar'
|
103
|
+
controller.session_token
|
104
|
+
expect(cookies.deleted?(:session_token, domain: :all)).to be_true
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should return nil on error' do
|
108
|
+
cookies[:session_token] = 'foobar'
|
109
|
+
expect(controller.session_token).to be_nil
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should return the token stored in :session_token' do
|
113
|
+
cookies[:session_token] = access_token.serialize
|
114
|
+
expect(controller.session_token.id).to eq access_token.id
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#store_session_token' do
|
119
|
+
it 'should set a session cookie' do
|
120
|
+
Rails.stub_chain(:env, :development?) { false }
|
121
|
+
controller.store_session_token(access_token)
|
122
|
+
expect(cookies[:session_token]).to eq access_token.serialize
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
# Load shared spec files
|
5
|
+
Dir["#{File.dirname(__FILE__)}/shared/**/*.rb"].each do |file|
|
6
|
+
require file
|
7
|
+
end
|
8
|
+
|
9
|
+
# Require library up front
|
10
|
+
require 'fridge'
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.before { Fridge.configuration.reset }
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fridge
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Frank Macreery
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: gem_config
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jwt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '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: '1.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: aptible-tasks
|
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: rake
|
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: rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec-rails
|
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: pry
|
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: Token validation for distributed resource servers
|
140
|
+
email:
|
141
|
+
- frank@macreery.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- .gitignore
|
147
|
+
- .rspec
|
148
|
+
- .travis.yml
|
149
|
+
- Gemfile
|
150
|
+
- LICENSE.md
|
151
|
+
- README.md
|
152
|
+
- Rakefile
|
153
|
+
- fridge.gemspec
|
154
|
+
- lib/fridge.rb
|
155
|
+
- lib/fridge/access_token.rb
|
156
|
+
- lib/fridge/invalid_token.rb
|
157
|
+
- lib/fridge/rails_helpers.rb
|
158
|
+
- lib/fridge/railtie.rb
|
159
|
+
- lib/fridge/serialization_error.rb
|
160
|
+
- lib/fridge/version.rb
|
161
|
+
- spec/fixtures/app.rb
|
162
|
+
- spec/fixtures/controller.rb
|
163
|
+
- spec/fridge/access_token_spec.rb
|
164
|
+
- spec/fridge/rails_helpers_spec.rb
|
165
|
+
- spec/spec_helper.rb
|
166
|
+
homepage: https://github.com/aptible/fridge
|
167
|
+
licenses:
|
168
|
+
- MIT
|
169
|
+
metadata: {}
|
170
|
+
post_install_message:
|
171
|
+
rdoc_options: []
|
172
|
+
require_paths:
|
173
|
+
- lib
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - '>='
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '0'
|
179
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - '>='
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
requirements: []
|
185
|
+
rubyforge_project:
|
186
|
+
rubygems_version: 2.2.1
|
187
|
+
signing_key:
|
188
|
+
specification_version: 4
|
189
|
+
summary: Token validation for distributed resource servers
|
190
|
+
test_files:
|
191
|
+
- spec/fixtures/app.rb
|
192
|
+
- spec/fixtures/controller.rb
|
193
|
+
- spec/fridge/access_token_spec.rb
|
194
|
+
- spec/fridge/rails_helpers_spec.rb
|
195
|
+
- spec/spec_helper.rb
|