warden-jwt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +13 -0
  6. data/Gemfile +6 -0
  7. data/Guardfile +16 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +41 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +7 -0
  13. data/lib/warden/jwt/config.rb +117 -0
  14. data/lib/warden/jwt/strategy.rb +73 -0
  15. data/lib/warden/jwt/version.rb +5 -0
  16. data/lib/warden/jwt.rb +9 -0
  17. data/spec/fixtures/vcr_cassettes/Warden_JWT_Strategy/_authenticate_/with_audience_check/and_invalid_audience/fails_authentication.yml +55 -0
  18. data/spec/fixtures/vcr_cassettes/Warden_JWT_Strategy/_authenticate_/with_audience_check/and_valid_audience/authenticates_successfully.yml +55 -0
  19. data/spec/fixtures/vcr_cassettes/Warden_JWT_Strategy/_authenticate_/with_invalid_password/fails_authentication.yml +52 -0
  20. data/spec/fixtures/vcr_cassettes/Warden_JWT_Strategy/_authenticate_/with_invalid_username/fails_authentication.yml +52 -0
  21. data/spec/fixtures/vcr_cassettes/Warden_JWT_Strategy/_authenticate_/with_issuer_check/and_invalid_issuer/raises_invalid_issuer_exception.yml +55 -0
  22. data/spec/fixtures/vcr_cassettes/Warden_JWT_Strategy/_authenticate_/with_issuer_check/and_valid_issuer/authenticates_successfully.yml +55 -0
  23. data/spec/fixtures/vcr_cassettes/Warden_JWT_Strategy/_authenticate_/with_valid_parameters/authenticates_successfully.yml +55 -0
  24. data/spec/helpers/request_helper.rb +9 -0
  25. data/spec/spec_helper.rb +18 -0
  26. data/spec/support/vcr.rb +7 -0
  27. data/spec/warden/jwt/config_spec.rb +183 -0
  28. data/spec/warden/jwt/strategy_spec.rb +143 -0
  29. data/spec/warden/jwt_spec.rb +7 -0
  30. data/warden-jwt.gemspec +38 -0
  31. metadata +297 -0
