doorkeeper 4.2.5 → 5.0.0
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/.github/ISSUE_TEMPLATE.md +25 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +17 -0
- data/.gitignore +2 -1
- data/.gitlab-ci.yml +16 -0
- data/.hound.yml +2 -13
- data/.rubocop.yml +17 -0
- data/.travis.yml +19 -4
- data/Appraisals +8 -4
- data/CODE_OF_CONDUCT.md +46 -0
- data/Gemfile +1 -1
- data/NEWS.md +115 -0
- data/README.md +175 -39
- data/RELEASING.md +5 -12
- data/Rakefile +6 -0
- data/SECURITY.md +15 -0
- data/UPGRADE.md +2 -0
- data/app/assets/stylesheets/doorkeeper/admin/application.css +2 -2
- data/app/controllers/doorkeeper/application_controller.rb +2 -5
- data/app/controllers/doorkeeper/application_metal_controller.rb +4 -0
- data/app/controllers/doorkeeper/applications_controller.rb +47 -13
- data/app/controllers/doorkeeper/authorizations_controller.rb +55 -12
- data/app/controllers/doorkeeper/authorized_applications_controller.rb +19 -2
- data/app/controllers/doorkeeper/tokens_controller.rb +24 -17
- data/app/helpers/doorkeeper/dashboard_helper.rb +8 -6
- data/app/validators/redirect_uri_validator.rb +13 -2
- data/app/views/doorkeeper/applications/_delete_form.html.erb +3 -1
- data/app/views/doorkeeper/applications/_form.html.erb +31 -19
- data/app/views/doorkeeper/applications/edit.html.erb +1 -1
- data/app/views/doorkeeper/applications/index.html.erb +18 -6
- data/app/views/doorkeeper/applications/new.html.erb +1 -1
- data/app/views/doorkeeper/applications/show.html.erb +8 -5
- data/app/views/doorkeeper/authorizations/error.html.erb +1 -1
- data/app/views/doorkeeper/authorizations/new.html.erb +5 -1
- data/app/views/doorkeeper/authorized_applications/index.html.erb +0 -1
- data/app/views/layouts/doorkeeper/admin.html.erb +15 -15
- data/config/locales/en.yml +19 -6
- data/doorkeeper.gemspec +20 -20
- data/gemfiles/rails_4_2.gemfile +6 -4
- data/gemfiles/rails_5_0.gemfile +4 -4
- data/gemfiles/rails_5_1.gemfile +6 -7
- data/gemfiles/rails_5_2.gemfile +12 -0
- data/gemfiles/rails_master.gemfile +17 -0
- data/lib/doorkeeper/config.rb +123 -71
- data/lib/doorkeeper/engine.rb +7 -3
- data/lib/doorkeeper/errors.rb +20 -5
- data/lib/doorkeeper/grape/helpers.rb +14 -9
- data/lib/doorkeeper/helpers/controller.rb +16 -22
- data/lib/doorkeeper/models/access_grant_mixin.rb +67 -23
- data/lib/doorkeeper/models/access_token_mixin.rb +54 -55
- data/lib/doorkeeper/models/application_mixin.rb +16 -30
- data/lib/doorkeeper/models/concerns/expirable.rb +7 -5
- data/lib/doorkeeper/models/concerns/orderable.rb +13 -0
- data/lib/doorkeeper/models/concerns/scopes.rb +1 -1
- data/lib/doorkeeper/oauth/authorization/code.rb +31 -8
- data/lib/doorkeeper/oauth/authorization/context.rb +15 -0
- data/lib/doorkeeper/oauth/authorization/token.rb +46 -20
- data/lib/doorkeeper/oauth/authorization_code_request.rb +33 -3
- data/lib/doorkeeper/oauth/base_request.rb +23 -7
- data/lib/doorkeeper/oauth/client/credentials.rb +6 -4
- data/lib/doorkeeper/oauth/client.rb +2 -4
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +2 -1
- data/lib/doorkeeper/oauth/client_credentials/issuer.rb +6 -3
- data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -6
- data/lib/doorkeeper/oauth/client_credentials_request.rb +0 -4
- data/lib/doorkeeper/oauth/error.rb +2 -2
- data/lib/doorkeeper/oauth/error_response.rb +11 -4
- data/lib/doorkeeper/oauth/forbidden_token_response.rb +1 -1
- data/lib/doorkeeper/oauth/helpers/scope_checker.rb +0 -8
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +15 -0
- data/lib/doorkeeper/oauth/invalid_token_response.rb +3 -4
- data/lib/doorkeeper/oauth/password_access_token_request.rb +8 -4
- data/lib/doorkeeper/oauth/pre_authorization.rb +45 -13
- data/lib/doorkeeper/oauth/refresh_token_request.rb +7 -1
- data/lib/doorkeeper/oauth/scopes.rb +19 -9
- data/lib/doorkeeper/oauth/token.rb +6 -3
- data/lib/doorkeeper/oauth/token_introspection.rb +128 -0
- data/lib/doorkeeper/oauth/token_response.rb +4 -2
- data/lib/doorkeeper/oauth.rb +13 -0
- data/lib/doorkeeper/orm/active_record/access_grant.rb +27 -0
- data/lib/doorkeeper/orm/active_record/access_token.rb +21 -20
- data/lib/doorkeeper/orm/active_record/application.rb +45 -0
- data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +26 -0
- data/lib/doorkeeper/orm/active_record.rb +21 -8
- data/lib/doorkeeper/rails/helpers.rb +7 -10
- data/lib/doorkeeper/rails/routes.rb +21 -7
- data/lib/doorkeeper/rake/db.rake +40 -0
- data/lib/doorkeeper/rake/setup.rake +6 -0
- data/lib/doorkeeper/rake.rb +14 -0
- data/lib/doorkeeper/request/authorization_code.rb +0 -2
- data/lib/doorkeeper/request/client_credentials.rb +0 -2
- data/lib/doorkeeper/request/code.rb +0 -2
- data/lib/doorkeeper/request/password.rb +1 -13
- data/lib/doorkeeper/request/refresh_token.rb +0 -2
- data/lib/doorkeeper/request/token.rb +0 -2
- data/lib/doorkeeper/request.rb +29 -30
- data/lib/doorkeeper/validations.rb +3 -2
- data/lib/doorkeeper/version.rb +14 -1
- data/lib/doorkeeper.rb +21 -17
- data/lib/generators/doorkeeper/application_owner_generator.rb +26 -12
- data/lib/generators/doorkeeper/confidential_applications_generator.rb +32 -0
- data/lib/generators/doorkeeper/install_generator.rb +17 -9
- data/lib/generators/doorkeeper/migration_generator.rb +26 -9
- data/lib/generators/doorkeeper/pkce_generator.rb +32 -0
- data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +31 -20
- data/lib/generators/doorkeeper/templates/add_confidential_to_applications.rb.erb +13 -0
- data/lib/generators/doorkeeper/templates/{add_owner_to_application_migration.rb → add_owner_to_application_migration.rb.erb} +1 -1
- data/lib/generators/doorkeeper/templates/{add_previous_refresh_token_to_access_tokens.rb → add_previous_refresh_token_to_access_tokens.rb.erb} +1 -1
- data/lib/generators/doorkeeper/templates/enable_pkce_migration.rb.erb +6 -0
- data/lib/generators/doorkeeper/templates/initializer.rb +104 -12
- data/lib/generators/doorkeeper/templates/{migration.rb → migration.rb.erb} +2 -1
- data/lib/generators/doorkeeper/views_generator.rb +3 -1
- data/spec/controllers/application_metal_controller_spec.rb +50 -0
- data/spec/controllers/applications_controller_spec.rb +141 -17
- data/spec/controllers/authorizations_controller_spec.rb +304 -27
- data/spec/controllers/protected_resources_controller_spec.rb +44 -35
- data/spec/controllers/token_info_controller_spec.rb +17 -21
- data/spec/controllers/tokens_controller_spec.rb +201 -17
- data/spec/dummy/app/assets/config/manifest.js +2 -0
- data/spec/dummy/config/environments/test.rb +4 -5
- data/spec/dummy/config/initializers/doorkeeper.rb +23 -2
- data/spec/dummy/config/initializers/{active_record_belongs_to_required_by_default.rb → new_framework_defaults.rb} +5 -1
- data/spec/dummy/config/initializers/secret_token.rb +0 -1
- data/spec/dummy/config/routes.rb +3 -42
- data/spec/dummy/db/migrate/20111122132257_create_users.rb +3 -1
- data/spec/dummy/db/migrate/20120312140401_add_password_to_users.rb +3 -1
- data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +3 -1
- data/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb +3 -1
- data/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb +3 -1
- data/spec/dummy/db/migrate/20170822064514_enable_pkce.rb +6 -0
- data/spec/dummy/db/migrate/20180210183654_add_confidential_to_applications.rb +13 -0
- data/spec/dummy/db/schema.rb +38 -37
- data/spec/factories.rb +1 -1
- data/spec/generators/application_owner_generator_spec.rb +25 -6
- data/spec/generators/confidential_applications_generator_spec.rb +45 -0
- data/spec/generators/install_generator_spec.rb +1 -1
- data/spec/generators/migration_generator_spec.rb +25 -4
- data/spec/generators/pkce_generator_spec.rb +43 -0
- data/spec/generators/previous_refresh_token_generator_spec.rb +57 -0
- data/spec/generators/views_generator_spec.rb +1 -1
- data/spec/grape/grape_integration_spec.rb +135 -0
- data/spec/helpers/doorkeeper/dashboard_helper_spec.rb +2 -2
- data/spec/lib/config_spec.rb +199 -22
- data/spec/lib/doorkeeper_spec.rb +1 -126
- data/spec/lib/models/expirable_spec.rb +0 -4
- data/spec/lib/models/revocable_spec.rb +2 -4
- data/spec/lib/models/scopes_spec.rb +0 -4
- data/spec/lib/oauth/authorization/uri_builder_spec.rb +0 -4
- data/spec/lib/oauth/authorization_code_request_spec.rb +63 -13
- data/spec/lib/oauth/base_request_spec.rb +43 -10
- data/spec/lib/oauth/base_response_spec.rb +1 -1
- data/spec/lib/oauth/client/credentials_spec.rb +5 -5
- data/spec/lib/oauth/client_credentials/creator_spec.rb +6 -2
- data/spec/lib/oauth/client_credentials/issuer_spec.rb +26 -7
- data/spec/lib/oauth/client_credentials/validation_spec.rb +2 -3
- data/spec/lib/oauth/client_credentials_integration_spec.rb +2 -2
- data/spec/lib/oauth/client_credentials_request_spec.rb +4 -5
- data/spec/lib/oauth/client_spec.rb +0 -3
- data/spec/lib/oauth/code_request_spec.rb +5 -5
- data/spec/lib/oauth/error_response_spec.rb +0 -3
- data/spec/lib/oauth/error_spec.rb +1 -3
- data/spec/lib/oauth/forbidden_token_response_spec.rb +1 -4
- data/spec/lib/oauth/helpers/scope_checker_spec.rb +0 -3
- data/spec/lib/oauth/helpers/unique_token_spec.rb +0 -1
- data/spec/lib/oauth/helpers/uri_checker_spec.rb +115 -3
- data/spec/lib/oauth/invalid_token_response_spec.rb +2 -5
- data/spec/lib/oauth/password_access_token_request_spec.rb +46 -5
- data/spec/lib/oauth/pre_authorization_spec.rb +40 -6
- data/spec/lib/oauth/refresh_token_request_spec.rb +30 -14
- data/spec/lib/oauth/scopes_spec.rb +28 -4
- data/spec/lib/oauth/token_request_spec.rb +10 -13
- data/spec/lib/oauth/token_response_spec.rb +0 -1
- data/spec/lib/oauth/token_spec.rb +37 -14
- data/spec/lib/orm/active_record/stale_records_cleaner_spec.rb +79 -0
- data/spec/lib/request/strategy_spec.rb +0 -1
- data/spec/lib/server_spec.rb +10 -0
- data/spec/models/doorkeeper/access_grant_spec.rb +45 -2
- data/spec/models/doorkeeper/access_token_spec.rb +132 -64
- data/spec/models/doorkeeper/application_spec.rb +114 -23
- data/spec/requests/applications/applications_request_spec.rb +139 -6
- data/spec/requests/applications/authorized_applications_spec.rb +1 -1
- data/spec/requests/endpoints/authorization_spec.rb +1 -1
- data/spec/requests/endpoints/token_spec.rb +15 -6
- data/spec/requests/flows/authorization_code_errors_spec.rb +12 -2
- data/spec/requests/flows/authorization_code_spec.rb +199 -2
- data/spec/requests/flows/client_credentials_spec.rb +73 -5
- data/spec/requests/flows/implicit_grant_errors_spec.rb +3 -3
- data/spec/requests/flows/implicit_grant_spec.rb +38 -11
- data/spec/requests/flows/password_spec.rb +160 -24
- data/spec/requests/flows/refresh_token_spec.rb +6 -6
- data/spec/requests/flows/revoke_token_spec.rb +26 -26
- data/spec/requests/flows/skip_authorization_spec.rb +16 -11
- data/spec/requests/protected_resources/metal_spec.rb +2 -2
- data/spec/requests/protected_resources/private_api_spec.rb +2 -2
- data/spec/routing/custom_controller_routes_spec.rb +63 -7
- data/spec/routing/default_routes_spec.rb +6 -2
- data/spec/routing/scoped_routes_spec.rb +16 -2
- data/spec/spec_helper.rb +54 -3
- data/spec/spec_helper_integration.rb +2 -64
- data/spec/support/dependencies/factory_bot.rb +2 -0
- data/spec/support/doorkeeper_rspec.rb +19 -0
- data/spec/support/helpers/access_token_request_helper.rb +1 -1
- data/spec/support/helpers/authorization_request_helper.rb +4 -4
- data/spec/support/helpers/model_helper.rb +9 -4
- data/spec/support/helpers/request_spec_helper.rb +18 -6
- data/spec/support/helpers/url_helper.rb +15 -10
- data/spec/support/http_method_shim.rb +12 -16
- data/spec/support/shared/controllers_shared_context.rb +2 -6
- data/spec/support/shared/models_shared_examples.rb +4 -4
- data/spec/validators/redirect_uri_validator_spec.rb +58 -7
- data/spec/version/version_spec.rb +15 -0
- data/vendor/assets/stylesheets/doorkeeper/bootstrap.min.css +4 -5
- metadata +75 -33
- data/spec/controllers/application_metal_controller.rb +0 -10
- data/spec/support/dependencies/factory_girl.rb +0 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
module ControllerActions
|
|
4
4
|
def index
|
|
@@ -9,11 +9,9 @@ module ControllerActions
|
|
|
9
9
|
render plain: 'show'
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def doorkeeper_unauthorized_render_options(*)
|
|
13
|
-
end
|
|
12
|
+
def doorkeeper_unauthorized_render_options(*); end
|
|
14
13
|
|
|
15
|
-
def doorkeeper_forbidden_render_options(*)
|
|
16
|
-
end
|
|
14
|
+
def doorkeeper_forbidden_render_options(*); end
|
|
17
15
|
end
|
|
18
16
|
|
|
19
17
|
describe 'doorkeeper authorize filter' do
|
|
@@ -35,12 +33,12 @@ describe 'doorkeeper authorize filter' do
|
|
|
35
33
|
|
|
36
34
|
it 'access_token param' do
|
|
37
35
|
expect(Doorkeeper::AccessToken).to receive(:by_token).with(token_string).and_return(token)
|
|
38
|
-
get :index, access_token: token_string
|
|
36
|
+
get :index, params: { access_token: token_string }
|
|
39
37
|
end
|
|
40
38
|
|
|
41
39
|
it 'bearer_token param' do
|
|
42
40
|
expect(Doorkeeper::AccessToken).to receive(:by_token).with(token_string).and_return(token)
|
|
43
|
-
get :index, bearer_token: token_string
|
|
41
|
+
get :index, params: { bearer_token: token_string }
|
|
44
42
|
end
|
|
45
43
|
|
|
46
44
|
it 'Authorization header' do
|
|
@@ -59,7 +57,7 @@ describe 'doorkeeper authorize filter' do
|
|
|
59
57
|
expect(Doorkeeper::AccessToken).to receive(:by_token).exactly(2).times.and_return(token)
|
|
60
58
|
request.env['HTTP_AUTHORIZATION'] = "Bearer #{token_string}"
|
|
61
59
|
get :index
|
|
62
|
-
controller.send(:remove_instance_variable, :@
|
|
60
|
+
controller.send(:remove_instance_variable, :@doorkeeper_token)
|
|
63
61
|
get :index
|
|
64
62
|
end
|
|
65
63
|
end
|
|
@@ -73,25 +71,25 @@ describe 'doorkeeper authorize filter' do
|
|
|
73
71
|
|
|
74
72
|
context 'with valid token', token: :valid do
|
|
75
73
|
it 'allows into index action' do
|
|
76
|
-
get :index, access_token: token_string
|
|
77
|
-
expect(response).to
|
|
74
|
+
get :index, params: { access_token: token_string }
|
|
75
|
+
expect(response).to be_successful
|
|
78
76
|
end
|
|
79
77
|
|
|
80
78
|
it 'allows into show action' do
|
|
81
|
-
get :show, id: '4', access_token: token_string
|
|
82
|
-
expect(response).to
|
|
79
|
+
get :show, params: { id: '4', access_token: token_string }
|
|
80
|
+
expect(response).to be_successful
|
|
83
81
|
end
|
|
84
82
|
end
|
|
85
83
|
|
|
86
84
|
context 'with invalid token', token: :invalid do
|
|
87
85
|
it 'does not allow into index action' do
|
|
88
|
-
get :index, access_token: token_string
|
|
86
|
+
get :index, params: { access_token: token_string }
|
|
89
87
|
expect(response.status).to eq 401
|
|
90
88
|
expect(response.header['WWW-Authenticate']).to match(/^Bearer/)
|
|
91
89
|
end
|
|
92
90
|
|
|
93
91
|
it 'does not allow into show action' do
|
|
94
|
-
get :show, id: '4', access_token: token_string
|
|
92
|
+
get :show, params: { id: '4', access_token: token_string }
|
|
95
93
|
expect(response.status).to eq 401
|
|
96
94
|
expect(response.header['WWW-Authenticate']).to match(/^Bearer/)
|
|
97
95
|
end
|
|
@@ -109,15 +107,16 @@ describe 'doorkeeper authorize filter' do
|
|
|
109
107
|
|
|
110
108
|
it 'allows if the token has particular scopes' do
|
|
111
109
|
token = double(Doorkeeper::AccessToken,
|
|
112
|
-
accessible?: true, scopes: %w
|
|
110
|
+
accessible?: true, scopes: %w[write public],
|
|
113
111
|
previous_refresh_token: "",
|
|
114
112
|
revoke_previous_refresh_token!: true)
|
|
115
113
|
expect(token).to receive(:acceptable?).with([:write]).and_return(true)
|
|
116
114
|
expect(
|
|
117
115
|
Doorkeeper::AccessToken
|
|
118
116
|
).to receive(:by_token).with(token_string).and_return(token)
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
|
|
118
|
+
get :index, params: { access_token: token_string }
|
|
119
|
+
expect(response).to be_successful
|
|
121
120
|
end
|
|
122
121
|
|
|
123
122
|
it 'does not allow if the token does not include given scope' do
|
|
@@ -129,7 +128,8 @@ describe 'doorkeeper authorize filter' do
|
|
|
129
128
|
Doorkeeper::AccessToken
|
|
130
129
|
).to receive(:by_token).with(token_string).and_return(token)
|
|
131
130
|
expect(token).to receive(:acceptable?).with([:write]).and_return(false)
|
|
132
|
-
|
|
131
|
+
|
|
132
|
+
get :index, params: { access_token: token_string }
|
|
133
133
|
expect(response.status).to eq 403
|
|
134
134
|
expect(response.header).to_not include('WWW-Authenticate')
|
|
135
135
|
end
|
|
@@ -146,27 +146,30 @@ describe 'doorkeeper authorize filter' do
|
|
|
146
146
|
before do
|
|
147
147
|
module ControllerActions
|
|
148
148
|
remove_method :doorkeeper_unauthorized_render_options
|
|
149
|
+
|
|
149
150
|
def doorkeeper_unauthorized_render_options(error: nil)
|
|
150
151
|
{ json: ActiveSupport::JSON.encode(error_message: error.description) }
|
|
151
152
|
end
|
|
152
153
|
end
|
|
153
154
|
end
|
|
155
|
+
|
|
154
156
|
after do
|
|
155
157
|
module ControllerActions
|
|
156
158
|
remove_method :doorkeeper_unauthorized_render_options
|
|
159
|
+
|
|
157
160
|
def doorkeeper_unauthorized_render_options(error: nil)
|
|
158
161
|
end
|
|
159
162
|
end
|
|
160
163
|
end
|
|
161
164
|
|
|
162
165
|
it 'it renders a custom JSON response', token: :invalid do
|
|
163
|
-
get :index, access_token: token_string
|
|
166
|
+
get :index, params: { access_token: token_string }
|
|
164
167
|
expect(response.status).to eq 401
|
|
165
168
|
expect(response.content_type).to eq('application/json')
|
|
166
169
|
expect(response.header['WWW-Authenticate']).to match(/^Bearer/)
|
|
167
|
-
|
|
168
|
-
expect(
|
|
169
|
-
expect(
|
|
170
|
+
|
|
171
|
+
expect(json_response).not_to be_nil
|
|
172
|
+
expect(json_response['error_message']).to match('token is invalid')
|
|
170
173
|
end
|
|
171
174
|
end
|
|
172
175
|
|
|
@@ -174,21 +177,23 @@ describe 'doorkeeper authorize filter' do
|
|
|
174
177
|
before do
|
|
175
178
|
module ControllerActions
|
|
176
179
|
remove_method :doorkeeper_unauthorized_render_options
|
|
177
|
-
|
|
180
|
+
|
|
181
|
+
def doorkeeper_unauthorized_render_options(**)
|
|
178
182
|
{ plain: 'Unauthorized' }
|
|
179
183
|
end
|
|
180
184
|
end
|
|
181
185
|
end
|
|
186
|
+
|
|
182
187
|
after do
|
|
183
188
|
module ControllerActions
|
|
184
189
|
remove_method :doorkeeper_unauthorized_render_options
|
|
185
|
-
|
|
186
|
-
end
|
|
190
|
+
|
|
191
|
+
def doorkeeper_unauthorized_render_options(error: nil); end
|
|
187
192
|
end
|
|
188
193
|
end
|
|
189
194
|
|
|
190
195
|
it 'it renders a custom text response', token: :invalid do
|
|
191
|
-
get :index, access_token: token_string
|
|
196
|
+
get :index, params: { access_token: token_string }
|
|
192
197
|
expect(response.status).to eq 401
|
|
193
198
|
expect(response.content_type).to eq('text/plain')
|
|
194
199
|
expect(response.header['WWW-Authenticate']).to match(/^Bearer/)
|
|
@@ -206,8 +211,8 @@ describe 'doorkeeper authorize filter' do
|
|
|
206
211
|
after do
|
|
207
212
|
module ControllerActions
|
|
208
213
|
remove_method :doorkeeper_forbidden_render_options
|
|
209
|
-
|
|
210
|
-
end
|
|
214
|
+
|
|
215
|
+
def doorkeeper_forbidden_render_options(*); end
|
|
211
216
|
end
|
|
212
217
|
end
|
|
213
218
|
|
|
@@ -223,12 +228,14 @@ describe 'doorkeeper authorize filter' do
|
|
|
223
228
|
expired?: false, previous_refresh_token: "",
|
|
224
229
|
revoke_previous_refresh_token!: true)
|
|
225
230
|
end
|
|
231
|
+
|
|
226
232
|
let(:token_string) { '1A2DUWE' }
|
|
227
233
|
|
|
228
234
|
context 'with a JSON custom render' do
|
|
229
235
|
before do
|
|
230
236
|
module ControllerActions
|
|
231
237
|
remove_method :doorkeeper_forbidden_render_options
|
|
238
|
+
|
|
232
239
|
def doorkeeper_forbidden_render_options(*)
|
|
233
240
|
{ json: { error_message: 'Forbidden' } }
|
|
234
241
|
end
|
|
@@ -236,13 +243,13 @@ describe 'doorkeeper authorize filter' do
|
|
|
236
243
|
end
|
|
237
244
|
|
|
238
245
|
it 'renders a custom JSON response' do
|
|
239
|
-
get :index, access_token: token_string
|
|
246
|
+
get :index, params: { access_token: token_string }
|
|
240
247
|
expect(response.header).to_not include('WWW-Authenticate')
|
|
241
248
|
expect(response.content_type).to eq('application/json')
|
|
242
249
|
expect(response.status).to eq 403
|
|
243
|
-
|
|
244
|
-
expect(
|
|
245
|
-
expect(
|
|
250
|
+
|
|
251
|
+
expect(json_response).not_to be_nil
|
|
252
|
+
expect(json_response['error_message']).to match('Forbidden')
|
|
246
253
|
end
|
|
247
254
|
end
|
|
248
255
|
|
|
@@ -258,7 +265,7 @@ describe 'doorkeeper authorize filter' do
|
|
|
258
265
|
end
|
|
259
266
|
|
|
260
267
|
it 'overrides the default status code' do
|
|
261
|
-
get :index, access_token: token_string
|
|
268
|
+
get :index, params: { access_token: token_string }
|
|
262
269
|
expect(response.status).to eq 404
|
|
263
270
|
end
|
|
264
271
|
end
|
|
@@ -267,6 +274,7 @@ describe 'doorkeeper authorize filter' do
|
|
|
267
274
|
before do
|
|
268
275
|
module ControllerActions
|
|
269
276
|
remove_method :doorkeeper_forbidden_render_options
|
|
277
|
+
|
|
270
278
|
def doorkeeper_forbidden_render_options(*)
|
|
271
279
|
{ plain: 'Forbidden' }
|
|
272
280
|
end
|
|
@@ -274,7 +282,7 @@ describe 'doorkeeper authorize filter' do
|
|
|
274
282
|
end
|
|
275
283
|
|
|
276
284
|
it 'renders a custom status code and text response' do
|
|
277
|
-
get :index, access_token: token_string
|
|
285
|
+
get :index, params: { access_token: token_string }
|
|
278
286
|
expect(response.header).to_not include('WWW-Authenticate')
|
|
279
287
|
expect(response.status).to eq 403
|
|
280
288
|
expect(response.body).to eq('Forbidden')
|
|
@@ -285,6 +293,7 @@ describe 'doorkeeper authorize filter' do
|
|
|
285
293
|
before do
|
|
286
294
|
module ControllerActions
|
|
287
295
|
remove_method :doorkeeper_forbidden_render_options
|
|
296
|
+
|
|
288
297
|
def doorkeeper_forbidden_render_options(*)
|
|
289
298
|
{ respond_not_found_when_forbidden: true, plain: 'Not Found' }
|
|
290
299
|
end
|
|
@@ -292,7 +301,7 @@ describe 'doorkeeper authorize filter' do
|
|
|
292
301
|
end
|
|
293
302
|
|
|
294
303
|
it 'overrides the default status code' do
|
|
295
|
-
get :index, access_token: token_string
|
|
304
|
+
get :index, params: { access_token: token_string }
|
|
296
305
|
expect(response.status).to eq 404
|
|
297
306
|
end
|
|
298
307
|
end
|
|
@@ -1,36 +1,27 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe Doorkeeper::TokenInfoController do
|
|
4
|
-
describe 'when requesting
|
|
5
|
-
let(:doorkeeper_token) {
|
|
6
|
-
|
|
7
|
-
before(:each) do
|
|
8
|
-
allow(controller).to receive(:doorkeeper_token) { doorkeeper_token }
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def do_get
|
|
12
|
-
get :show
|
|
13
|
-
end
|
|
4
|
+
describe 'when requesting token info with valid token' do
|
|
5
|
+
let(:doorkeeper_token) { FactoryBot.create(:access_token) }
|
|
14
6
|
|
|
15
7
|
describe 'successful request' do
|
|
8
|
+
it 'responds with token info' do
|
|
9
|
+
get :show, params: { access_token: doorkeeper_token.token }
|
|
16
10
|
|
|
17
|
-
it 'responds with tokeninfo' do
|
|
18
|
-
do_get
|
|
19
11
|
expect(response.body).to eq(doorkeeper_token.to_json)
|
|
20
12
|
end
|
|
21
13
|
|
|
22
14
|
it 'responds with a 200 status' do
|
|
23
|
-
|
|
15
|
+
get :show, params: { access_token: doorkeeper_token.token }
|
|
16
|
+
|
|
24
17
|
expect(response.status).to eq 200
|
|
25
18
|
end
|
|
26
19
|
end
|
|
27
20
|
|
|
28
21
|
describe 'invalid token response' do
|
|
29
|
-
before(:each) do
|
|
30
|
-
allow(controller).to receive(:doorkeeper_token).and_return(nil)
|
|
31
|
-
end
|
|
32
22
|
it 'responds with 401 when doorkeeper_token is not valid' do
|
|
33
|
-
|
|
23
|
+
get :show
|
|
24
|
+
|
|
34
25
|
expect(response.status).to eq 401
|
|
35
26
|
expect(response.headers['WWW-Authenticate']).to match(/^Bearer/)
|
|
36
27
|
end
|
|
@@ -38,14 +29,19 @@ describe Doorkeeper::TokenInfoController do
|
|
|
38
29
|
it 'responds with 401 when doorkeeper_token is invalid, expired or revoked' do
|
|
39
30
|
allow(controller).to receive(:doorkeeper_token).and_return(doorkeeper_token)
|
|
40
31
|
allow(doorkeeper_token).to receive(:accessible?).and_return(false)
|
|
41
|
-
|
|
32
|
+
|
|
33
|
+
get :show
|
|
34
|
+
|
|
42
35
|
expect(response.status).to eq 401
|
|
43
36
|
expect(response.headers['WWW-Authenticate']).to match(/^Bearer/)
|
|
44
37
|
end
|
|
45
38
|
|
|
46
39
|
it 'responds body message for error' do
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
get :show
|
|
41
|
+
|
|
42
|
+
expect(response.body).to eq(
|
|
43
|
+
Doorkeeper::OAuth::ErrorResponse.new(name: :invalid_request, status: :unauthorized).body.to_json
|
|
44
|
+
)
|
|
49
45
|
end
|
|
50
46
|
end
|
|
51
47
|
end
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe Doorkeeper::TokensController do
|
|
4
4
|
describe 'when authorization has succeeded' do
|
|
5
|
-
let :token
|
|
6
|
-
double(:token, authorize: true)
|
|
7
|
-
end
|
|
5
|
+
let(:token) { double(:token, authorize: true) }
|
|
8
6
|
|
|
9
|
-
before do
|
|
10
|
-
allow(controller).to receive(:token) { token }
|
|
11
|
-
end
|
|
12
7
|
|
|
13
8
|
it 'returns the authorization' do
|
|
14
9
|
skip 'verify need of these specs'
|
|
@@ -38,7 +33,7 @@ describe Doorkeeper::TokensController do
|
|
|
38
33
|
allow(I18n).to receive(:translate).
|
|
39
34
|
with(
|
|
40
35
|
custom_message,
|
|
41
|
-
hash_including(scope: [
|
|
36
|
+
hash_including(scope: %i[doorkeeper errors messages])
|
|
42
37
|
).
|
|
43
38
|
and_return('Authorization custom message')
|
|
44
39
|
|
|
@@ -57,19 +52,71 @@ describe Doorkeeper::TokensController do
|
|
|
57
52
|
}
|
|
58
53
|
expect(response.status).to eq 401
|
|
59
54
|
expect(response.headers['WWW-Authenticate']).to match(/Bearer/)
|
|
60
|
-
expect(JSON.
|
|
55
|
+
expect(JSON.parse(response.body)).to eq expected_response_body
|
|
61
56
|
end
|
|
62
57
|
end
|
|
63
58
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
# http://tools.ietf.org/html/rfc7009#section-2.2
|
|
60
|
+
describe 'revoking tokens' do
|
|
61
|
+
let(:client) { FactoryBot.create(:application) }
|
|
62
|
+
let(:access_token) { FactoryBot.create(:access_token, application: client) }
|
|
63
|
+
|
|
64
|
+
before(:each) do
|
|
65
|
+
allow(controller).to receive(:token) { access_token }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context 'when associated app is public' do
|
|
69
|
+
let(:client) { FactoryBot.create(:application, confidential: false) }
|
|
70
|
+
|
|
71
|
+
it 'returns 200' do
|
|
72
|
+
post :revoke
|
|
73
|
+
|
|
74
|
+
expect(response.status).to eq 200
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'revokes the access token' do
|
|
78
|
+
post :revoke
|
|
79
|
+
|
|
80
|
+
expect(access_token.reload).to have_attributes(revoked?: true)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context 'when associated app is confidential' do
|
|
85
|
+
let(:client) { FactoryBot.create(:application, confidential: true) }
|
|
86
|
+
let(:oauth_client) { Doorkeeper::OAuth::Client.new(client) }
|
|
87
|
+
|
|
88
|
+
before(:each) do
|
|
89
|
+
allow_any_instance_of(Doorkeeper::Server).to receive(:client) { oauth_client }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'returns 200' do
|
|
93
|
+
post :revoke
|
|
94
|
+
|
|
95
|
+
expect(response.status).to eq 200
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'revokes the access token' do
|
|
99
|
+
post :revoke
|
|
69
100
|
|
|
70
|
-
|
|
101
|
+
expect(access_token.reload).to have_attributes(revoked?: true)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
context 'when authorization fails' do
|
|
105
|
+
let(:some_other_client) { FactoryBot.create(:application, confidential: true) }
|
|
106
|
+
let(:oauth_client) { Doorkeeper::OAuth::Client.new(some_other_client) }
|
|
107
|
+
|
|
108
|
+
it 'returns 200' do
|
|
109
|
+
post :revoke
|
|
71
110
|
|
|
72
|
-
|
|
111
|
+
expect(response.status).to eq 200
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'does not revoke the access token' do
|
|
115
|
+
post :revoke
|
|
116
|
+
|
|
117
|
+
expect(access_token.reload).to have_attributes(revoked?: false)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
73
120
|
end
|
|
74
121
|
end
|
|
75
122
|
|
|
@@ -79,10 +126,147 @@ describe Doorkeeper::TokensController do
|
|
|
79
126
|
expect(strategy).to receive(:authorize).once
|
|
80
127
|
allow(controller).to receive(:strategy) { strategy }
|
|
81
128
|
allow(controller).to receive(:create) do
|
|
82
|
-
controller.send :authorize_response
|
|
129
|
+
2.times { controller.send :authorize_response }
|
|
130
|
+
controller.render json: {}, status: :ok
|
|
83
131
|
end
|
|
84
132
|
|
|
85
133
|
post :create
|
|
86
134
|
end
|
|
87
135
|
end
|
|
136
|
+
|
|
137
|
+
describe 'when requested token introspection' do
|
|
138
|
+
context 'authorized using Bearer token' do
|
|
139
|
+
let(:client) { FactoryBot.create(:application) }
|
|
140
|
+
let(:access_token) { FactoryBot.create(:access_token, application: client) }
|
|
141
|
+
|
|
142
|
+
it 'responds with full token introspection' do
|
|
143
|
+
request.headers['Authorization'] = "Bearer #{access_token.token}"
|
|
144
|
+
|
|
145
|
+
post :introspect, params: { token: access_token.token }
|
|
146
|
+
|
|
147
|
+
should_have_json 'active', true
|
|
148
|
+
expect(json_response).to include('client_id', 'token_type', 'exp', 'iat')
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
context 'authorized using Client Authentication' do
|
|
153
|
+
let(:client) { FactoryBot.create(:application) }
|
|
154
|
+
let(:access_token) { FactoryBot.create(:access_token, application: client) }
|
|
155
|
+
|
|
156
|
+
it 'responds with full token introspection' do
|
|
157
|
+
request.headers['Authorization'] = basic_auth_header_for_client(client)
|
|
158
|
+
|
|
159
|
+
post :introspect, params: { token: access_token.token }
|
|
160
|
+
|
|
161
|
+
should_have_json 'active', true
|
|
162
|
+
expect(json_response).to include('client_id', 'token_type', 'exp', 'iat')
|
|
163
|
+
should_have_json 'client_id', client.uid
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
context 'public access token' do
|
|
168
|
+
let(:client) { FactoryBot.create(:application) }
|
|
169
|
+
let(:access_token) { FactoryBot.create(:access_token, application: nil) }
|
|
170
|
+
|
|
171
|
+
it 'responds with full token introspection' do
|
|
172
|
+
request.headers['Authorization'] = basic_auth_header_for_client(client)
|
|
173
|
+
|
|
174
|
+
post :introspect, params: { token: access_token.token }
|
|
175
|
+
|
|
176
|
+
should_have_json 'active', true
|
|
177
|
+
expect(json_response).to include('client_id', 'token_type', 'exp', 'iat')
|
|
178
|
+
should_have_json 'client_id', nil
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
context 'token was issued to a different client than is making this request' do
|
|
183
|
+
let(:client) { FactoryBot.create(:application) }
|
|
184
|
+
let(:different_client) { FactoryBot.create(:application) }
|
|
185
|
+
let(:access_token) { FactoryBot.create(:access_token, application: client) }
|
|
186
|
+
|
|
187
|
+
it 'responds with only active state' do
|
|
188
|
+
request.headers['Authorization'] = basic_auth_header_for_client(different_client)
|
|
189
|
+
|
|
190
|
+
post :introspect, params: { token: access_token.token }
|
|
191
|
+
|
|
192
|
+
expect(response).to be_successful
|
|
193
|
+
|
|
194
|
+
should_have_json 'active', false
|
|
195
|
+
expect(json_response).not_to include('client_id', 'token_type', 'exp', 'iat')
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
context 'using invalid credentials to authorize' do
|
|
200
|
+
let(:client) { double(uid: '123123', secret: '666999') }
|
|
201
|
+
let(:access_token) { FactoryBot.create(:access_token) }
|
|
202
|
+
|
|
203
|
+
it 'responds with invalid_client error' do
|
|
204
|
+
request.headers['Authorization'] = basic_auth_header_for_client(client)
|
|
205
|
+
|
|
206
|
+
post :introspect, params: { token: access_token.token }
|
|
207
|
+
|
|
208
|
+
expect(response).not_to be_successful
|
|
209
|
+
response_status_should_be 401
|
|
210
|
+
|
|
211
|
+
should_not_have_json 'active'
|
|
212
|
+
should_have_json 'error', 'invalid_client'
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
context 'using wrong token value' do
|
|
217
|
+
let(:client) { FactoryBot.create(:application) }
|
|
218
|
+
let(:access_token) { FactoryBot.create(:access_token, application: client) }
|
|
219
|
+
|
|
220
|
+
it 'responds with only active state' do
|
|
221
|
+
request.headers['Authorization'] = basic_auth_header_for_client(client)
|
|
222
|
+
|
|
223
|
+
post :introspect, params: { token: SecureRandom.hex(16) }
|
|
224
|
+
|
|
225
|
+
should_have_json 'active', false
|
|
226
|
+
expect(json_response).not_to include('client_id', 'token_type', 'exp', 'iat')
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
context 'when requested Access Token expired' do
|
|
231
|
+
let(:client) { FactoryBot.create(:application) }
|
|
232
|
+
let(:access_token) { FactoryBot.create(:access_token, application: client, created_at: 1.year.ago) }
|
|
233
|
+
|
|
234
|
+
it 'responds with only active state' do
|
|
235
|
+
request.headers['Authorization'] = basic_auth_header_for_client(client)
|
|
236
|
+
|
|
237
|
+
post :introspect, params: { token: access_token.token }
|
|
238
|
+
|
|
239
|
+
should_have_json 'active', false
|
|
240
|
+
expect(json_response).not_to include('client_id', 'token_type', 'exp', 'iat')
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
context 'when requested Access Token revoked' do
|
|
245
|
+
let(:client) { FactoryBot.create(:application) }
|
|
246
|
+
let(:access_token) { FactoryBot.create(:access_token, application: client, revoked_at: 1.year.ago) }
|
|
247
|
+
|
|
248
|
+
it 'responds with only active state' do
|
|
249
|
+
request.headers['Authorization'] = basic_auth_header_for_client(client)
|
|
250
|
+
|
|
251
|
+
post :introspect, params: { token: access_token.token }
|
|
252
|
+
|
|
253
|
+
should_have_json 'active', false
|
|
254
|
+
expect(json_response).not_to include('client_id', 'token_type', 'exp', 'iat')
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
context 'unauthorized (no bearer token or client credentials)' do
|
|
259
|
+
let(:access_token) { FactoryBot.create(:access_token) }
|
|
260
|
+
|
|
261
|
+
it 'responds with invalid_request error' do
|
|
262
|
+
post :introspect, params: { token: access_token.token }
|
|
263
|
+
|
|
264
|
+
expect(response).not_to be_successful
|
|
265
|
+
response_status_should_be 401
|
|
266
|
+
|
|
267
|
+
should_not_have_json 'active'
|
|
268
|
+
should_have_json 'error', 'invalid_request'
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
88
272
|
end
|
|
@@ -7,6 +7,10 @@ Dummy::Application.configure do
|
|
|
7
7
|
# and recreated between test runs. Don't rely on the data there!
|
|
8
8
|
config.cache_classes = true
|
|
9
9
|
|
|
10
|
+
config.assets.enabled = true
|
|
11
|
+
config.assets.version = '1.0'
|
|
12
|
+
config.assets.digest = false
|
|
13
|
+
|
|
10
14
|
# Do not eager load code on boot. This avoids loading your whole application
|
|
11
15
|
# just for the purpose of running a single test. If you are using a tool that
|
|
12
16
|
# preloads Rails for running tests, you may have to set it to true.
|
|
@@ -36,9 +40,4 @@ Dummy::Application.configure do
|
|
|
36
40
|
config.active_support.deprecation = :stderr
|
|
37
41
|
|
|
38
42
|
config.eager_load = true
|
|
39
|
-
|
|
40
|
-
if DOORKEEPER_ORM == :active_record
|
|
41
|
-
config.active_record.table_name_prefix = TABLE_NAME_PREFIX.to_s
|
|
42
|
-
config.active_record.table_name_suffix = TABLE_NAME_SUFFIX.to_s
|
|
43
|
-
end
|
|
44
43
|
end
|
|
@@ -8,7 +8,11 @@ Doorkeeper.configure do
|
|
|
8
8
|
User.where(id: session[:user_id]).first || redirect_to(root_url, alert: 'Needs sign in.')
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
# If you
|
|
11
|
+
# If you didn't skip applications controller from Doorkeeper routes in your application routes.rb
|
|
12
|
+
# file then you need to declare this block in order to restrict access to the web interface for
|
|
13
|
+
# adding oauth authorized applications. In other case it will return 403 Forbidden response
|
|
14
|
+
# every time somebody will try to access the admin web interface.
|
|
15
|
+
#
|
|
12
16
|
# admin_authenticator do
|
|
13
17
|
# # Put your admin authentication logic here.
|
|
14
18
|
# # Example implementation:
|
|
@@ -29,6 +33,12 @@ Doorkeeper.configure do
|
|
|
29
33
|
# Issue access tokens with refresh token (disabled by default)
|
|
30
34
|
use_refresh_token
|
|
31
35
|
|
|
36
|
+
# Forbids creating/updating applications with arbitrary scopes that are
|
|
37
|
+
# not in configuration, i.e. `default_scopes` or `optional_scopes`.
|
|
38
|
+
# (disabled by default)
|
|
39
|
+
#
|
|
40
|
+
# enforce_configured_scopes
|
|
41
|
+
|
|
32
42
|
# Provide support for an owner to be assigned to each registered application (disabled by default)
|
|
33
43
|
# Optional parameter confirmation: true (default false) if you want to enforce ownership of
|
|
34
44
|
# a registered application
|
|
@@ -82,7 +92,18 @@ Doorkeeper.configure do
|
|
|
82
92
|
# http://tools.ietf.org/html/rfc6819#section-4.4.2
|
|
83
93
|
# http://tools.ietf.org/html/rfc6819#section-4.4.3
|
|
84
94
|
#
|
|
85
|
-
# grant_flows %w
|
|
95
|
+
# grant_flows %w[authorization_code client_credentials]
|
|
96
|
+
|
|
97
|
+
# Hook into the strategies' request & response life-cycle in case your
|
|
98
|
+
# application needs advanced customization or logging:
|
|
99
|
+
#
|
|
100
|
+
# before_successful_strategy_response do |request|
|
|
101
|
+
# puts "BEFORE HOOK FIRED! #{request}"
|
|
102
|
+
# end
|
|
103
|
+
#
|
|
104
|
+
# after_successful_strategy_response do |request, response|
|
|
105
|
+
# puts "AFTER HOOK FIRED! #{request}, #{response}"
|
|
106
|
+
# end
|
|
86
107
|
|
|
87
108
|
# Under some circumstances you might want to have applications auto-approved,
|
|
88
109
|
# so that the user skips the authorization step.
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Require `belongs_to` associations by default. This is a new Rails 5.0
|
|
2
2
|
# default, so it is introduced as a configuration option to ensure that apps
|
|
3
3
|
# made on earlier versions of Rails are not affected when upgrading.
|
|
4
|
-
if Rails
|
|
4
|
+
if Rails::VERSION::MAJOR >= 5
|
|
5
5
|
Rails.application.config.active_record.belongs_to_required_by_default = true
|
|
6
|
+
|
|
7
|
+
if Rails::VERSION::MINOR >= 2
|
|
8
|
+
Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
|
|
9
|
+
end
|
|
6
10
|
end
|
|
@@ -5,5 +5,4 @@
|
|
|
5
5
|
# Make sure the secret is at least 30 characters and all random,
|
|
6
6
|
# no regular words or you'll be exposed to dictionary attacks.
|
|
7
7
|
Dummy::Application.config.secret_key_base =
|
|
8
|
-
Dummy::Application.config.secret_token =
|
|
9
8
|
'c00157b5a1bb6181792f0f4a8a080485de7bab9987e6cf159dc74c4f0573345c1bfa713b5d756e1491fc0b098567e8a619e2f8d268eda86a20a720d05d633780'
|