rack-prx_auth 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/Guardfile +2 -2
- data/README.md +37 -2
- data/Rakefile +2 -1
- data/lib/rack/prx_auth/certificate.rb +55 -0
- data/lib/rack/prx_auth/version.rb +1 -1
- data/lib/rack/prx_auth.rb +30 -27
- data/rack-prx_auth.gemspec +6 -8
- data/test/rack/prx_auth/certificate_test.rb +130 -0
- data/test/rack/prx_auth_test.rb +79 -0
- data/test/test_helper.rb +9 -0
- metadata +38 -76
- data/lib/rack/prx_auth/controller_methods.rb +0 -11
- data/lib/rack/prx_auth/public_key.rb +0 -37
- data/lib/rack/prx_auth/railtie.rb +0 -15
- data/test/controller_methods_test.rb +0 -44
- data/test/rack-prx_auth_test.rb +0 -129
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65365d276d9668d2b774150b51cae69cc94e7d4a
|
4
|
+
data.tar.gz: 2f9218bdcfe4b22ac97f45f6040b575e0fa9c665
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 253b726a570baa8db347649f171b89f7d955ebec32c65d860aa426a1dda1df0c1fe333d5518b28530f7a9047bc29febf82d5978d8aca71035f3463017ba63625
|
7
|
+
data.tar.gz: bdd2a4f396fb85aefd68677becf5fedaf16a8b46482ff8bd6735455afa0de71cb367d07a13743405ec5f8911ea83523d9b69900d55593295c10dc61b2ce8fb62
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/Guardfile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
guard :minitest, all_after_pass: true do
|
2
2
|
watch(%r{^test/(.*)\/?test_(.*)\.rb})
|
3
|
-
watch(%r{^lib/
|
4
|
-
watch(%r{^lib/
|
3
|
+
watch(%r{^lib/(.*/)?([^/]+)\.rb}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
|
4
|
+
watch(%r{^lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
|
5
5
|
watch(%r{^lib/(.+)\.rb}) { |m| "test/#{m[1]}_test.rb" }
|
6
6
|
watch(%r{^test/.+_test\.rb})
|
7
7
|
watch(%r{^test/test_helper\.rb}) { 'test' }
|
data/README.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
# Rack::PrxAuth
|
2
2
|
|
3
|
-
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/rack-prx_auth.svg)](http://badge.fury.io/rb/rack-prx_auth)
|
4
|
+
[![Dependency Status](https://gemnasium.com/PRX/rack-prx_auth.svg)](https://gemnasium.com/PRX/rack-prx_auth)
|
5
|
+
[![Build Status](https://travis-ci.org/PRX/rack-prx_auth.svg?branch=master)](https://travis-ci.org/PRX/rack-prx_auth)
|
6
|
+
[![Code Climate](https://codeclimate.com/github/PRX/rack-prx_auth/badges/gpa.svg)](https://codeclimate.com/github/PRX/rack-prx_auth)
|
7
|
+
[![Coverage Status](https://coveralls.io/repos/PRX/rack-prx_auth/badge.svg)](https://coveralls.io/r/PRX/rack-prx_auth)
|
8
|
+
|
9
|
+
This gem adds middleware to a Rack application that decodes and verified a JSON Web Token (JWT) issued by PRX.org. If the JWT is invalid, the middleware will respond with a 401 Unauthorized. If the JWT was not issued by PRX (or the specified issuer), the request will continue through the middleware stack.
|
4
10
|
|
5
11
|
## Installation
|
6
12
|
|
@@ -18,9 +24,38 @@ Or install it yourself as:
|
|
18
24
|
|
19
25
|
$ gem install rack-prx_auth
|
20
26
|
|
27
|
+
In a non-Rails app, add the following to the application's config.ru file:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
use Rack::PrxAuth, cert_location: [CERT LOCATION], issuer: [ISSUER]
|
31
|
+
```
|
32
|
+
The `cert_location` and `issuer` parameters are optional. See below.
|
33
|
+
|
21
34
|
## Usage
|
22
35
|
|
23
|
-
|
36
|
+
### The Request
|
37
|
+
|
38
|
+
Rack-prx_auth looks for a token in the request's HTTP_AUTHORIZATION header. It expects that the header's content will take the form of 'Bearer <your token>'. If no HTTP_AUTHORIZATION header is present, rack-prx_auth passes the request to the next middleware.
|
39
|
+
|
40
|
+
We have another application that's in charge of making the token. It's called id.prx.org. Its job is to show a form for a user to enter credentials, validate those credentials, and then generate a JWT using PRX's private key. See http://openid.net/specs/openid-connect-implicit-1_0.html to find out what information is encoded in a JWT. Basically it's a hash containing, among other things, the user's ID, the issuer of the token, and when the token expires.
|
41
|
+
|
42
|
+
### Configuration
|
43
|
+
|
44
|
+
Rack-prx_auth takes two optional parameters, `issuer` and `cert_location`. See Installation for how to specify them.
|
45
|
+
|
46
|
+
By default, rack-prx_auth will assume that you want to make sure the JWT was issued by PRX. After decoding the JWT, rack-prx_auth checks the `issuer` field to make sure it's id.prx.org. If you want it to check for a different issuer, pass `issuer: <your issuer>` as a parameter.
|
47
|
+
|
48
|
+
Since the JWT was created using PRX's private key, rack-prx_auth needs to fetch PRX's public key to decode it. It does this by accessing the `cert_location` (default is https://id.prx.org/api/v1/certs), generating an OpenSSL::X509::Certificate based on its contents, and determining the public key from the certificate object. Should you wish to get your public key from a different certificate, you may specify a different endpoint by passing `cert_location: <your cert location>` as a parameter. Keep in mind that unless the certificate matches the private key used to make the JWT, rack-prx_auth will return 401.
|
49
|
+
|
50
|
+
### The Response
|
51
|
+
|
52
|
+
If the token isn't valid, meaning it's expired or it wasn't created using our private key, rack-prx_auth will return 401 Unauthorized.
|
53
|
+
|
54
|
+
If there's nothing in the HTTP_AUTHORIZATION heading, there's something but JSON::JWT can't decode it, or the issuer field doesn't specify the correct issuer, rack-prx_auth just punts to the next piece of middleware.
|
55
|
+
|
56
|
+
If all goes well, rack-prx_auth takes the decoded JWT and makes a TokenData object. Then it adds this object to the `env` with the key 'prx.auth'.
|
57
|
+
|
58
|
+
If you are using rack-prx_auth in a Rails app, you'll have a few handy controller methods available to you. Calling `prx_auth_token` within a controller returns the TokenData object, and `prx_authenticated?` tells you whether a TokenData object is available. Also, if you call `user_id` on the TokenData object, you get the user's ID so you can ask id.prx.org for information about them.
|
24
59
|
|
25
60
|
## Contributing
|
26
61
|
|
data/Rakefile
CHANGED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'json/jwt'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
class PrxAuth
|
6
|
+
class Certificate
|
7
|
+
EXPIRES_IN = 43200
|
8
|
+
DEFAULT_CERT_LOC = URI('https://id.prx.org/api/v1/certs')
|
9
|
+
|
10
|
+
attr_reader :cert_location
|
11
|
+
|
12
|
+
def initialize(cert_uri = nil)
|
13
|
+
@cert_location = cert_uri.nil? ? DEFAULT_CERT_LOC : URI(cert_uri)
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?(token)
|
17
|
+
begin
|
18
|
+
JSON::JWT.decode(token, public_key)
|
19
|
+
rescue JSON::JWT::VerificationFailed
|
20
|
+
false
|
21
|
+
else
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def public_key
|
29
|
+
certificate.public_key
|
30
|
+
end
|
31
|
+
|
32
|
+
def certificate
|
33
|
+
if @certificate.nil? || needs_refresh?
|
34
|
+
@certificate = fetch
|
35
|
+
end
|
36
|
+
@certificate
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch
|
40
|
+
certs = JSON.parse(Net::HTTP.get(cert_location))
|
41
|
+
cert_string = certs['certificates'].values[0]
|
42
|
+
@refresh_at = Time.now.to_i + EXPIRES_IN
|
43
|
+
OpenSSL::X509::Certificate.new(cert_string)
|
44
|
+
end
|
45
|
+
|
46
|
+
def needs_refresh?
|
47
|
+
expired? || @refresh_at <= Time.now.to_i
|
48
|
+
end
|
49
|
+
|
50
|
+
def expired?
|
51
|
+
@certificate.not_after < Time.now
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/rack/prx_auth.rb
CHANGED
@@ -1,34 +1,47 @@
|
|
1
1
|
require 'json/jwt'
|
2
2
|
require 'rack/prx_auth/version'
|
3
|
-
require 'rack/prx_auth/
|
3
|
+
require 'rack/prx_auth/certificate'
|
4
4
|
require 'rack/prx_auth/token_data'
|
5
|
-
require 'rack/prx_auth/railtie' if defined?(Rails)
|
6
5
|
|
7
6
|
module Rack
|
8
7
|
class PrxAuth
|
9
|
-
|
8
|
+
INVALID_TOKEN = [
|
9
|
+
401, {'Content-Type' => 'application/json'},
|
10
|
+
[{status: 401, error: 'Invalid JSON Web Token'}.to_json]
|
11
|
+
]
|
12
|
+
|
13
|
+
DEFAULT_ISS = 'id.prx.org'
|
14
|
+
|
15
|
+
attr_reader :issuer
|
10
16
|
|
11
17
|
def initialize(app, options = {})
|
12
18
|
@app = app
|
13
|
-
@
|
19
|
+
@certificate = Certificate.new(options[:cert_location])
|
20
|
+
@issuer = options[:issuer] || DEFAULT_ISS
|
14
21
|
end
|
15
22
|
|
16
23
|
def call(env)
|
17
|
-
|
24
|
+
return @app.call(env) unless env['HTTP_AUTHORIZATION']
|
25
|
+
|
26
|
+
token = env['HTTP_AUTHORIZATION'].split[1]
|
18
27
|
claims = decode_token(token)
|
19
28
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
else
|
25
|
-
[401, {'Content-Type' => 'application/json'}, [{status: 401, error: 'Invalid JSON Web Token'}.to_json]]
|
26
|
-
end
|
27
|
-
else
|
29
|
+
return @app.call(env) unless should_validate_token?(claims)
|
30
|
+
|
31
|
+
if valid?(claims, token)
|
32
|
+
env['prx.auth'] = TokenData.new(claims)
|
28
33
|
@app.call(env)
|
34
|
+
else
|
35
|
+
INVALID_TOKEN
|
29
36
|
end
|
30
37
|
end
|
31
38
|
|
39
|
+
private
|
40
|
+
|
41
|
+
def valid?(claims, token)
|
42
|
+
!expired?(claims) && @certificate.valid?(token)
|
43
|
+
end
|
44
|
+
|
32
45
|
def decode_token(token)
|
33
46
|
begin
|
34
47
|
JSON::JWT.decode(token, :skip_verification)
|
@@ -37,22 +50,12 @@ module Rack
|
|
37
50
|
end
|
38
51
|
end
|
39
52
|
|
40
|
-
def
|
41
|
-
|
42
|
-
JSON::JWT.decode(token, @public_key.key)
|
43
|
-
rescue JSON::JWT::VerificationFailed
|
44
|
-
false
|
45
|
-
else
|
46
|
-
true
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def cert_expired?(certificate)
|
51
|
-
certificate.not_after < Time.now
|
53
|
+
def expired?(claims)
|
54
|
+
Time.now.to_i > (claims['iat'] + claims['exp'])
|
52
55
|
end
|
53
56
|
|
54
|
-
def
|
55
|
-
|
57
|
+
def should_validate_token?(claims)
|
58
|
+
claims['iss'] == @issuer
|
56
59
|
end
|
57
60
|
end
|
58
61
|
end
|
data/rack-prx_auth.gemspec
CHANGED
@@ -6,23 +6,21 @@ require 'rack/prx_auth/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "rack-prx_auth"
|
8
8
|
spec.version = Rack::PrxAuth::VERSION
|
9
|
-
spec.authors = ["Eve Asher"]
|
10
|
-
spec.email = ["eve@prx.org"]
|
9
|
+
spec.authors = ["Eve Asher", "Chris Rhoden"]
|
10
|
+
spec.email = ["eve@prx.org", "carhoden@gmail.com"]
|
11
11
|
spec.summary = %q{Rack middleware that verifies and decodes a JWT token and attaches the token's claims to env.}
|
12
12
|
spec.description = %q{Specific to PRX. Will ignore tokens that were not issued by PRX.}
|
13
|
-
spec.homepage = ""
|
13
|
+
spec.homepage = "https://github.com/PRX/rack-prx_auth"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
-
spec.test_files = spec.files.grep(%r{^
|
18
|
+
spec.test_files = spec.files.grep(%r{^test/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_development_dependency 'bundler', '~> 1.
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
22
22
|
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
-
spec.add_development_dependency '
|
24
|
-
spec.add_development_dependency 'guard-minitest', '~> 2.3', '>= 2.3.2'
|
25
|
-
spec.add_development_dependency 'minitest-stub_any_instance'
|
23
|
+
spec.add_development_dependency 'coveralls', '~> 0'
|
26
24
|
|
27
25
|
spec.add_dependency 'rack', '~> 1.5', '>= 1.5.2'
|
28
26
|
spec.add_dependency 'json', '~> 1.8', '>= 1.8.1'
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Rack::PrxAuth::Certificate do
|
4
|
+
let(:subject) { Rack::PrxAuth::Certificate.new }
|
5
|
+
let(:certificate) { subject }
|
6
|
+
|
7
|
+
describe '#initialize' do
|
8
|
+
it 'allows setting the location of the certificates' do
|
9
|
+
cert = Rack::PrxAuth::Certificate.new('http://example.com')
|
10
|
+
cert.cert_location.must_equal URI('http://example.com')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'defaults to DEFAULT_CERT_LOC' do
|
14
|
+
certificate.cert_location.must_equal Rack::PrxAuth::Certificate::DEFAULT_CERT_LOC
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#valid?' do
|
19
|
+
it 'validates the token with the public key' do
|
20
|
+
token, key = nil, nil
|
21
|
+
certificate.stub(:public_key, :public_key) do
|
22
|
+
JSON::JWT.stub(:decode, Proc.new {|t, k| token, key = t, k }) do
|
23
|
+
certificate.valid?(:token)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
token.must_equal :token
|
28
|
+
key.must_equal :public_key
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns false if verification fails' do
|
32
|
+
JSON::JWT.stub(:decode, Proc.new do |t, k|
|
33
|
+
raise JSON::JWT::VerificationFailed
|
34
|
+
end) do
|
35
|
+
certificate.stub(:public_key, :foo) do
|
36
|
+
certificate.wont_be :valid?, :token
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns true if verification passes' do
|
42
|
+
JSON::JWT.stub(:decode, {}) do
|
43
|
+
certificate.stub(:public_key, :foo) do
|
44
|
+
certificate.must_be :valid?, :token
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#certificate' do
|
51
|
+
it 'calls fetch if unprimed' do
|
52
|
+
def certificate.fetch
|
53
|
+
:sigil
|
54
|
+
end
|
55
|
+
|
56
|
+
certificate.send(:certificate).must_equal :sigil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#public_key' do
|
61
|
+
it 'pulls from the certificate' do
|
62
|
+
certificate.stub(:certificate, Struct.new(:public_key).new(:key)) do
|
63
|
+
certificate.send(:public_key).must_equal :key
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#fetch' do
|
69
|
+
it 'pulls from `#cert_location`' do
|
70
|
+
Net::HTTP.stub(:get, ->(x) { "{\"certificates\":{\"asdf\":\"#{x}\"}}" }) do
|
71
|
+
OpenSSL::X509::Certificate.stub(:new, ->(x) { x }) do
|
72
|
+
certificate.stub(:cert_location, "a://fake.url/here") do
|
73
|
+
certificate.send(:fetch).must_equal "a://fake.url/here"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'sets the expiration value' do
|
80
|
+
Net::HTTP.stub(:get, ->(x) { "{\"certificates\":{\"asdf\":\"#{x}\"}}" }) do
|
81
|
+
OpenSSL::X509::Certificate.stub(:new, ->(_) { Struct.new(:not_after).new(Time.now + 10000) }) do
|
82
|
+
certificate.send :certificate
|
83
|
+
certificate.wont_be :needs_refresh?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#expired?' do
|
90
|
+
let(:stub_cert) { Struct.new(:not_after).new(Time.now + 10000) }
|
91
|
+
before(:each) do
|
92
|
+
certificate.instance_variable_set :'@certificate', stub_cert
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'is false when the certificate is not expired' do
|
96
|
+
certificate.wont_be :expired?
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'is true when the certificate is expired' do
|
100
|
+
stub_cert.not_after = Time.now - 500
|
101
|
+
certificate.must_be :expired?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '#needs_refresh?' do
|
106
|
+
def refresh_at=(time)
|
107
|
+
certificate.instance_variable_set :'@refresh_at', time
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'is true if certificate is expired' do
|
111
|
+
certificate.stub(:expired?, true) do
|
112
|
+
certificate.must_be :needs_refresh?
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'is true if we are past refresh value' do
|
117
|
+
self.refresh_at = Time.now.to_i - 1000
|
118
|
+
certificate.stub(:expired?, false) do
|
119
|
+
certificate.must_be :needs_refresh?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'is false if certificate is not expired and refresh is in the future' do
|
124
|
+
self.refresh_at = Time.now.to_i + 10000
|
125
|
+
certificate.stub(:expired?, false) do
|
126
|
+
certificate.wont_be :needs_refresh?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Rack::PrxAuth do
|
4
|
+
let(:app) { Proc.new {|env| env } }
|
5
|
+
let(:prxauth) { Rack::PrxAuth.new(app) }
|
6
|
+
let(:fake_token) { 'afawefawefawefawegstgnsrtiohnlijblublwjnvrtoign'}
|
7
|
+
let(:env) { {'HTTP_AUTHORIZATION' => 'Bearer ' + fake_token } }
|
8
|
+
let(:claims) { {'sub'=>3, 'exp'=>3600, 'iat'=>Time.now.to_i, 'token_type'=>'bearer', 'scope'=>nil, 'iss'=>'id.prx.org'} }
|
9
|
+
|
10
|
+
describe '#call' do
|
11
|
+
it 'does nothing if there is no authorization header' do
|
12
|
+
env = {}
|
13
|
+
|
14
|
+
prxauth.call(env.clone).must_equal env
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'does nothing if the token is from another issuer' do
|
18
|
+
claims['iss'] = 'auth.elsewhere.org'
|
19
|
+
|
20
|
+
JSON::JWT.stub(:decode, claims) do
|
21
|
+
prxauth.call(env.clone).must_equal env
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'does nothing if token is invalid' do
|
26
|
+
prxauth.call(env.clone).must_equal env
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns 401 if verification fails' do
|
30
|
+
JSON::JWT.stub(:decode, claims) do
|
31
|
+
prxauth.stub(:valid?, false) do
|
32
|
+
prxauth.call(env).must_equal Rack::PrxAuth::INVALID_TOKEN
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'returns 401 if access token has expired' do
|
38
|
+
JSON::JWT.stub(:decode, claims) do
|
39
|
+
prxauth.stub(:expired?, true) do
|
40
|
+
prxauth.call(env).must_equal Rack::PrxAuth::INVALID_TOKEN
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'attaches claims to request params if verification passes' do
|
46
|
+
prxauth.stub(:decode_token, claims) do
|
47
|
+
prxauth.stub(:valid?, true) do
|
48
|
+
prxauth.call(env)['prx.auth'].tap do |token|
|
49
|
+
token.must_be_instance_of Rack::PrxAuth::TokenData
|
50
|
+
token.attributes.must_equal claims
|
51
|
+
token.user_id.must_equal claims['sub']
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#token_expired?' do
|
59
|
+
it 'returns true if token is expired' do
|
60
|
+
claims['iat'] = Time.now.to_i - 4000
|
61
|
+
|
62
|
+
prxauth.send(:expired?, claims).must_equal true
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns false if it is valid' do
|
66
|
+
prxauth.send(:expired?, claims).must_equal false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'initialize' do
|
71
|
+
it 'takes a certificate location as an option' do
|
72
|
+
loc = nil
|
73
|
+
Rack::PrxAuth::Certificate.stub(:new, Proc.new{|l| loc = l}) do
|
74
|
+
Rack::PrxAuth.new(app, cert_location: :location)
|
75
|
+
loc.must_equal :location
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
@@ -1,181 +1,142 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-prx_auth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eve Asher
|
8
|
+
- Chris Rhoden
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2015-04-22 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: bundler
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
16
17
|
requirements:
|
17
|
-
- -
|
18
|
+
- - ~>
|
18
19
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
20
|
+
version: '1.3'
|
20
21
|
type: :development
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
|
-
- -
|
25
|
+
- - ~>
|
25
26
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
27
|
+
version: '1.3'
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
29
|
name: rake
|
29
30
|
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
|
-
- -
|
32
|
+
- - ~>
|
32
33
|
- !ruby/object:Gem::Version
|
33
34
|
version: '10.0'
|
34
35
|
type: :development
|
35
36
|
prerelease: false
|
36
37
|
version_requirements: !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
|
-
- -
|
39
|
+
- - ~>
|
39
40
|
- !ruby/object:Gem::Version
|
40
41
|
version: '10.0'
|
41
42
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
43
|
+
name: coveralls
|
43
44
|
requirement: !ruby/object:Gem::Requirement
|
44
45
|
requirements:
|
45
|
-
- -
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '2.6'
|
48
|
-
- - ">="
|
49
|
-
- !ruby/object:Gem::Version
|
50
|
-
version: 2.6.1
|
51
|
-
type: :development
|
52
|
-
prerelease: false
|
53
|
-
version_requirements: !ruby/object:Gem::Requirement
|
54
|
-
requirements:
|
55
|
-
- - "~>"
|
56
|
-
- !ruby/object:Gem::Version
|
57
|
-
version: '2.6'
|
58
|
-
- - ">="
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
version: 2.6.1
|
61
|
-
- !ruby/object:Gem::Dependency
|
62
|
-
name: guard-minitest
|
63
|
-
requirement: !ruby/object:Gem::Requirement
|
64
|
-
requirements:
|
65
|
-
- - "~>"
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version: '2.3'
|
68
|
-
- - ">="
|
69
|
-
- !ruby/object:Gem::Version
|
70
|
-
version: 2.3.2
|
71
|
-
type: :development
|
72
|
-
prerelease: false
|
73
|
-
version_requirements: !ruby/object:Gem::Requirement
|
74
|
-
requirements:
|
75
|
-
- - "~>"
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: '2.3'
|
78
|
-
- - ">="
|
79
|
-
- !ruby/object:Gem::Version
|
80
|
-
version: 2.3.2
|
81
|
-
- !ruby/object:Gem::Dependency
|
82
|
-
name: minitest-stub_any_instance
|
83
|
-
requirement: !ruby/object:Gem::Requirement
|
84
|
-
requirements:
|
85
|
-
- - ">="
|
46
|
+
- - ~>
|
86
47
|
- !ruby/object:Gem::Version
|
87
48
|
version: '0'
|
88
49
|
type: :development
|
89
50
|
prerelease: false
|
90
51
|
version_requirements: !ruby/object:Gem::Requirement
|
91
52
|
requirements:
|
92
|
-
- -
|
53
|
+
- - ~>
|
93
54
|
- !ruby/object:Gem::Version
|
94
55
|
version: '0'
|
95
56
|
- !ruby/object:Gem::Dependency
|
96
57
|
name: rack
|
97
58
|
requirement: !ruby/object:Gem::Requirement
|
98
59
|
requirements:
|
99
|
-
- -
|
60
|
+
- - ~>
|
100
61
|
- !ruby/object:Gem::Version
|
101
62
|
version: '1.5'
|
102
|
-
- -
|
63
|
+
- - '>='
|
103
64
|
- !ruby/object:Gem::Version
|
104
65
|
version: 1.5.2
|
105
66
|
type: :runtime
|
106
67
|
prerelease: false
|
107
68
|
version_requirements: !ruby/object:Gem::Requirement
|
108
69
|
requirements:
|
109
|
-
- -
|
70
|
+
- - ~>
|
110
71
|
- !ruby/object:Gem::Version
|
111
72
|
version: '1.5'
|
112
|
-
- -
|
73
|
+
- - '>='
|
113
74
|
- !ruby/object:Gem::Version
|
114
75
|
version: 1.5.2
|
115
76
|
- !ruby/object:Gem::Dependency
|
116
77
|
name: json
|
117
78
|
requirement: !ruby/object:Gem::Requirement
|
118
79
|
requirements:
|
119
|
-
- -
|
80
|
+
- - ~>
|
120
81
|
- !ruby/object:Gem::Version
|
121
82
|
version: '1.8'
|
122
|
-
- -
|
83
|
+
- - '>='
|
123
84
|
- !ruby/object:Gem::Version
|
124
85
|
version: 1.8.1
|
125
86
|
type: :runtime
|
126
87
|
prerelease: false
|
127
88
|
version_requirements: !ruby/object:Gem::Requirement
|
128
89
|
requirements:
|
129
|
-
- -
|
90
|
+
- - ~>
|
130
91
|
- !ruby/object:Gem::Version
|
131
92
|
version: '1.8'
|
132
|
-
- -
|
93
|
+
- - '>='
|
133
94
|
- !ruby/object:Gem::Version
|
134
95
|
version: 1.8.1
|
135
96
|
- !ruby/object:Gem::Dependency
|
136
97
|
name: json-jwt
|
137
98
|
requirement: !ruby/object:Gem::Requirement
|
138
99
|
requirements:
|
139
|
-
- -
|
100
|
+
- - ~>
|
140
101
|
- !ruby/object:Gem::Version
|
141
102
|
version: '0.7'
|
142
|
-
- -
|
103
|
+
- - '>='
|
143
104
|
- !ruby/object:Gem::Version
|
144
105
|
version: 0.7.0
|
145
106
|
type: :runtime
|
146
107
|
prerelease: false
|
147
108
|
version_requirements: !ruby/object:Gem::Requirement
|
148
109
|
requirements:
|
149
|
-
- -
|
110
|
+
- - ~>
|
150
111
|
- !ruby/object:Gem::Version
|
151
112
|
version: '0.7'
|
152
|
-
- -
|
113
|
+
- - '>='
|
153
114
|
- !ruby/object:Gem::Version
|
154
115
|
version: 0.7.0
|
155
116
|
description: Specific to PRX. Will ignore tokens that were not issued by PRX.
|
156
117
|
email:
|
157
118
|
- eve@prx.org
|
119
|
+
- carhoden@gmail.com
|
158
120
|
executables: []
|
159
121
|
extensions: []
|
160
122
|
extra_rdoc_files: []
|
161
123
|
files:
|
162
|
-
-
|
163
|
-
-
|
124
|
+
- .gitignore
|
125
|
+
- .travis.yml
|
164
126
|
- Gemfile
|
165
127
|
- Guardfile
|
166
128
|
- LICENSE
|
167
129
|
- README.md
|
168
130
|
- Rakefile
|
169
131
|
- lib/rack/prx_auth.rb
|
170
|
-
- lib/rack/prx_auth/
|
171
|
-
- lib/rack/prx_auth/public_key.rb
|
172
|
-
- lib/rack/prx_auth/railtie.rb
|
132
|
+
- lib/rack/prx_auth/certificate.rb
|
173
133
|
- lib/rack/prx_auth/token_data.rb
|
174
134
|
- lib/rack/prx_auth/version.rb
|
175
135
|
- rack-prx_auth.gemspec
|
176
|
-
- test/
|
177
|
-
- test/rack
|
178
|
-
|
136
|
+
- test/rack/prx_auth/certificate_test.rb
|
137
|
+
- test/rack/prx_auth_test.rb
|
138
|
+
- test/test_helper.rb
|
139
|
+
homepage: https://github.com/PRX/rack-prx_auth
|
179
140
|
licenses:
|
180
141
|
- MIT
|
181
142
|
metadata: {}
|
@@ -185,21 +146,22 @@ require_paths:
|
|
185
146
|
- lib
|
186
147
|
required_ruby_version: !ruby/object:Gem::Requirement
|
187
148
|
requirements:
|
188
|
-
- -
|
149
|
+
- - '>='
|
189
150
|
- !ruby/object:Gem::Version
|
190
151
|
version: '0'
|
191
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
192
153
|
requirements:
|
193
|
-
- -
|
154
|
+
- - '>='
|
194
155
|
- !ruby/object:Gem::Version
|
195
156
|
version: '0'
|
196
157
|
requirements: []
|
197
158
|
rubyforge_project:
|
198
|
-
rubygems_version: 2.
|
159
|
+
rubygems_version: 2.1.11
|
199
160
|
signing_key:
|
200
161
|
specification_version: 4
|
201
162
|
summary: Rack middleware that verifies and decodes a JWT token and attaches the token's
|
202
163
|
claims to env.
|
203
164
|
test_files:
|
204
|
-
- test/
|
205
|
-
- test/rack
|
165
|
+
- test/rack/prx_auth/certificate_test.rb
|
166
|
+
- test/rack/prx_auth_test.rb
|
167
|
+
- test/test_helper.rb
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module Rack
|
2
|
-
class PrxAuth
|
3
|
-
class PublicKey
|
4
|
-
EXPIRES_IN = 43200
|
5
|
-
|
6
|
-
attr_reader :certificate, :cert_location
|
7
|
-
|
8
|
-
def initialize(cert_location = nil)
|
9
|
-
@created_at = Time.now
|
10
|
-
@cert_location = URI(cert_location || 'https://auth.prx.org/api/v1/certs')
|
11
|
-
get_key
|
12
|
-
end
|
13
|
-
|
14
|
-
def refresh_key
|
15
|
-
if Time.now > @created_at + EXPIRES_IN
|
16
|
-
get_key
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def get_key
|
21
|
-
@certificate = get_certificate
|
22
|
-
@key = @certificate.public_key
|
23
|
-
end
|
24
|
-
|
25
|
-
def get_certificate
|
26
|
-
certs = JSON.parse(Net::HTTP.get(@cert_location))
|
27
|
-
cert_string = certs['certificates'].values[0]
|
28
|
-
OpenSSL::X509::Certificate.new(cert_string)
|
29
|
-
end
|
30
|
-
|
31
|
-
def key
|
32
|
-
refresh_key
|
33
|
-
@key
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'rails/railtie'
|
2
|
-
require 'rack/prx_auth/controller_methods'
|
3
|
-
require 'rack/prx_auth'
|
4
|
-
|
5
|
-
class Rack::PrxAuth
|
6
|
-
class Railtie < Rails::Railtie
|
7
|
-
config.to_prepare do
|
8
|
-
ApplicationController.send(:include, Rack::PrxAuth::ControllerMethods)
|
9
|
-
end
|
10
|
-
|
11
|
-
initializer 'rack-prx_auth.insert_middleware' do |app|
|
12
|
-
app.config.middleware.insert_before ActionDispatch::ParamsParser, 'Rack::PrxAuth'
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'minitest/spec'
|
3
|
-
require 'minitest/pride'
|
4
|
-
require 'rack/prx_auth'
|
5
|
-
require 'rack/prx_auth/controller_methods'
|
6
|
-
require 'rack/prx_auth/token_data'
|
7
|
-
|
8
|
-
class FakeController
|
9
|
-
include Rack::PrxAuth::ControllerMethods
|
10
|
-
attr_accessor :request
|
11
|
-
|
12
|
-
Request = Struct.new('Request', :env)
|
13
|
-
|
14
|
-
def initialize(env)
|
15
|
-
self.request = Request.new(env)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
describe Rack::PrxAuth::ControllerMethods do
|
20
|
-
let(:claims) { {'sub'=>nil, 'exp'=>3600, 'iat'=>Time.now.to_i, 'token_type'=>'bearer', 'scope'=>nil, 'iss'=>'auth.prx.org'} }
|
21
|
-
let(:token_data) { Rack::PrxAuth::TokenData.new(claims) }
|
22
|
-
let(:env) { {'prx.auth' => token_data } }
|
23
|
-
let(:empty_env) { Hash.new }
|
24
|
-
|
25
|
-
describe '#prx_auth_token' do
|
26
|
-
it 'returns the token data object' do
|
27
|
-
FakeController.new(env).prx_auth_token.must_equal token_data
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'returns nil if there is no prx.auth field' do
|
31
|
-
FakeController.new(empty_env).prx_auth_token.must_be_nil
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
describe '#prx_authenticated?' do
|
36
|
-
it 'returns false if there is no token data' do
|
37
|
-
FakeController.new(empty_env).prx_authenticated?.must_equal false
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'must be true if there is token data' do
|
41
|
-
FakeController.new(env).prx_authenticated?.must_equal true
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
data/test/rack-prx_auth_test.rb
DELETED
@@ -1,129 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'minitest/spec'
|
3
|
-
require 'minitest/pride'
|
4
|
-
require 'minitest/stub_any_instance'
|
5
|
-
require 'rack/prx_auth'
|
6
|
-
|
7
|
-
describe Rack::PrxAuth do
|
8
|
-
let(:app) { Proc.new {|env| env } }
|
9
|
-
let(:prxauth) { Rack::PrxAuth.new(app) }
|
10
|
-
let(:fake_token) { 'afawefawefawefawegstgnsrtiohnlijblublwjnvrtoign'}
|
11
|
-
let(:env) { {'HTTP_AUTHORIZATION' => 'Bearer ' + fake_token } }
|
12
|
-
let(:claims) { {'sub'=>3, 'exp'=>3600, 'iat'=>Time.now.to_i, 'token_type'=>'bearer', 'scope'=>nil, 'iss'=>'auth.prx.org'} }
|
13
|
-
|
14
|
-
describe '#call' do
|
15
|
-
it 'does nothing if there is no authorization header' do
|
16
|
-
env = {}
|
17
|
-
|
18
|
-
prxauth.call(env).must_equal env
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'does nothing if the token is from another issuer' do
|
22
|
-
claims['iss'] = 'auth.elsewhere.org'
|
23
|
-
|
24
|
-
JSON::JWT.stub(:decode, claims) do
|
25
|
-
prxauth.call(env).must_equal env
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'does nothing if token is invalid' do
|
30
|
-
prxauth.call(env).must_equal env
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'returns 401 if verification fails' do
|
34
|
-
JSON::JWT.stub(:decode, claims) do
|
35
|
-
prxauth.stub(:verified?, false) do
|
36
|
-
prxauth.call(env).must_equal [401, {'Content-Type' => 'application/json'}, [{status: 401, error: 'Invalid JSON Web Token'}.to_json]]
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'returns 401 if access token has expired' do
|
42
|
-
JSON::JWT.stub(:decode, claims) do
|
43
|
-
prxauth.stub(:token_expired?, true) do
|
44
|
-
prxauth.call(env).must_equal [401, {'Content-Type' => 'application/json'}, [{status: 401, error: 'Invalid JSON Web Token'}.to_json]]
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'returns 401 if certificate has expired' do
|
50
|
-
JSON::JWT.stub(:decode, claims) do
|
51
|
-
prxauth.stub(:cert_expired?, true) do
|
52
|
-
prxauth.call(env).must_equal [401, {'Content-Type' => 'application/json'}, [{status: 401, error: 'Invalid JSON Web Token'}.to_json]]
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'attaches claims to request params if verification passes' do
|
58
|
-
JSON::JWT.stub(:decode, claims) do
|
59
|
-
prxauth.call(env)['prx.auth'].must_be_instance_of Rack::PrxAuth::TokenData
|
60
|
-
prxauth.call(env)['prx.auth'].attributes.must_equal claims
|
61
|
-
prxauth.call(env)['prx.auth'].user_id.must_equal claims['sub']
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
describe '#token_expired?' do
|
67
|
-
it 'returns true if token is expired' do
|
68
|
-
claims['iat'] = Time.now.to_i - 4000
|
69
|
-
|
70
|
-
prxauth.token_expired?(claims).must_equal true
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'returns false if it is valid' do
|
74
|
-
prxauth.token_expired?(claims).must_equal false
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
describe '#cert_expired?' do
|
79
|
-
let(:cert) { prxauth.public_key.certificate }
|
80
|
-
|
81
|
-
it 'returns true if cert is expired' do
|
82
|
-
cert.stub(:not_after, Time.now - 100000) do
|
83
|
-
prxauth.cert_expired?(cert).must_equal true
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
it 'returns false if it is valid' do
|
88
|
-
cert.stub(:not_after, Time.now + 100000) do
|
89
|
-
prxauth.cert_expired?(cert).must_equal false
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
describe '#verified?' do
|
95
|
-
it 'returns false if error is raised' do
|
96
|
-
raise_error = Proc.new { raise JSON::JWT::VerificationFailed }
|
97
|
-
|
98
|
-
JSON::JWT.stub(:decode, raise_error) do
|
99
|
-
prxauth.verified?(fake_token).must_equal false
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
it 'returns true if no error is raised' do
|
104
|
-
JSON::JWT.stub(:decode, claims) do
|
105
|
-
prxauth.verified?(fake_token).must_equal true
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
describe 'initialize' do
|
111
|
-
it 'takes a certificate location as an option' do
|
112
|
-
Rack::PrxAuth::PublicKey.stub_any_instance(:get_key, nil) do
|
113
|
-
prxauth = Rack::PrxAuth.new(app, cert_location: 'http://www.prx-auth.org/api/v1/certs')
|
114
|
-
key = prxauth.public_key
|
115
|
-
key.cert_location.host.must_equal 'www.prx-auth.org'
|
116
|
-
key.cert_location.path.must_equal '/api/v1/certs'
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
it 'uses auth.prx.org if no uri is given' do
|
121
|
-
Rack::PrxAuth::PublicKey.stub_any_instance(:get_key, nil) do
|
122
|
-
prxauth = Rack::PrxAuth.new(app)
|
123
|
-
key = prxauth.public_key
|
124
|
-
key.cert_location.host.must_equal 'auth.prx.org'
|
125
|
-
key.cert_location.path.must_equal '/api/v1/certs'
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|