@@ -0,0 +1,55 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: http://localhost:4000/oauth/token
6
+ body:
7
+ encoding: US-ASCII
8
+ string: grant_type=password&username=you%40example.com&password=mypassword
9
+ headers:
10
+ Accept:
11
+ - "*/*; q=0.5, application/xml"
12
+ Accept-Encoding:
13
+ - gzip, deflate
14
+ Content-Length:
15
+ - '66'
16
+ Content-Type:
17
+ - application/x-www-form-urlencoded
18
+ User-Agent:
19
+ - Ruby
20
+ response:
21
+ status:
22
+ code: 200
23
+ message: OK
24
+ headers:
25
+ X-Frame-Options:
26
+ - SAMEORIGIN
27
+ X-Xss-Protection:
28
+ - 1; mode=block
29
+ X-Content-Type-Options:
30
+ - nosniff
31
+ Cache-Control:
32
+ - no-store
33
+ Pragma:
34
+ - no-cache
35
+ Content-Type:
36
+ - application/json; charset=utf-8
37
+ Etag:
38
+ - W/"e660f9f498b0d02ca450145e9ac9a20e"
39
+ Set-Cookie:
40
+ - _oauth-provider-doorkeeper_session=NUR4dnBROHZHTjhiTm5DZmF4LzJUQjY3Y0wvTFliMDA2U1RIQTJ2cFk3VjJpaWFWM3NGYmFmSE5wRm1mbHFQWGViOW9PYlRaenNZR0h2cERCOXMraUE9PS0tT0U3YlJnT0J5bm9sVmpwQnoxNXhkZz09--cca28b97961fe2e163405d48eed26c0f1767398d;
41
+ path=/; HttpOnly
42
+ X-Request-Id:
43
+ - 86aa1ef4-c4cb-446e-b45e-af9b82e6519d
44
+ X-Runtime:
45
+ - '0.134671'
46
+ Connection:
47
+ - close
48
+ Server:
49
+ - thin
50
+ body:
51
+ encoding: UTF-8
52
+ string: '{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOiIyMDE1LTA3LTI4VDA0OjE2OjUxLjAyMSswMDowMCIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDAwMCIsImF1ZCI6WyIxZjI0YmY1NDJiNjkyNWZmM2IxODAzMmY5MzExYzEyMmMyMTA2OGNlMGEwOTAzM2IyMDcxMTJmODI2ZGIzZDdmIl0sInVpZCI6OSwidW4iOiJ0ZXN0IiwiZW1haWwiOiJ5b3VAZXhhbXBsZS5jb20iLCJmbiI6IlRlc3QiLCJsbiI6IlRlc3QiLCJuIjoiVGVzdCIsInIiOiJkZWZhdWx0IiwibSI6W3sicCI6ImRlZmF1bHQiLCJvaWQiOiIxZjI0YmY1NDJiNjkyNWZmM2IxODAzMmY5MzExYzEyMmMyMTA2OGNlMGEwOTAzM2IyMDcxMTJmODI2ZGIzZDdmIiwibyI6IkRpZ2lmaW5kIn1dfQ.BjbvUcrR8HKo51wBA3hv3VlQimmS_oMrpSweri57-yk","token_type":"bearer","expires_in":7200,"refresh_token":"c20c932ebd9dcad41b47e7ab6998ad48fe365c8da605de95f7ce0dfd0a97ee1f","scope":"public","created_at":1438057011}'
53
+ http_version:
54
+ recorded_at: Tue, 28 Jul 2015 04:16:51 GMT
55
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,55 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: http://localhost:4000/oauth/token
6
+ body:
7
+ encoding: US-ASCII
8
+ string: grant_type=password&username=you%40example.com&password=mypassword
9
+ headers:
10
+ Accept:
11
+ - "*/*; q=0.5, application/xml"
12
+ Accept-Encoding:
13
+ - gzip, deflate
14
+ Content-Length:
15
+ - '66'
16
+ Content-Type:
17
+ - application/x-www-form-urlencoded
18
+ User-Agent:
19
+ - Ruby
20
+ response:
21
+ status:
22
+ code: 200
23
+ message: OK
24
+ headers:
25
+ X-Frame-Options:
26
+ - SAMEORIGIN
27
+ X-Xss-Protection:
28
+ - 1; mode=block
29
+ X-Content-Type-Options:
30
+ - nosniff
31
+ Cache-Control:
32
+ - no-store
33
+ Pragma:
34
+ - no-cache
35
+ Content-Type:
36
+ - application/json; charset=utf-8
37
+ Etag:
38
+ - W/"745b7d26dc386138c4cac5914250021b"
39
+ Set-Cookie:
40
+ - _oauth-provider-doorkeeper_session=VGxZYkVEUHM2TXhqNkxTb0VqM3ZrMWJ0NEpLWm9iZzgvcWR3a2lxVktGd0lPMFBIbVBBVGE4dVBYSXFmVmxlckhiVjlleElGaEFFOEJzYmRBNUltZWc9PS0tVVNrVVhvL3pzaWs3aW1ZMlY0d0NpQT09--b8cdb35682eda8b627cb0bc52cd2918cec880d7c;
41
+ path=/; HttpOnly
42
+ X-Request-Id:
43
+ - 8c70bde2-a5a6-4341-9c4b-1e399dd750d7
44
+ X-Runtime:
45
+ - '0.379515'
46
+ Connection:
47
+ - close
48
+ Server:
49
+ - thin
50
+ body:
51
+ encoding: UTF-8
52
+ string: '{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOiIyMDE1LTA3LTI4VDA0OjE0OjA5Ljg0NCswMDowMCIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDAwMCIsImF1ZCI6WyIxZjI0YmY1NDJiNjkyNWZmM2IxODAzMmY5MzExYzEyMmMyMTA2OGNlMGEwOTAzM2IyMDcxMTJmODI2ZGIzZDdmIl0sInVpZCI6OSwidW4iOiJ0ZXN0IiwiZW1haWwiOiJ5b3VAZXhhbXBsZS5jb20iLCJmbiI6IlRlc3QiLCJsbiI6IlRlc3QiLCJuIjoiVGVzdCIsInIiOiJkZWZhdWx0IiwibSI6W3sicCI6ImRlZmF1bHQiLCJvaWQiOiIxZjI0YmY1NDJiNjkyNWZmM2IxODAzMmY5MzExYzEyMmMyMTA2OGNlMGEwOTAzM2IyMDcxMTJmODI2ZGIzZDdmIiwibyI6IkRpZ2lmaW5kIn1dfQ.DPWNg9ESIDUJRVGhD1ICKs93YryTWRNXyu4Fv7QmSwM","token_type":"bearer","expires_in":7200,"refresh_token":"aa729119459f41d03c7d6076f3115d5162ad00a2890d45ebc570002f3bed2f21","scope":"public","created_at":1438056849}'
53
+ http_version:
54
+ recorded_at: Tue, 28 Jul 2015 04:14:09 GMT
55
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,9 @@
1
+ module Warden::Spec
2
+ module Helpers
3
+ def env_with_params(path = "/", params = {}, env = {})
4
+ method = params.delete(:method) || "GET"
5
+ env = { 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => "#{method}" }.merge(env)
6
+ Rack::MockRequest.env_for("#{path}?#{Rack::Utils.build_query(params)}", env)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ require "codeclimate-test-reporter"
2
+ CodeClimate::TestReporter.start
3
+
4
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
5
+ require 'warden/jwt'
6
+ require 'webmock/rspec'
7
+ require 'support/vcr'
8
+ require 'rubygems'
9
+ require 'rack'
10
+
11
+
12
+ Dir[File.join(File.dirname(__FILE__), "helpers", "**/*.rb")].each do |f|
13
+ require f
14
+ end
15
+
16
+ RSpec.configure do |config|
17
+ config.include(Warden::Spec::Helpers)
18
+ end
@@ -0,0 +1,7 @@
1
+ require 'vcr'
2
+
3
+ VCR.configure do |config|
4
+ config.cassette_library_dir = "spec/fixtures/vcr_cassettes"
5
+ config.hook_into :webmock
6
+ config.configure_rspec_metadata!
7
+ end
@@ -0,0 +1,183 @@
1
+ require 'spec_helper'
2
+
3
+ describe Warden::JWT::Config do
4
+ let(:warden_scope) { :test_scope }
5
+
6
+ let(:env) do
7
+ { 'warden' => double(:config => warden_config) }
8
+ end
9
+
10
+ let(:warden_config) do
11
+ { :scope_defaults => { warden_scope => { :config => scope_config } } }
12
+ end
13
+
14
+ let(:scope_config) do
15
+ {}
16
+ end
17
+
18
+ let(:request) do
19
+ double(:url => 'http://example.com/the/path', :path => '/the/path')
20
+ end
21
+
22
+ subject(:config) do
23
+ described_class.new(env, warden_scope)
24
+ end
25
+
26
+ before do
27
+ allow(config).to receive_messages(:request => request)
28
+ end
29
+
30
+ def silence_warnings
31
+ old_verbose, $VERBOSE = $VERBOSE, nil
32
+ yield
33
+ ensure
34
+ $VERBOSE = old_verbose
35
+ end
36
+
37
+ describe '#audience' do
38
+ context 'when specified in scope config' do
39
+ it 'returns the audience' do
40
+ scope_config[:audience] = 'foobar'
41
+ expect(config.audience).to eq 'foobar'
42
+ end
43
+ end
44
+
45
+ context 'when specified in ENV' do
46
+ it 'returns the audience' do
47
+ allow(ENV).to receive(:[]).with('IDENTITY_CLIENT_ID').and_return('foobar')
48
+ expect(config.audience).to eq 'foobar'
49
+ end
50
+ end
51
+
52
+ context 'when not specified' do
53
+ it 'raises BadConfig' do
54
+ expect { config.audience }.to raise_error(described_class::BadConfig)
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#secret' do
60
+ context 'when specified in scope config' do
61
+ it 'returns the client secret' do
62
+ scope_config[:secret] = 'foobar'
63
+ expect(config.secret).to eq 'foobar'
64
+ end
65
+ end
66
+
67
+ context 'when specified in ENV' do
68
+ it 'returns the secret' do
69
+ allow(ENV).to receive(:[]).with('IDENTITY_SECRET').and_return('foobar')
70
+ silence_warnings do
71
+ expect(config.secret).to eq 'foobar'
72
+ end
73
+ end
74
+ end
75
+
76
+ context 'when not specified' do
77
+ it 'raises BadConfig' do
78
+ expect { config.secret }.to raise_error(described_class::BadConfig)
79
+ end
80
+ end
81
+ end
82
+
83
+ describe '#issuer' do
84
+ context 'when specified in scope config' do
85
+ it 'returns the identity uri' do
86
+ scope_config[:issuer] = 'http://example.com/callback'
87
+ expect(config.issuer).to eq 'http://example.com/callback'
88
+ end
89
+ end
90
+
91
+ context 'when specified in ENV' do
92
+ it 'returns the identity_uri' do
93
+ allow(ENV).to receive(:[]).with('IDENTITY_URL').and_return('http://example.com/callback')
94
+ silence_warnings do
95
+ expect(config.issuer).to eq 'http://example.com/callback'
96
+ end
97
+ end
98
+ end
99
+
100
+ context 'when not specified' do
101
+ it 'raises BadConfig' do
102
+ expect { config.issuer }.to raise_error(described_class::BadConfig)
103
+ end
104
+ end
105
+ end
106
+
107
+ describe '#username_param' do
108
+ context 'when specified in config' do
109
+ it 'returns the username param' do
110
+ scope_config[:username_param] = 'user'
111
+ expect(config.username_param).to eq 'user'
112
+ end
113
+ end
114
+
115
+ context 'when not specified' do
116
+ it 'returns username' do
117
+ expect(config.username_param).to eq 'username'
118
+ end
119
+ end
120
+ end
121
+
122
+ describe '#password_param' do
123
+ context 'when specified in config' do
124
+ it 'returns the password param' do
125
+ scope_config[:password_param] = 'pass'
126
+ expect(config.password_param).to eq 'pass'
127
+ end
128
+ end
129
+
130
+ context 'when not specified' do
131
+ it 'returns password' do
132
+ expect(config.password_param).to eq 'password'
133
+ end
134
+ end
135
+ end
136
+
137
+ describe "#verify_issuer" do
138
+ context 'when specified in config' do
139
+ it 'returns the verify_issuer param' do
140
+ scope_config[:verify_issuer] = false
141
+ expect(config.verify_issuer).to eq false
142
+ end
143
+ end
144
+
145
+ context 'when not specified' do
146
+ it 'returns true' do
147
+ expect(config.verify_issuer).to eq true
148
+ end
149
+ end
150
+ end
151
+
152
+ describe "#verify_audience" do
153
+ context 'when specified in config' do
154
+ it 'returns the verify_audience param' do
155
+ scope_config[:verify_audience] = false
156
+ expect(config.verify_audience).to eq false
157
+ end
158
+ end
159
+
160
+ context 'when not specified' do
161
+ it 'returns true' do
162
+ expect(config.verify_audience).to eq true
163
+ end
164
+ end
165
+ end
166
+
167
+ describe '#to_hash' do
168
+ it 'includes all configs' do
169
+ scope_config.merge!(
170
+ :issuer => '/foo',
171
+ :audience => 'abc',
172
+ :secret => '123',
173
+ :username_param => 'wef',
174
+ :password_param => 'wef',
175
+ :verify_issuer => 'wef',
176
+ :verify_audience => 'wef'
177
+ )
178
+
179
+ expect(config.to_hash.keys).
180
+ to match_array([:issuer, :audience, :secret, :username_param, :password_param, :verify_issuer, :verify_audience])
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+ require 'warden/jwt'
3
+
4
+ describe Warden::JWT::Strategy do
5
+ let(:strategy) { Warden::Strategies[:jwt].new(env_with_params(path, params, env), warden_scope) }
6
+
7
+ let(:warden_scope) { :test_scope }
8
+
9
+ let(:env) do
10
+ { 'warden' => double(:config => warden_config) }
11
+ end
12
+
13
+ let(:warden_config) do
14
+ { :scope_defaults => { warden_scope => { :config => scope_config } } }
15
+ end
16
+
17
+ let(:secret) { '1fcd5a60e0f8f874c9297d7bc9e8af21a1e8be86add775fcaa1ed8a3722089f398a4ef6fa8a0cc115b85557a72920feaf894e4a34dc3f87dbf91d95f398b5c4c' }
18
+ let(:issuer) { 'http://localhost:4000'}
19
+ let(:audience) { '1f24bf542b6925ff3b18032f9311c122c21068ce0a09033b207112f826db3d7f' }
20
+ let(:verify_audience) { false }
21
+ let(:verify_issuer) { false }
22
+
23
+ let(:scope_config) do
24
+ {
25
+ :issuer => issuer,
26
+ :audience => audience,
27
+ :secret => secret,
28
+ :verify_audience => verify_audience,
29
+ :verify_issuer => verify_issuer
30
+ }
31
+ end
32
+
33
+ let(:params) { {} }
34
+
35
+ let(:path) { '/' }
36
+
37
+ before(:each) do
38
+ RAS = Warden::Strategies unless defined?(RAS)
39
+ Warden::Strategies.clear!
40
+ Warden::Strategies.add(:jwt, Warden::JWT::Strategy)
41
+ end
42
+
43
+ describe "#valid?" do
44
+ context "with empty params" do
45
+ let(:params) { {} }
46
+
47
+ it "is false" do
48
+ expect(strategy.valid?).to eq(false)
49
+ end
50
+ end
51
+
52
+ context "with only username param" do
53
+ let(:params) { {'username' => 'user'} }
54
+
55
+ it "is false" do
56
+ expect(strategy.valid?).to eq(false)
57
+ end
58
+ end
59
+
60
+ context "with only password param" do
61
+ let(:params) { {'password' => 'pass'} }
62
+
63
+ it "is false" do
64
+ expect(strategy.valid?).to eq(false)
65
+ end
66
+ end
67
+
68
+ context "with username and password" do
69
+ let(:params) { {'username' => 'user', 'password' => 'pass'} }
70
+
71
+ it "is true" do
72
+ expect(strategy.valid?).to eq(true)
73
+ end
74
+ end
75
+ end
76
+
77
+ describe "#decode_token_from_json" do
78
+ let(:respone) { }
79
+ end
80
+
81
+ describe "#authenticate!", :vcr do
82
+ let(:params) { {'username' => 'you@example.com', 'password' => 'mypassword'} }
83
+
84
+ context "with valid parameters" do
85
+ it "authenticates successfully" do
86
+ expect(strategy.authenticate!).to eq(:success)
87
+ end
88
+ end
89
+
90
+ context "with invalid password" do
91
+ let(:params) { {'username' => 'you@example.com', 'password' => 'pass'} }
92
+
93
+ it "fails authentication" do
94
+ expect(strategy.authenticate!).to eq(:failure)
95
+ end
96
+ end
97
+
98
+ context "with invalid username" do
99
+ let(:params) { {'username' => 'me@example.com', 'password' => 'pass'} }
100
+
101
+ it "fails authentication" do
102
+ expect(strategy.authenticate!).to eq(:failure)
103
+ end
104
+ end
105
+
106
+ context "with audience check" do
107
+ let(:verify_audience) { true }
108
+
109
+ context "and valid audience" do
110
+ it "authenticates successfully" do
111
+ expect(strategy.authenticate!).to eq(:success)
112
+ end
113
+ end
114
+
115
+ context "and invalid audience" do
116
+ let(:audience) { 'weiufhwef'}
117
+
118
+ it "fails authentication" do
119
+ expect(strategy.authenticate!).to eq(:failure)
120
+ end
121
+ end
122
+ end
123
+
124
+
125
+ context "with issuer check" do
126
+ let(:verify_issuer) { true }
127
+
128
+ context "and valid issuer" do
129
+ it "authenticates successfully" do
130
+ expect(strategy.authenticate!).to eq(:success)
131
+ end
132
+ end
133
+
134
+ context "and invalid issuer" do
135
+ let(:issuer) { 'http://localhost:2000/' }
136
+
137
+ it "raises invalid issuer exception" do
138
+ expect{ strategy.authenticate! }.to raise_error(JWT::InvalidIssuerError)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Warden::JWT do
4
+ it 'has a version number' do
5
+ expect(Warden::JWT::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'warden/jwt/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "warden-jwt"
8
+ spec.version = Warden::JWT::VERSION
9
+ spec.authors = ["Rob Sharp"]
10
+ spec.email = ["rob.sharp@digivizer.com"]
11
+
12
+ spec.summary = %q{A Warden strategy for JWT}
13
+ spec.homepage = "http://github.com/dgvz/warden-jwt"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_runtime_dependency 'jwt', '~> 1.5'
23
+ spec.add_runtime_dependency 'rest-client', '~> 1.8'
24
+ spec.add_runtime_dependency 'warden', '~> 1.2'
25
+ spec.add_runtime_dependency 'addressable', '~> 2.3'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.0'
28
+ spec.add_development_dependency 'rake', '~> 10'
29
+ spec.add_development_dependency 'rack', '~> 1.6'
30
+ spec.add_development_dependency 'rspec', '~> 3.3'
31
+ spec.add_development_dependency 'simplecov', '~> 0.10'
32
+ spec.add_development_dependency 'vcr', '~> 2.9'
33
+ spec.add_development_dependency 'webmock', '~> 1.21'
34
+ spec.add_development_dependency 'multi_json', '~> 1.11'
35
+ spec.add_development_dependency 'guard', '~> 2.11'
36
+ spec.add_development_dependency 'guard-rspec', '~> 4.5'
37
+ spec.add_development_dependency 'guard-bundler', '~> 0.1'
38
+ end