privy_wine_bouncer 1.0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.rubocop.yml +23 -0
- data/.rubocop_todo.yml +182 -0
- data/.travis.yml +124 -0
- data/CHANGELOG.md +60 -0
- data/CONTRIBUTING.md +55 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +22 -0
- data/README.md +238 -0
- data/Rakefile +11 -0
- data/UPGRADING.md +62 -0
- data/lib/generators/templates/wine_bouncer.rb +9 -0
- data/lib/generators/wine_bouncer/initializer_generator.rb +14 -0
- data/lib/privy_wine_bouncer.rb +3 -0
- data/lib/wine_bouncer/auth_methods/auth_methods.rb +38 -0
- data/lib/wine_bouncer/auth_strategies/default.rb +27 -0
- data/lib/wine_bouncer/auth_strategies/protected.rb +43 -0
- data/lib/wine_bouncer/auth_strategies/swagger.rb +33 -0
- data/lib/wine_bouncer/base_strategy.rb +7 -0
- data/lib/wine_bouncer/configuration.rb +70 -0
- data/lib/wine_bouncer/errors.rb +23 -0
- data/lib/wine_bouncer/extension.rb +24 -0
- data/lib/wine_bouncer/oauth2.rb +106 -0
- data/lib/wine_bouncer/version.rb +5 -0
- data/lib/wine_bouncer.rb +14 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/api/default_api.rb +71 -0
- data/spec/dummy/app/api/protected_api.rb +66 -0
- data/spec/dummy/app/api/swagger_api.rb +61 -0
- data/spec/dummy/app/assets/config/manifest.js +1 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +7 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +4 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/models/user.rb +4 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +31 -0
- data/spec/dummy/config/boot.rb +7 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +7 -0
- data/spec/dummy/config/environments/development.rb +39 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +43 -0
- data/spec/dummy/config/initializers/assets.rb +10 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/doorkeeper.rb +94 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
- data/spec/dummy/config/initializers/inflections.rb +18 -0
- data/spec/dummy/config/initializers/mime_types.rb +6 -0
- data/spec/dummy/config/initializers/secret_token.rb +6 -0
- data/spec/dummy/config/initializers/session_store.rb +5 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +16 -0
- data/spec/dummy/config/locales/doorkeeper.en.yml +71 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +8 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20140915153344_create_users.rb +11 -0
- data/spec/dummy/db/migrate/20140915160601_create_doorkeeper_tables.rb +43 -0
- data/spec/dummy/db/schema.rb +62 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/access_token.rb +13 -0
- data/spec/factories/application.rb +8 -0
- data/spec/factories/user.rb +7 -0
- data/spec/intergration/oauth2_default_strategy_spec.rb +189 -0
- data/spec/intergration/oauth2_protected_strategy_spec.rb +199 -0
- data/spec/intergration/oauth2_swagger_strategy_spec.rb +156 -0
- data/spec/lib/generators/wine_bouncer/initializer_generator_spec.rb +19 -0
- data/spec/lib/wine_bouncer/auth_methods/auth_methods_spec.rb +105 -0
- data/spec/lib/wine_bouncer/auth_strategies/default_spec.rb +76 -0
- data/spec/lib/wine_bouncer/auth_strategies/swagger_spec.rb +115 -0
- data/spec/rails_helper.rb +79 -0
- data/spec/shared/orm/active_record.rb +4 -0
- data/spec/spec_helper.rb +95 -0
- data/wine_bouncer.gemspec +33 -0
- metadata +386 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<style>
|
7
|
+
body {
|
8
|
+
background-color: #EFEFEF;
|
9
|
+
color: #2E2F30;
|
10
|
+
text-align: center;
|
11
|
+
font-family: arial, sans-serif;
|
12
|
+
margin: 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
div.dialog {
|
16
|
+
width: 95%;
|
17
|
+
max-width: 33em;
|
18
|
+
margin: 4em auto 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
div.dialog > div {
|
22
|
+
border: 1px solid #CCC;
|
23
|
+
border-right-color: #999;
|
24
|
+
border-left-color: #999;
|
25
|
+
border-bottom-color: #BBB;
|
26
|
+
border-top: #B00100 solid 4px;
|
27
|
+
border-top-left-radius: 9px;
|
28
|
+
border-top-right-radius: 9px;
|
29
|
+
background-color: white;
|
30
|
+
padding: 7px 12% 0;
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
32
|
+
}
|
33
|
+
|
34
|
+
h1 {
|
35
|
+
font-size: 100%;
|
36
|
+
color: #730E15;
|
37
|
+
line-height: 1.5em;
|
38
|
+
}
|
39
|
+
|
40
|
+
div.dialog > p {
|
41
|
+
margin: 0 0 1em;
|
42
|
+
padding: 1em;
|
43
|
+
background-color: #F7F7F7;
|
44
|
+
border: 1px solid #CCC;
|
45
|
+
border-right-color: #999;
|
46
|
+
border-left-color: #999;
|
47
|
+
border-bottom-color: #999;
|
48
|
+
border-bottom-left-radius: 4px;
|
49
|
+
border-bottom-right-radius: 4px;
|
50
|
+
border-top-color: #DADADA;
|
51
|
+
color: #666;
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
</head>
|
56
|
+
|
57
|
+
<body>
|
58
|
+
<!-- This file lives in public/500.html -->
|
59
|
+
<div class="dialog">
|
60
|
+
<div>
|
61
|
+
<h1>We're sorry, but something went wrong.</h1>
|
62
|
+
</div>
|
63
|
+
<p>If you are the application owner check the logs for more information.</p>
|
64
|
+
</div>
|
65
|
+
</body>
|
66
|
+
</html>
|
File without changes
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
FactoryBot.define do
|
4
|
+
factory :access_token, class: Doorkeeper::AccessToken do
|
5
|
+
sequence(:resource_owner_id) { |n| n }
|
6
|
+
application
|
7
|
+
expires_in { 2.hours }
|
8
|
+
|
9
|
+
factory :clientless_access_token do
|
10
|
+
application { nil }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
describe Api::MountedDefaultApiUnderTest, type: :api do
|
7
|
+
let(:user) { FactoryBot.create :user }
|
8
|
+
let(:token) { FactoryBot.create :clientless_access_token, resource_owner_id: user.id, scopes: 'public' }
|
9
|
+
let(:unscoped_token) { FactoryBot.create :clientless_access_token, resource_owner_id: user.id, scopes: '' }
|
10
|
+
let(:custom_scope) { FactoryBot.create :clientless_access_token, resource_owner_id: user.id, scopes: 'custom_scope' } #not a default scope
|
11
|
+
|
12
|
+
before(:example) do
|
13
|
+
WineBouncer.configure do |c|
|
14
|
+
c.auth_strategy = :default
|
15
|
+
|
16
|
+
c.define_resource_owner do
|
17
|
+
User.find(doorkeeper_access_token.resource_owner_id) if doorkeeper_access_token
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'tokens and scopes' do
|
23
|
+
it 'gives access when the token and scope are correct' do
|
24
|
+
get '/default_api/protected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
25
|
+
|
26
|
+
expect(last_response.status).to eq(200)
|
27
|
+
json = JSON.parse(last_response.body)
|
28
|
+
expect(json).to have_key('hello')
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'gives access when tokens are correct and an non doorkeeper default scope is used.' do
|
32
|
+
get '/default_api/oauth2_custom_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{custom_scope.token}"
|
33
|
+
|
34
|
+
expect(last_response.status).to eq(200)
|
35
|
+
json = JSON.parse(last_response.body)
|
36
|
+
expect(json).to have_key('hello')
|
37
|
+
expect(json['hello']).to eq('oauth2_custom_scope')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'raises an authentication error when the token is invalid' do
|
41
|
+
expect { get '/default_api/protected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}-invalid" }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'raises an oauth authentication error when no token is given' do
|
45
|
+
expect { get '/default_api/protected' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'raises an auth forbidden authentication error when the user scope is not correct' do
|
49
|
+
expect { get '/default_api/protected_with_private_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'unprotected endpoint' do
|
54
|
+
it 'allows to call an unprotected endpoint without token' do
|
55
|
+
get '/default_api/unprotected'
|
56
|
+
|
57
|
+
expect(last_response.status).to eq(200)
|
58
|
+
json = JSON.parse(last_response.body)
|
59
|
+
|
60
|
+
expect(json).to have_key('hello')
|
61
|
+
expect(json['hello']).to eq('unprotected world')
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'allows to call an unprotected endpoint with token' do
|
65
|
+
get '/default_api/unprotected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
66
|
+
|
67
|
+
expect(last_response.status).to eq(200)
|
68
|
+
json = JSON.parse(last_response.body)
|
69
|
+
expect(json).to have_key('hello')
|
70
|
+
expect(json['hello']).to eq('unprotected world')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'protected_without_scopes' do
|
75
|
+
it 'allows to call an protected endpoint without scopes' do
|
76
|
+
get '/default_api/protected_without_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
77
|
+
|
78
|
+
expect(last_response.status).to eq(200)
|
79
|
+
json = JSON.parse(last_response.body)
|
80
|
+
expect(json).to have_key('hello')
|
81
|
+
expect(json['hello']).to eq('protected unscoped world')
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'raises an error when an protected endpoint without scopes is called without token ' do
|
85
|
+
expect { get '/default_api/protected_without_scope' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'raises an error because the user does not have the default scope' do
|
89
|
+
expect { get '/default_api/protected_without_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{unscoped_token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'oauth2_dsl' do
|
94
|
+
it 'allows to call an protected endpoint without scopes' do
|
95
|
+
get '/default_api/oauth2_dsl', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
96
|
+
|
97
|
+
expect(last_response.status).to eq(200)
|
98
|
+
json = JSON.parse(last_response.body)
|
99
|
+
expect(json).to have_key('hello')
|
100
|
+
expect(json['hello']).to eq('oauth2 dsl')
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'raises an error when an protected endpoint without scopes is called without token ' do
|
104
|
+
expect { get '/default_api/oauth2_dsl' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'without parameters' do
|
108
|
+
it 'accepts tokens with default scopes' do
|
109
|
+
get '/swagger_api/oauth2_dsl_default_scopes', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
110
|
+
expect(last_response.status).to eq(200)
|
111
|
+
json = JSON.parse(last_response.body)
|
112
|
+
expect(json).to have_key('hello')
|
113
|
+
expect(json['hello']).to eq('oauth dsl default scopes')
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'raises an error when an protected endpoint without scopes is called without token ' do
|
117
|
+
expect { get '/default_api/oauth2_dsl_default_scopes' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'raises an error when token scopes are not default scopes ' do
|
121
|
+
expect { get '/default_api/oauth2_dsl_default_scopes', nil, 'HTTP_AUTHORIZATION' => "Bearer #{custom_scope.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'custom scopes' do
|
126
|
+
it 'allows to call custom scopes' do
|
127
|
+
get '/default_api/oauth2_dsl_custom_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{custom_scope.token}"
|
128
|
+
expect(last_response.status).to eq(200)
|
129
|
+
json = JSON.parse(last_response.body)
|
130
|
+
expect(json).to have_key('hello')
|
131
|
+
expect(json['hello']).to eq('oauth2 dsl custom scope')
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'raises an error when an protected endpoint without scopes is called without token ' do
|
135
|
+
expect { get '/default_api/oauth2_dsl_custom_scope' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'raises an error when token scopes do not match' do
|
139
|
+
expect { get '/default_api/oauth2_dsl_custom_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'oauth2_dsl_multiple_scopes' do
|
144
|
+
it 'allows call on the first scope' do
|
145
|
+
scope_token = FactoryBot.create :clientless_access_token, resource_owner_id: user.id, scopes: 'multiple'
|
146
|
+
get '/default_api/oauth2_dsl_multiple_scopes', nil, 'HTTP_AUTHORIZATION' => "Bearer #{scope_token.token}"
|
147
|
+
expect(last_response.status).to eq(200)
|
148
|
+
json = JSON.parse(last_response.body)
|
149
|
+
expect(json).to have_key('hello')
|
150
|
+
expect(json['hello']).to eq('oauth2 dsl multiple scopes')
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'allows call on the second scope' do
|
154
|
+
scope_token = FactoryBot.create :clientless_access_token, resource_owner_id: user.id, scopes: 'scopes'
|
155
|
+
get '/default_api/oauth2_dsl_multiple_scopes', nil, 'HTTP_AUTHORIZATION' => "Bearer #{scope_token.token}"
|
156
|
+
expect(last_response.status).to eq(200)
|
157
|
+
json = JSON.parse(last_response.body)
|
158
|
+
expect(json).to have_key('hello')
|
159
|
+
expect(json['hello']).to eq('oauth2 dsl multiple scopes')
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'raises an error scope does not match any of the scopes' do
|
163
|
+
expect { get '/default_api/oauth2_dsl_multiple_scopes' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'not_described_world' do
|
169
|
+
it 'allows to call an endpoint without description' do
|
170
|
+
get '/default_api/not_described_world'
|
171
|
+
expect(last_response.status).to eq(200)
|
172
|
+
json = JSON.parse(last_response.body)
|
173
|
+
expect(json).to have_key('hello')
|
174
|
+
expect(json['hello']).to eq('non described world')
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'resource_owner' do
|
179
|
+
it 'is available in the endpoint' do
|
180
|
+
get '/default_api/protected_user', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
181
|
+
|
182
|
+
expect(last_response.status).to eq(200)
|
183
|
+
json = JSON.parse(last_response.body)
|
184
|
+
|
185
|
+
expect(json).to have_key('hello')
|
186
|
+
expect(json['hello']).to eq(user.name)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
describe Api::MountedProtectedApiUnderTest, type: :api do
|
7
|
+
let(:user) { FactoryBot.create :user }
|
8
|
+
let(:token) { FactoryBot.create :clientless_access_token, resource_owner_id: user.id, scopes: 'public' }
|
9
|
+
let(:unscoped_token) { FactoryBot.create :clientless_access_token, resource_owner_id: user.id, scopes: '' }
|
10
|
+
let(:custom_scope) { FactoryBot.create :clientless_access_token, resource_owner_id: user.id, scopes: 'custom_scope' } #not a default scope
|
11
|
+
|
12
|
+
before(:example) do
|
13
|
+
WineBouncer.configure do |c|
|
14
|
+
c.auth_strategy = :protected
|
15
|
+
|
16
|
+
c.define_resource_owner do
|
17
|
+
User.find(doorkeeper_access_token.resource_owner_id) if doorkeeper_access_token
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when WineBouncer is disabled' do
|
23
|
+
before :all do
|
24
|
+
WineBouncer.configure do |c|
|
25
|
+
c.disable { true }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
after :all do
|
30
|
+
WineBouncer.configure do |c|
|
31
|
+
c.disable { false }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'allows request to protected resource without token' do
|
36
|
+
get '/protected_api/protected'
|
37
|
+
expect(last_response.status).to eq(200)
|
38
|
+
json = JSON.parse(last_response.body)
|
39
|
+
expect(json).to have_key('hello')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'tokens and scopes' do
|
44
|
+
it 'gives access when the token and scope are correct' do
|
45
|
+
get '/protected_api/protected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
46
|
+
|
47
|
+
expect(last_response.status).to eq(200)
|
48
|
+
json = JSON.parse(last_response.body)
|
49
|
+
expect(json).to have_key('hello')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'raises an authentication error when the token is invalid' do
|
53
|
+
expect { get '/protected_api/protected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}-invalid" }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'raises an oauth authentication error when no token is given' do
|
57
|
+
expect { get '/protected_api/protected' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'raises an auth forbidden authentication error when the user scope is not correct' do
|
61
|
+
expect { get '/protected_api/protected_with_private_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'unprotected endpoint' do
|
66
|
+
it 'allows to call an unprotected endpoint without token' do
|
67
|
+
get '/protected_api/unprotected'
|
68
|
+
|
69
|
+
expect(last_response.status).to eq(200)
|
70
|
+
json = JSON.parse(last_response.body)
|
71
|
+
|
72
|
+
expect(json).to have_key('hello')
|
73
|
+
expect(json['hello']).to eq('unprotected world')
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'allows to call an unprotected endpoint with token' do
|
77
|
+
get '/protected_api/unprotected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
78
|
+
|
79
|
+
expect(last_response.status).to eq(200)
|
80
|
+
json = JSON.parse(last_response.body)
|
81
|
+
expect(json).to have_key('hello')
|
82
|
+
expect(json['hello']).to eq('unprotected world')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'protected_without_scopes' do
|
87
|
+
it 'does not allow an unauthenticated user to call a protected endpoint' do
|
88
|
+
expect { get '/protected_api/protected_without_scope' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'allows to call an protected endpoint without scopes' do
|
92
|
+
get '/protected_api/protected_without_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
93
|
+
|
94
|
+
expect(last_response.status).to eq(200)
|
95
|
+
json = JSON.parse(last_response.body)
|
96
|
+
expect(json).to have_key('hello')
|
97
|
+
expect(json['hello']).to eq('protected unscoped world')
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'raises an error when an protected endpoint without scopes is called without token ' do
|
101
|
+
expect { get '/protected_api/protected_without_scope' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'raises an error because the user does not have the default scope' do
|
105
|
+
expect { get '/protected_api/protected_without_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{unscoped_token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'oauth2_dsl' do
|
110
|
+
it 'allows to call an protected endpoint without scopes' do
|
111
|
+
get '/protected_api/oauth2_dsl', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
112
|
+
|
113
|
+
expect(last_response.status).to eq(200)
|
114
|
+
json = JSON.parse(last_response.body)
|
115
|
+
expect(json).to have_key('hello')
|
116
|
+
expect(json['hello']).to eq('oauth2_dsl')
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'raises an error when an protected endpoint is called without token' do
|
120
|
+
expect { get '/protected_api/oauth2_dsl' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'without parameters' do
|
124
|
+
it 'allows to call an endpoint with default scopes' do
|
125
|
+
get '/protected_api/oauth2_protected_with_default_scopes', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
126
|
+
expect(last_response.status).to eq(200)
|
127
|
+
json = JSON.parse(last_response.body)
|
128
|
+
expect(json).to have_key('hello')
|
129
|
+
expect(json['hello']).to eq('default oauth2 dsl')
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'raises an error when an protected endpoint without scopes is called without token ' do
|
133
|
+
expect { get '/protected_api/oauth2_protected_with_default_scopes' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'raises an error when token scopes are not default scopes ' do
|
137
|
+
expect { get '/protected_api/oauth2_protected_with_default_scopes', nil, 'HTTP_AUTHORIZATION' => "Bearer #{custom_scope.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'custom scopes' do
|
142
|
+
it 'protects endpoints with custom scopes' do
|
143
|
+
get '/protected_api/oauth2_dsl_custom_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{custom_scope.token}"
|
144
|
+
expect(last_response.status).to eq(200)
|
145
|
+
json = JSON.parse(last_response.body)
|
146
|
+
expect(json).to have_key('hello')
|
147
|
+
expect(json['hello']).to eq('custom scope')
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'raises an error when an protected endpoint without scopes is called without token ' do
|
151
|
+
expect { get '/protected_api/oauth2_dsl_custom_scope' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'raises an error when token scopes do not match' do
|
155
|
+
expect { get '/protected_api/oauth2_dsl_custom_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'public endpoint' do
|
160
|
+
it 'allows to call an unprotected endpoint without token' do
|
161
|
+
get '/protected_api/unprotected_endpoint'
|
162
|
+
expect(last_response.status).to eq(200)
|
163
|
+
json = JSON.parse(last_response.body)
|
164
|
+
expect(json).to have_key('hello')
|
165
|
+
expect(json['hello']).to eq('public oauth2 dsl')
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'allows requests with tokens to public endpoints with tokens' do
|
169
|
+
expect { get '/protected_api/oauth2_protected_with_default_scopes', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}" }.not_to raise_error
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'not_described_world' do
|
175
|
+
it 'authentication is required for a non described endpoint' do
|
176
|
+
get '/protected_api/not_described_world', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
177
|
+
expect(last_response.status).to eq(200)
|
178
|
+
json = JSON.parse(last_response.body)
|
179
|
+
expect(json).to have_key('hello')
|
180
|
+
expect(json['hello']).to eq('non described world')
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'raises an error when an protected endpoint without scopes is called without token ' do
|
184
|
+
expect { get '/protected_api/not_described_world' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context 'resource_owner' do
|
189
|
+
it 'is available in the endpoint' do
|
190
|
+
get '/protected_api/protected_user', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
191
|
+
|
192
|
+
expect(last_response.status).to eq(200)
|
193
|
+
json = JSON.parse(last_response.body)
|
194
|
+
|
195
|
+
expect(json).to have_key('hello')
|
196
|
+
expect(json['hello']).to eq(user.name)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
describe Api::MountedSwaggerApiUnderTest, type: :api do
|
7
|
+
let(:user) { FactoryBot.create :user }
|
8
|
+
let(:token) { FactoryBot.create :clientless_access_token, resource_owner_id: user.id, scopes: 'public' }
|
9
|
+
let(:unscoped_token) { FactoryBot.create :clientless_access_token, resource_owner_id: user.id, scopes: '' }
|
10
|
+
let(:custom_scope) { FactoryBot.create :clientless_access_token, resource_owner_id: user.id, scopes: 'custom_scope' } #not a default scope
|
11
|
+
|
12
|
+
before(:example) do
|
13
|
+
WineBouncer.configure do |c|
|
14
|
+
c.auth_strategy = :swagger
|
15
|
+
|
16
|
+
c.define_resource_owner do
|
17
|
+
User.find(doorkeeper_access_token.resource_owner_id) if doorkeeper_access_token
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'tokens and scopes' do
|
23
|
+
it 'gives access when the token and scope are correct' do
|
24
|
+
get '/swagger_api/protected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
25
|
+
|
26
|
+
expect(last_response.status).to eq(200)
|
27
|
+
json = JSON.parse(last_response.body)
|
28
|
+
expect(json).to have_key('hello')
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'raises an authentication error when the token is invalid' do
|
32
|
+
expect { get '/swagger_api/protected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}-invalid" }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'raises an oauth authentication error when no token is given' do
|
36
|
+
expect { get '/swagger_api/protected' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'raises an auth forbidden authentication error when the user scope is not correct' do
|
40
|
+
expect { get '/swagger_api/protected_with_private_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'unprotected endpoint' do
|
45
|
+
it 'allows to call an unprotected endpoint without token' do
|
46
|
+
get '/swagger_api/unprotected'
|
47
|
+
|
48
|
+
expect(last_response.status).to eq(200)
|
49
|
+
json = JSON.parse(last_response.body)
|
50
|
+
|
51
|
+
expect(json).to have_key('hello')
|
52
|
+
expect(json['hello']).to eq('unprotected world')
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'allows to call an unprotected endpoint with token' do
|
56
|
+
get '/swagger_api/unprotected', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
57
|
+
|
58
|
+
expect(last_response.status).to eq(200)
|
59
|
+
json = JSON.parse(last_response.body)
|
60
|
+
expect(json).to have_key('hello')
|
61
|
+
expect(json['hello']).to eq('unprotected world')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'protected_without_scopes' do
|
66
|
+
it 'allows to call an protected endpoint without scopes' do
|
67
|
+
get '/swagger_api/protected_without_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
68
|
+
|
69
|
+
expect(last_response.status).to eq(200)
|
70
|
+
json = JSON.parse(last_response.body)
|
71
|
+
expect(json).to have_key('hello')
|
72
|
+
expect(json['hello']).to eq('protected unscoped world')
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'raises an error when an protected endpoint without scopes is called without token ' do
|
76
|
+
expect { get '/swagger_api/protected_without_scope' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'raises an error because the user does not have the default scope' do
|
80
|
+
expect { get '/swagger_api/protected_without_scope', nil, 'HTTP_AUTHORIZATION' => "Bearer #{unscoped_token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'oauth2 dsl' do
|
85
|
+
it 'allows to call an protected endpoint without scopes' do
|
86
|
+
get '/swagger_api/oauth2_dsl', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
87
|
+
|
88
|
+
expect(last_response.status).to eq(200)
|
89
|
+
json = JSON.parse(last_response.body)
|
90
|
+
expect(json).to have_key('hello')
|
91
|
+
expect(json['hello']).to eq('oauth2_dsl')
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'raises an error when an protected endpoint without scopes is called without token ' do
|
95
|
+
expect { get '/swagger_api/oauth2_dsl' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'without parameters' do
|
99
|
+
it 'accepts tokens with default scopes' do
|
100
|
+
get '/swagger_api/oauth2_dsl_default_scopes', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
101
|
+
expect(last_response.status).to eq(200)
|
102
|
+
json = JSON.parse(last_response.body)
|
103
|
+
expect(json).to have_key('hello')
|
104
|
+
expect(json['hello']).to eq('oauth dsl default scopes')
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'raises an error when an protected endpoint without scopes is called without token ' do
|
108
|
+
expect { get '/swagger_api/oauth2_dsl_default_scopes' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'raises an error when token scopes are not default scopes ' do
|
112
|
+
expect { get '/swagger_api/oauth2_dsl_default_scopes', nil, 'HTTP_AUTHORIZATION' => "Bearer #{custom_scope.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'custom scopes' do
|
117
|
+
it 'accepts tokens with default scopes' do
|
118
|
+
get '/swagger_api/oauth2_dsl_custom_scopes', nil, 'HTTP_AUTHORIZATION' => "Bearer #{custom_scope.token}"
|
119
|
+
expect(last_response.status).to eq(200)
|
120
|
+
json = JSON.parse(last_response.body)
|
121
|
+
expect(json).to have_key('hello')
|
122
|
+
expect(json['hello']).to eq('oauth dsl custom scopes')
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'raises an error when an protected endpoint without scopes is called without token ' do
|
126
|
+
expect { get '/swagger_api/oauth2_dsl_custom_scopes' }.to raise_exception(WineBouncer::Errors::OAuthUnauthorizedError)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'raises an error when token scopes do not match' do
|
130
|
+
expect { get '/swagger_api/oauth2_dsl_custom_scopes', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}" }.to raise_exception(WineBouncer::Errors::OAuthForbiddenError)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'not_described_world' do
|
136
|
+
it 'allows to call an endpoint without description' do
|
137
|
+
get '/swagger_api/not_described_world'
|
138
|
+
expect(last_response.status).to eq(200)
|
139
|
+
json = JSON.parse(last_response.body)
|
140
|
+
expect(json).to have_key('hello')
|
141
|
+
expect(json['hello']).to eq('non described world')
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'resource_owner' do
|
146
|
+
it 'is available in the endpoint' do
|
147
|
+
get '/swagger_api/protected_user', nil, 'HTTP_AUTHORIZATION' => "Bearer #{token.token}"
|
148
|
+
|
149
|
+
expect(last_response.status).to eq(200)
|
150
|
+
json = JSON.parse(last_response.body)
|
151
|
+
|
152
|
+
expect(json).to have_key('hello')
|
153
|
+
expect(json['hello']).to eq(user.name)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'generator_spec'
|
4
|
+
require 'rails_helper'
|
5
|
+
require 'spec_helper'
|
6
|
+
require 'generators/wine_bouncer/initializer_generator'
|
7
|
+
|
8
|
+
describe WineBouncer::Generators::InitializerGenerator, type: :generator do
|
9
|
+
destination '/tmp/wine_bouncer'
|
10
|
+
|
11
|
+
before do
|
12
|
+
prepare_destination
|
13
|
+
run_generator
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'creates a test initializer' do
|
17
|
+
assert_file 'config/initializers/wine_bouncer.rb', /WineBouncer\.configure/
|
18
|
+
end
|
19
|
+
end
|