rack-prx_auth 0.0.4 → 0.0.6
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 +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
|
+
[](http://badge.fury.io/rb/rack-prx_auth)
|
4
|
+
[](https://gemnasium.com/PRX/rack-prx_auth)
|
5
|
+
[](https://travis-ci.org/PRX/rack-prx_auth)
|
6
|
+
[](https://codeclimate.com/github/PRX/rack-prx_auth)
|
7
|
+
[](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
|