doorkeeper 5.0.3 → 5.1.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/.hound.yml +2 -1
- data/.rubocop.yml +37 -4
- data/.travis.yml +11 -21
- data/Appraisals +29 -7
- data/Dangerfile +5 -2
- data/Gemfile +19 -5
- data/NEWS.md +81 -21
- data/README.md +75 -485
- data/Rakefile +10 -8
- data/app/controllers/doorkeeper/application_controller.rb +1 -2
- data/app/controllers/doorkeeper/application_metal_controller.rb +2 -13
- data/app/controllers/doorkeeper/applications_controller.rb +20 -8
- data/app/controllers/doorkeeper/authorized_applications_controller.rb +1 -1
- data/app/controllers/doorkeeper/token_info_controller.rb +2 -2
- data/app/controllers/doorkeeper/tokens_controller.rb +11 -11
- data/app/helpers/doorkeeper/dashboard_helper.rb +1 -1
- data/app/validators/redirect_uri_validator.rb +5 -2
- data/app/views/doorkeeper/applications/_form.html.erb +6 -0
- data/app/views/doorkeeper/applications/show.html.erb +1 -1
- data/app/views/layouts/doorkeeper/admin.html.erb +5 -3
- data/bin/console +16 -0
- data/config/locales/en.yml +1 -0
- data/doorkeeper.gemspec +24 -21
- data/gemfiles/rails_5_0.gemfile +9 -4
- data/gemfiles/rails_5_1.gemfile +9 -4
- data/gemfiles/rails_5_2.gemfile +10 -5
- data/gemfiles/rails_6_0.gemfile +17 -0
- data/gemfiles/rails_master.gemfile +9 -9
- data/lib/doorkeeper/config/option.rb +64 -0
- data/lib/doorkeeper/config.rb +191 -75
- data/lib/doorkeeper/engine.rb +1 -1
- data/lib/doorkeeper/grape/authorization_decorator.rb +4 -4
- data/lib/doorkeeper/grape/helpers.rb +3 -3
- data/lib/doorkeeper/helpers/controller.rb +3 -2
- data/lib/doorkeeper/models/access_grant_mixin.rb +20 -3
- data/lib/doorkeeper/models/access_token_mixin.rb +63 -19
- data/lib/doorkeeper/models/application_mixin.rb +43 -1
- data/lib/doorkeeper/models/concerns/expirable.rb +4 -2
- data/lib/doorkeeper/models/concerns/ownership.rb +1 -6
- data/lib/doorkeeper/models/concerns/reusable.rb +19 -0
- data/lib/doorkeeper/models/concerns/revocable.rb +2 -1
- data/lib/doorkeeper/models/concerns/scopes.rb +5 -1
- data/lib/doorkeeper/models/concerns/secret_storable.rb +108 -0
- data/lib/doorkeeper/oauth/authorization/code.rb +2 -2
- data/lib/doorkeeper/oauth/authorization/token.rb +12 -7
- data/lib/doorkeeper/oauth/authorization/uri_builder.rb +1 -1
- data/lib/doorkeeper/oauth/authorization_code_request.rb +6 -4
- data/lib/doorkeeper/oauth/client.rb +1 -1
- data/lib/doorkeeper/oauth/client_credentials/validation.rb +5 -4
- data/lib/doorkeeper/oauth/client_credentials_request.rb +1 -1
- data/lib/doorkeeper/oauth/code_response.rb +2 -2
- data/lib/doorkeeper/oauth/error_response.rb +10 -6
- data/lib/doorkeeper/oauth/forbidden_token_response.rb +1 -1
- data/lib/doorkeeper/oauth/helpers/scope_checker.rb +23 -8
- data/lib/doorkeeper/oauth/helpers/unique_token.rb +13 -1
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +37 -1
- data/lib/doorkeeper/oauth/invalid_token_response.rb +5 -1
- data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -2
- data/lib/doorkeeper/oauth/pre_authorization.rb +11 -5
- data/lib/doorkeeper/oauth/refresh_token_request.rb +5 -2
- data/lib/doorkeeper/oauth/scopes.rb +5 -3
- data/lib/doorkeeper/oauth/token.rb +2 -2
- data/lib/doorkeeper/oauth/token_introspection.rb +74 -8
- data/lib/doorkeeper/oauth/token_response.rb +9 -9
- data/lib/doorkeeper/oauth.rb +5 -5
- data/lib/doorkeeper/orm/active_record/access_grant.rb +27 -13
- data/lib/doorkeeper/orm/active_record/access_token.rb +6 -13
- data/lib/doorkeeper/orm/active_record/application.rb +21 -57
- data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +10 -3
- data/lib/doorkeeper/orm/active_record.rb +6 -6
- data/lib/doorkeeper/rails/helpers.rb +1 -1
- data/lib/doorkeeper/rails/routes/mapping.rb +7 -7
- data/lib/doorkeeper/rails/routes.rb +11 -11
- data/lib/doorkeeper/rake/db.rake +13 -13
- data/lib/doorkeeper/rake.rb +1 -1
- data/lib/doorkeeper/request.rb +1 -1
- data/lib/doorkeeper/secret_storing/base.rb +64 -0
- data/lib/doorkeeper/secret_storing/bcrypt.rb +60 -0
- data/lib/doorkeeper/secret_storing/plain.rb +33 -0
- data/lib/doorkeeper/secret_storing/sha256_hash.rb +26 -0
- data/lib/doorkeeper/stale_records_cleaner.rb +1 -1
- data/lib/doorkeeper/version.rb +3 -3
- data/lib/doorkeeper.rb +69 -60
- data/lib/generators/doorkeeper/application_owner_generator.rb +10 -9
- data/lib/generators/doorkeeper/confidential_applications_generator.rb +10 -9
- data/lib/generators/doorkeeper/install_generator.rb +11 -9
- data/lib/generators/doorkeeper/migration_generator.rb +9 -9
- data/lib/generators/doorkeeper/pkce_generator.rb +10 -9
- data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +10 -9
- data/lib/generators/doorkeeper/templates/initializer.rb +120 -13
- data/lib/generators/doorkeeper/templates/migration.rb.erb +15 -7
- data/lib/generators/doorkeeper/views_generator.rb +6 -4
- data/spec/controllers/application_metal_controller_spec.rb +24 -10
- data/spec/controllers/applications_controller_spec.rb +54 -52
- data/spec/controllers/authorizations_controller_spec.rb +139 -145
- data/spec/controllers/protected_resources_controller_spec.rb +78 -76
- data/spec/controllers/token_info_controller_spec.rb +14 -12
- data/spec/controllers/tokens_controller_spec.rb +167 -108
- data/spec/dummy/Rakefile +3 -1
- data/spec/dummy/app/controllers/application_controller.rb +3 -1
- data/spec/dummy/app/controllers/custom_authorizations_controller.rb +2 -0
- data/spec/dummy/app/controllers/full_protected_resources_controller.rb +4 -2
- data/spec/dummy/app/controllers/home_controller.rb +5 -3
- data/spec/dummy/app/controllers/metal_controller.rb +2 -0
- data/spec/dummy/app/controllers/semi_protected_resources_controller.rb +4 -2
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/user.rb +2 -0
- data/spec/dummy/config/application.rb +25 -1
- data/spec/dummy/config/environments/development.rb +2 -0
- data/spec/dummy/config/environments/production.rb +2 -0
- data/spec/dummy/config/environments/test.rb +3 -1
- data/spec/dummy/config/initializers/backtrace_silencers.rb +2 -0
- data/spec/dummy/config/initializers/doorkeeper.rb +5 -2
- data/spec/dummy/config/initializers/secret_token.rb +3 -1
- data/spec/dummy/config/initializers/session_store.rb +3 -1
- data/spec/dummy/config/initializers/wrap_parameters.rb +2 -0
- data/spec/dummy/config.ru +3 -1
- data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +17 -10
- data/spec/dummy/db/migrate/20170822064514_enable_pkce.rb +2 -0
- data/spec/dummy/db/schema.rb +1 -1
- data/spec/dummy/script/rails +5 -3
- data/spec/factories.rb +8 -6
- data/spec/generators/application_owner_generator_spec.rb +13 -26
- data/spec/generators/confidential_applications_generator_spec.rb +12 -28
- data/spec/generators/install_generator_spec.rb +17 -15
- data/spec/generators/migration_generator_spec.rb +13 -26
- data/spec/generators/pkce_generator_spec.rb +11 -26
- data/spec/generators/previous_refresh_token_generator_spec.rb +16 -29
- data/spec/generators/templates/routes.rb +2 -0
- data/spec/generators/views_generator_spec.rb +14 -12
- data/spec/grape/grape_integration_spec.rb +34 -32
- data/spec/helpers/doorkeeper/dashboard_helper_spec.rb +9 -7
- data/spec/lib/config_spec.rb +268 -99
- data/spec/lib/doorkeeper_spec.rb +3 -1
- data/spec/lib/models/expirable_spec.rb +22 -8
- data/spec/lib/models/reusable_spec.rb +40 -0
- data/spec/lib/models/revocable_spec.rb +8 -6
- data/spec/lib/models/scopes_spec.rb +27 -13
- data/spec/lib/models/secret_storable_spec.rb +135 -0
- data/spec/lib/oauth/authorization/uri_builder_spec.rb +17 -15
- data/spec/lib/oauth/authorization_code_request_spec.rb +34 -11
- data/spec/lib/oauth/base_request_spec.rb +20 -8
- data/spec/lib/oauth/base_response_spec.rb +3 -1
- data/spec/lib/oauth/client/credentials_spec.rb +24 -22
- data/spec/lib/oauth/client_credentials/creator_spec.rb +56 -10
- data/spec/lib/oauth/client_credentials/issuer_spec.rb +27 -18
- data/spec/lib/oauth/client_credentials/validation_spec.rb +20 -15
- data/spec/lib/oauth/client_credentials_integration_spec.rb +7 -5
- data/spec/lib/oauth/client_credentials_request_spec.rb +27 -21
- data/spec/lib/oauth/client_spec.rb +15 -13
- data/spec/lib/oauth/code_request_spec.rb +8 -6
- data/spec/lib/oauth/code_response_spec.rb +9 -7
- data/spec/lib/oauth/error_response_spec.rb +19 -11
- data/spec/lib/oauth/error_spec.rb +4 -2
- data/spec/lib/oauth/forbidden_token_response_spec.rb +7 -5
- data/spec/lib/oauth/helpers/scope_checker_spec.rb +68 -31
- data/spec/lib/oauth/helpers/unique_token_spec.rb +8 -6
- data/spec/lib/oauth/helpers/uri_checker_spec.rb +115 -95
- data/spec/lib/oauth/invalid_token_response_spec.rb +3 -1
- data/spec/lib/oauth/password_access_token_request_spec.rb +79 -30
- data/spec/lib/oauth/pre_authorization_spec.rb +80 -54
- data/spec/lib/oauth/refresh_token_request_spec.rb +36 -33
- data/spec/lib/oauth/scopes_spec.rb +63 -61
- data/spec/lib/oauth/token_request_spec.rb +79 -24
- data/spec/lib/oauth/token_response_spec.rb +40 -38
- data/spec/lib/oauth/token_spec.rb +60 -44
- data/spec/lib/request/strategy_spec.rb +3 -1
- data/spec/lib/secret_storing/base_spec.rb +60 -0
- data/spec/lib/secret_storing/bcrypt_spec.rb +49 -0
- data/spec/lib/secret_storing/plain_spec.rb +44 -0
- data/spec/lib/secret_storing/sha256_hash_spec.rb +48 -0
- data/spec/lib/server_spec.rb +16 -14
- data/spec/lib/stale_records_cleaner_spec.rb +17 -17
- data/spec/models/doorkeeper/access_grant_spec.rb +80 -15
- data/spec/models/doorkeeper/access_token_spec.rb +198 -73
- data/spec/models/doorkeeper/application_spec.rb +280 -288
- data/spec/requests/applications/applications_request_spec.rb +98 -66
- data/spec/requests/applications/authorized_applications_spec.rb +20 -18
- data/spec/requests/endpoints/authorization_spec.rb +25 -23
- data/spec/requests/endpoints/token_spec.rb +38 -36
- data/spec/requests/flows/authorization_code_errors_spec.rb +26 -24
- data/spec/requests/flows/authorization_code_spec.rb +193 -151
- data/spec/requests/flows/client_credentials_spec.rb +54 -52
- data/spec/requests/flows/implicit_grant_errors_spec.rb +10 -8
- data/spec/requests/flows/implicit_grant_spec.rb +27 -25
- data/spec/requests/flows/password_spec.rb +59 -55
- data/spec/requests/flows/refresh_token_spec.rb +45 -43
- data/spec/requests/flows/revoke_token_spec.rb +40 -54
- data/spec/requests/flows/skip_authorization_spec.rb +23 -21
- data/spec/requests/protected_resources/metal_spec.rb +7 -5
- data/spec/requests/protected_resources/private_api_spec.rb +35 -33
- data/spec/routing/custom_controller_routes_spec.rb +71 -65
- data/spec/routing/default_routes_spec.rb +22 -20
- data/spec/routing/scoped_routes_spec.rb +20 -18
- data/spec/spec_helper.rb +15 -13
- data/spec/spec_helper_integration.rb +3 -1
- data/spec/support/dependencies/factory_bot.rb +3 -1
- data/spec/support/doorkeeper_rspec.rb +3 -1
- data/spec/support/helpers/access_token_request_helper.rb +3 -1
- data/spec/support/helpers/authorization_request_helper.rb +4 -2
- data/spec/support/helpers/config_helper.rb +2 -0
- data/spec/support/helpers/model_helper.rb +3 -1
- data/spec/support/helpers/request_spec_helper.rb +5 -3
- data/spec/support/helpers/url_helper.rb +9 -7
- data/spec/support/http_method_shim.rb +4 -9
- data/spec/support/orm/active_record.rb +3 -1
- data/spec/support/shared/controllers_shared_context.rb +18 -16
- data/spec/support/shared/hashing_shared_context.rb +36 -0
- data/spec/support/shared/models_shared_examples.rb +12 -10
- data/spec/validators/redirect_uri_validator_spec.rb +74 -45
- data/spec/version/version_spec.rb +7 -5
- metadata +60 -26
- data/gemfiles/rails_4_2.gemfile +0 -13
- data/spec/dummy/config/initializers/new_framework_defaults.rb +0 -8
|
@@ -1,66 +1,95 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
feature "Authorization Code Flow" do
|
|
4
6
|
background do
|
|
5
|
-
config_is_set(:authenticate_resource_owner) { User.first || redirect_to(
|
|
7
|
+
config_is_set(:authenticate_resource_owner) { User.first || redirect_to("/sign_in") }
|
|
6
8
|
client_exists
|
|
7
9
|
create_resource_owner
|
|
8
10
|
sign_in
|
|
9
11
|
end
|
|
10
12
|
|
|
11
|
-
scenario
|
|
13
|
+
scenario "resource owner authorizes the client" do
|
|
12
14
|
visit authorization_endpoint_url(client: @client)
|
|
13
|
-
click_on
|
|
15
|
+
click_on "Authorize"
|
|
14
16
|
|
|
15
17
|
access_grant_should_exist_for(@client, @resource_owner)
|
|
16
18
|
|
|
17
19
|
i_should_be_on_client_callback(@client)
|
|
18
20
|
|
|
19
|
-
url_should_have_param(
|
|
20
|
-
url_should_not_have_param(
|
|
21
|
-
url_should_not_have_param(
|
|
21
|
+
url_should_have_param("code", Doorkeeper::AccessGrant.first.token)
|
|
22
|
+
url_should_not_have_param("state")
|
|
23
|
+
url_should_not_have_param("error")
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
context "with grant hashing enabled" do
|
|
27
|
+
background do
|
|
28
|
+
config_is_set(:token_secret_strategy, ::Doorkeeper::SecretStoring::Sha256Hash)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
scenario "Authorization Code Flow with hashing" do
|
|
32
|
+
@client.redirect_uri = Doorkeeper.configuration.native_redirect_uri
|
|
33
|
+
@client.save!
|
|
34
|
+
visit authorization_endpoint_url(client: @client)
|
|
35
|
+
click_on "Authorize"
|
|
36
|
+
|
|
37
|
+
access_grant_should_exist_for(@client, @resource_owner)
|
|
38
|
+
|
|
39
|
+
code = current_params["code"]
|
|
40
|
+
expect(code).not_to be_nil
|
|
41
|
+
|
|
42
|
+
hashed_code = Doorkeeper::AccessGrant.secret_strategy.transform_secret code
|
|
43
|
+
expect(hashed_code).to eq Doorkeeper::AccessGrant.first.token
|
|
44
|
+
|
|
45
|
+
expect(code).not_to eq(hashed_code)
|
|
46
|
+
|
|
47
|
+
i_should_see "Authorization code:"
|
|
48
|
+
i_should_see code
|
|
49
|
+
i_should_not_see hashed_code
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
scenario "resource owner authorizes using test url" do
|
|
25
54
|
@client.redirect_uri = Doorkeeper.configuration.native_redirect_uri
|
|
26
55
|
@client.save!
|
|
27
56
|
visit authorization_endpoint_url(client: @client)
|
|
28
|
-
click_on
|
|
57
|
+
click_on "Authorize"
|
|
29
58
|
|
|
30
59
|
access_grant_should_exist_for(@client, @resource_owner)
|
|
31
60
|
|
|
32
|
-
url_should_have_param(
|
|
33
|
-
i_should_see
|
|
61
|
+
url_should_have_param("code", Doorkeeper::AccessGrant.first.token)
|
|
62
|
+
i_should_see "Authorization code:"
|
|
34
63
|
i_should_see Doorkeeper::AccessGrant.first.token
|
|
35
64
|
end
|
|
36
65
|
|
|
37
|
-
scenario
|
|
38
|
-
visit authorization_endpoint_url(client: @client, state:
|
|
39
|
-
click_on
|
|
40
|
-
url_should_have_param(
|
|
41
|
-
url_should_have_param(
|
|
42
|
-
url_should_not_have_param(
|
|
66
|
+
scenario "resource owner authorizes the client with state parameter set" do
|
|
67
|
+
visit authorization_endpoint_url(client: @client, state: "return-me")
|
|
68
|
+
click_on "Authorize"
|
|
69
|
+
url_should_have_param("code", Doorkeeper::AccessGrant.first.token)
|
|
70
|
+
url_should_have_param("state", "return-me")
|
|
71
|
+
url_should_not_have_param("code_challenge_method")
|
|
43
72
|
end
|
|
44
73
|
|
|
45
|
-
scenario
|
|
74
|
+
scenario "resource owner requests an access token with authorization code" do
|
|
46
75
|
visit authorization_endpoint_url(client: @client)
|
|
47
|
-
click_on
|
|
76
|
+
click_on "Authorize"
|
|
48
77
|
|
|
49
78
|
authorization_code = Doorkeeper::AccessGrant.first.token
|
|
50
79
|
create_access_token authorization_code, @client
|
|
51
80
|
|
|
52
81
|
access_token_should_exist_for(@client, @resource_owner)
|
|
53
82
|
|
|
54
|
-
should_not_have_json
|
|
83
|
+
should_not_have_json "error"
|
|
55
84
|
|
|
56
|
-
should_have_json
|
|
57
|
-
should_have_json
|
|
58
|
-
should_have_json_within
|
|
85
|
+
should_have_json "access_token", Doorkeeper::AccessToken.first.token
|
|
86
|
+
should_have_json "token_type", "Bearer"
|
|
87
|
+
should_have_json_within "expires_in", Doorkeeper::AccessToken.first.expires_in, 1
|
|
59
88
|
end
|
|
60
89
|
|
|
61
|
-
scenario
|
|
90
|
+
scenario "resource owner requests an access token with authorization code but without secret" do
|
|
62
91
|
visit authorization_endpoint_url(client: @client)
|
|
63
|
-
click_on
|
|
92
|
+
click_on "Authorize"
|
|
64
93
|
|
|
65
94
|
authorization_code = Doorkeeper::AccessGrant.first.token
|
|
66
95
|
page.driver.post token_endpoint_url(code: authorization_code, client_id: @client.uid,
|
|
@@ -68,277 +97,290 @@ feature 'Authorization Code Flow' do
|
|
|
68
97
|
|
|
69
98
|
expect(Doorkeeper::AccessToken.count).to be_zero
|
|
70
99
|
|
|
71
|
-
should_have_json
|
|
100
|
+
should_have_json "error", "invalid_client"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
scenario "resource owner requests an access token with authorization code but without client id" do
|
|
104
|
+
visit authorization_endpoint_url(client: @client)
|
|
105
|
+
click_on "Authorize"
|
|
106
|
+
|
|
107
|
+
authorization_code = Doorkeeper::AccessGrant.first.token
|
|
108
|
+
page.driver.post token_endpoint_url(code: authorization_code, client_secret: @client.secret,
|
|
109
|
+
redirect_uri: @client.redirect_uri)
|
|
110
|
+
|
|
111
|
+
expect(Doorkeeper::AccessToken.count).to be_zero
|
|
112
|
+
|
|
113
|
+
should_have_json "error", "invalid_client"
|
|
72
114
|
end
|
|
73
115
|
|
|
74
|
-
scenario
|
|
116
|
+
scenario "silently authorizes if matching token exists" do
|
|
75
117
|
default_scopes_exist :public, :write
|
|
76
118
|
|
|
77
119
|
access_token_exists application: @client,
|
|
78
120
|
expires_in: -100, # even expired token
|
|
79
121
|
resource_owner_id: @resource_owner.id,
|
|
80
|
-
scopes:
|
|
122
|
+
scopes: "public write"
|
|
81
123
|
|
|
82
|
-
visit authorization_endpoint_url(client: @client, scope:
|
|
124
|
+
visit authorization_endpoint_url(client: @client, scope: "public write")
|
|
83
125
|
|
|
84
126
|
response_status_should_be 200
|
|
85
|
-
i_should_not_see
|
|
127
|
+
i_should_not_see "Authorize"
|
|
86
128
|
end
|
|
87
129
|
|
|
88
|
-
context
|
|
89
|
-
context
|
|
90
|
-
let(:code_challenge) {
|
|
91
|
-
let(:code_verifier) {
|
|
130
|
+
context "with PKCE" do
|
|
131
|
+
context "plain" do
|
|
132
|
+
let(:code_challenge) { "a45a9fea-0676-477e-95b1-a40f72ac3cfb" }
|
|
133
|
+
let(:code_verifier) { "a45a9fea-0676-477e-95b1-a40f72ac3cfb" }
|
|
92
134
|
|
|
93
|
-
scenario
|
|
135
|
+
scenario "resource owner authorizes the client with code_challenge parameter set" do
|
|
94
136
|
visit authorization_endpoint_url(
|
|
95
137
|
client: @client,
|
|
96
138
|
code_challenge: code_challenge,
|
|
97
|
-
code_challenge_method:
|
|
139
|
+
code_challenge_method: "plain"
|
|
98
140
|
)
|
|
99
|
-
click_on
|
|
141
|
+
click_on "Authorize"
|
|
100
142
|
|
|
101
|
-
url_should_have_param(
|
|
102
|
-
url_should_not_have_param(
|
|
103
|
-
url_should_not_have_param(
|
|
143
|
+
url_should_have_param("code", Doorkeeper::AccessGrant.first.token)
|
|
144
|
+
url_should_not_have_param("code_challenge_method")
|
|
145
|
+
url_should_not_have_param("code_challenge")
|
|
104
146
|
end
|
|
105
147
|
|
|
106
|
-
scenario
|
|
148
|
+
scenario "mobile app requests an access token with authorization code but not pkce token" do
|
|
107
149
|
visit authorization_endpoint_url(client: @client)
|
|
108
|
-
click_on
|
|
150
|
+
click_on "Authorize"
|
|
109
151
|
|
|
110
|
-
authorization_code = current_params[
|
|
152
|
+
authorization_code = current_params["code"]
|
|
111
153
|
create_access_token authorization_code, @client, code_verifier
|
|
112
154
|
|
|
113
|
-
should_have_json
|
|
155
|
+
should_have_json "error", "invalid_grant"
|
|
114
156
|
end
|
|
115
157
|
|
|
116
|
-
scenario
|
|
158
|
+
scenario "mobile app requests an access token with authorization code and plain code challenge method" do
|
|
117
159
|
visit authorization_endpoint_url(
|
|
118
160
|
client: @client,
|
|
119
161
|
code_challenge: code_challenge,
|
|
120
|
-
code_challenge_method:
|
|
162
|
+
code_challenge_method: "plain"
|
|
121
163
|
)
|
|
122
|
-
click_on
|
|
164
|
+
click_on "Authorize"
|
|
123
165
|
|
|
124
|
-
authorization_code = current_params[
|
|
166
|
+
authorization_code = current_params["code"]
|
|
125
167
|
create_access_token authorization_code, @client, code_verifier
|
|
126
168
|
|
|
127
169
|
access_token_should_exist_for(@client, @resource_owner)
|
|
128
170
|
|
|
129
|
-
should_not_have_json
|
|
171
|
+
should_not_have_json "error"
|
|
130
172
|
|
|
131
|
-
should_have_json
|
|
132
|
-
should_have_json
|
|
133
|
-
should_have_json_within
|
|
173
|
+
should_have_json "access_token", Doorkeeper::AccessToken.first.token
|
|
174
|
+
should_have_json "token_type", "Bearer"
|
|
175
|
+
should_have_json_within "expires_in", Doorkeeper::AccessToken.first.expires_in, 1
|
|
134
176
|
end
|
|
135
177
|
|
|
136
|
-
scenario
|
|
178
|
+
scenario "mobile app requests an access token with authorization code and code_challenge" do
|
|
137
179
|
visit authorization_endpoint_url(client: @client,
|
|
138
180
|
code_challenge: code_verifier,
|
|
139
|
-
code_challenge_method:
|
|
140
|
-
click_on
|
|
181
|
+
code_challenge_method: "plain")
|
|
182
|
+
click_on "Authorize"
|
|
141
183
|
|
|
142
|
-
authorization_code = current_params[
|
|
184
|
+
authorization_code = current_params["code"]
|
|
143
185
|
create_access_token authorization_code, @client, code_verifier: nil
|
|
144
186
|
|
|
145
|
-
should_not_have_json
|
|
146
|
-
should_have_json
|
|
187
|
+
should_not_have_json "access_token"
|
|
188
|
+
should_have_json "error", "invalid_grant"
|
|
147
189
|
end
|
|
148
190
|
end
|
|
149
191
|
|
|
150
|
-
context
|
|
151
|
-
let(:code_challenge) {
|
|
152
|
-
let(:code_verifier) {
|
|
192
|
+
context "s256" do
|
|
193
|
+
let(:code_challenge) { "Oz733NtQ0rJP8b04fgZMJMwprn6Iw8sMCT_9bR1q4tA" }
|
|
194
|
+
let(:code_verifier) { "a45a9fea-0676-477e-95b1-a40f72ac3cfb" }
|
|
153
195
|
|
|
154
|
-
scenario
|
|
196
|
+
scenario "resource owner authorizes the client with code_challenge parameter set" do
|
|
155
197
|
visit authorization_endpoint_url(
|
|
156
198
|
client: @client,
|
|
157
199
|
code_challenge: code_challenge,
|
|
158
|
-
code_challenge_method:
|
|
200
|
+
code_challenge_method: "S256"
|
|
159
201
|
)
|
|
160
|
-
click_on
|
|
202
|
+
click_on "Authorize"
|
|
161
203
|
|
|
162
|
-
url_should_have_param(
|
|
163
|
-
url_should_not_have_param(
|
|
164
|
-
url_should_not_have_param(
|
|
204
|
+
url_should_have_param("code", Doorkeeper::AccessGrant.first.token)
|
|
205
|
+
url_should_not_have_param("code_challenge_method")
|
|
206
|
+
url_should_not_have_param("code_challenge")
|
|
165
207
|
end
|
|
166
208
|
|
|
167
|
-
scenario
|
|
209
|
+
scenario "mobile app requests an access token with authorization code and S256 code challenge method" do
|
|
168
210
|
visit authorization_endpoint_url(
|
|
169
211
|
client: @client,
|
|
170
212
|
code_challenge: code_challenge,
|
|
171
|
-
code_challenge_method:
|
|
213
|
+
code_challenge_method: "S256"
|
|
172
214
|
)
|
|
173
|
-
click_on
|
|
215
|
+
click_on "Authorize"
|
|
174
216
|
|
|
175
|
-
authorization_code = current_params[
|
|
217
|
+
authorization_code = current_params["code"]
|
|
176
218
|
create_access_token authorization_code, @client, code_verifier
|
|
177
219
|
|
|
178
220
|
access_token_should_exist_for(@client, @resource_owner)
|
|
179
221
|
|
|
180
|
-
should_not_have_json
|
|
222
|
+
should_not_have_json "error"
|
|
181
223
|
|
|
182
|
-
should_have_json
|
|
183
|
-
should_have_json
|
|
184
|
-
should_have_json_within
|
|
224
|
+
should_have_json "access_token", Doorkeeper::AccessToken.first.token
|
|
225
|
+
should_have_json "token_type", "Bearer"
|
|
226
|
+
should_have_json_within "expires_in", Doorkeeper::AccessToken.first.expires_in, 1
|
|
185
227
|
end
|
|
186
228
|
|
|
187
|
-
scenario
|
|
229
|
+
scenario "mobile app requests an access token with authorization code and without code_verifier" do
|
|
188
230
|
visit authorization_endpoint_url(
|
|
189
231
|
client: @client,
|
|
190
232
|
code_challenge: code_challenge,
|
|
191
|
-
code_challenge_method:
|
|
233
|
+
code_challenge_method: "S256"
|
|
192
234
|
)
|
|
193
|
-
click_on
|
|
194
|
-
authorization_code = current_params[
|
|
235
|
+
click_on "Authorize"
|
|
236
|
+
authorization_code = current_params["code"]
|
|
195
237
|
create_access_token authorization_code, @client
|
|
196
|
-
should_have_json
|
|
197
|
-
should_not_have_json
|
|
238
|
+
should_have_json "error", "invalid_request"
|
|
239
|
+
should_not_have_json "access_token"
|
|
198
240
|
end
|
|
199
241
|
|
|
200
|
-
scenario
|
|
242
|
+
scenario "mobile app requests an access token with authorization code and without secret" do
|
|
201
243
|
visit authorization_endpoint_url(
|
|
202
244
|
client: @client,
|
|
203
245
|
code_challenge: code_challenge,
|
|
204
|
-
code_challenge_method:
|
|
246
|
+
code_challenge_method: "S256"
|
|
205
247
|
)
|
|
206
|
-
click_on
|
|
248
|
+
click_on "Authorize"
|
|
207
249
|
|
|
208
|
-
authorization_code = current_params[
|
|
250
|
+
authorization_code = current_params["code"]
|
|
209
251
|
page.driver.post token_endpoint_url(code: authorization_code, client_id: @client.uid,
|
|
210
252
|
redirect_uri: @client.redirect_uri, code_verifier: code_verifier)
|
|
211
|
-
should_have_json
|
|
212
|
-
should_not_have_json
|
|
253
|
+
should_have_json "error", "invalid_client"
|
|
254
|
+
should_not_have_json "access_token"
|
|
213
255
|
end
|
|
214
256
|
|
|
215
|
-
scenario
|
|
257
|
+
scenario "mobile app requests an access token with authorization code and without secret but is marked as not confidential" do
|
|
216
258
|
@client.update_attribute :confidential, false
|
|
217
|
-
visit authorization_endpoint_url(client: @client, code_challenge: code_challenge, code_challenge_method:
|
|
218
|
-
click_on
|
|
259
|
+
visit authorization_endpoint_url(client: @client, code_challenge: code_challenge, code_challenge_method: "S256")
|
|
260
|
+
click_on "Authorize"
|
|
219
261
|
|
|
220
|
-
authorization_code = current_params[
|
|
262
|
+
authorization_code = current_params["code"]
|
|
221
263
|
page.driver.post token_endpoint_url(
|
|
222
264
|
code: authorization_code,
|
|
223
265
|
client_id: @client.uid,
|
|
224
266
|
redirect_uri: @client.redirect_uri,
|
|
225
267
|
code_verifier: code_verifier
|
|
226
268
|
)
|
|
227
|
-
should_not_have_json
|
|
269
|
+
should_not_have_json "error"
|
|
228
270
|
|
|
229
|
-
should_have_json
|
|
230
|
-
should_have_json
|
|
231
|
-
should_have_json_within
|
|
271
|
+
should_have_json "access_token", Doorkeeper::AccessToken.first.token
|
|
272
|
+
should_have_json "token_type", "Bearer"
|
|
273
|
+
should_have_json_within "expires_in", Doorkeeper::AccessToken.first.expires_in, 1
|
|
232
274
|
end
|
|
233
275
|
|
|
234
|
-
scenario
|
|
276
|
+
scenario "mobile app requests an access token with authorization code but no code verifier" do
|
|
235
277
|
visit authorization_endpoint_url(
|
|
236
278
|
client: @client,
|
|
237
279
|
code_challenge: code_challenge,
|
|
238
|
-
code_challenge_method:
|
|
280
|
+
code_challenge_method: "S256"
|
|
239
281
|
)
|
|
240
|
-
click_on
|
|
282
|
+
click_on "Authorize"
|
|
241
283
|
|
|
242
|
-
authorization_code = current_params[
|
|
284
|
+
authorization_code = current_params["code"]
|
|
243
285
|
create_access_token authorization_code, @client
|
|
244
286
|
|
|
245
|
-
should_not_have_json
|
|
246
|
-
should_have_json
|
|
287
|
+
should_not_have_json "access_token"
|
|
288
|
+
should_have_json "error", "invalid_request"
|
|
247
289
|
end
|
|
248
290
|
|
|
249
|
-
scenario
|
|
291
|
+
scenario "mobile app requests an access token with authorization code with wrong verifier" do
|
|
250
292
|
visit authorization_endpoint_url(
|
|
251
293
|
client: @client,
|
|
252
294
|
code_challenge: code_challenge,
|
|
253
|
-
code_challenge_method:
|
|
295
|
+
code_challenge_method: "S256"
|
|
254
296
|
)
|
|
255
|
-
click_on
|
|
297
|
+
click_on "Authorize"
|
|
256
298
|
|
|
257
|
-
authorization_code = current_params[
|
|
258
|
-
create_access_token authorization_code, @client,
|
|
299
|
+
authorization_code = current_params["code"]
|
|
300
|
+
create_access_token authorization_code, @client, "incorrect-code-verifier"
|
|
259
301
|
|
|
260
|
-
should_not_have_json
|
|
261
|
-
should_have_json
|
|
302
|
+
should_not_have_json "access_token"
|
|
303
|
+
should_have_json "error", "invalid_grant"
|
|
262
304
|
end
|
|
263
305
|
|
|
264
|
-
scenario
|
|
306
|
+
scenario "code_challenge_mehthod in token request is totally ignored" do
|
|
265
307
|
visit authorization_endpoint_url(
|
|
266
308
|
client: @client,
|
|
267
309
|
code_challenge: code_challenge,
|
|
268
|
-
code_challenge_method:
|
|
310
|
+
code_challenge_method: "S256"
|
|
269
311
|
)
|
|
270
|
-
click_on
|
|
312
|
+
click_on "Authorize"
|
|
271
313
|
|
|
272
|
-
authorization_code = current_params[
|
|
314
|
+
authorization_code = current_params["code"]
|
|
273
315
|
page.driver.post token_endpoint_url(
|
|
274
316
|
code: authorization_code,
|
|
275
317
|
client: @client,
|
|
276
318
|
code_verifier: code_challenge,
|
|
277
|
-
code_challenge_method:
|
|
319
|
+
code_challenge_method: "plain"
|
|
278
320
|
)
|
|
279
321
|
|
|
280
|
-
should_not_have_json
|
|
281
|
-
should_have_json
|
|
322
|
+
should_not_have_json "access_token"
|
|
323
|
+
should_have_json "error", "invalid_grant"
|
|
282
324
|
end
|
|
283
325
|
|
|
284
|
-
scenario
|
|
326
|
+
scenario "expects to set code_challenge_method explicitely without fallback" do
|
|
285
327
|
visit authorization_endpoint_url(client: @client, code_challenge: code_challenge)
|
|
286
|
-
expect(page).to have_content(
|
|
328
|
+
expect(page).to have_content("The code challenge method must be plain or S256.")
|
|
287
329
|
end
|
|
288
330
|
end
|
|
289
331
|
end
|
|
290
332
|
|
|
291
|
-
context
|
|
333
|
+
context "when application scopes are present and no scope is passed" do
|
|
292
334
|
background do
|
|
293
|
-
@client.
|
|
335
|
+
@client.update(scopes: "public write read")
|
|
294
336
|
end
|
|
295
337
|
|
|
296
|
-
scenario
|
|
338
|
+
scenario "access grant has no scope" do
|
|
297
339
|
default_scopes_exist :admin
|
|
298
340
|
visit authorization_endpoint_url(client: @client)
|
|
299
|
-
click_on
|
|
341
|
+
click_on "Authorize"
|
|
300
342
|
access_grant_should_exist_for(@client, @resource_owner)
|
|
301
343
|
grant = Doorkeeper::AccessGrant.first
|
|
302
344
|
expect(grant.scopes).to be_empty
|
|
303
345
|
end
|
|
304
346
|
|
|
305
|
-
scenario
|
|
347
|
+
scenario "access grant have scopes which are common in application scopees and default scopes" do
|
|
306
348
|
default_scopes_exist :public, :write
|
|
307
349
|
visit authorization_endpoint_url(client: @client)
|
|
308
|
-
click_on
|
|
350
|
+
click_on "Authorize"
|
|
309
351
|
access_grant_should_exist_for(@client, @resource_owner)
|
|
310
352
|
access_grant_should_have_scopes :public, :write
|
|
311
353
|
end
|
|
312
354
|
end
|
|
313
355
|
|
|
314
|
-
context
|
|
356
|
+
context "with scopes" do
|
|
315
357
|
background do
|
|
316
358
|
default_scopes_exist :public
|
|
317
359
|
optional_scopes_exist :write
|
|
318
360
|
end
|
|
319
361
|
|
|
320
|
-
scenario
|
|
362
|
+
scenario "resource owner authorizes the client with default scopes" do
|
|
321
363
|
visit authorization_endpoint_url(client: @client)
|
|
322
|
-
click_on
|
|
364
|
+
click_on "Authorize"
|
|
323
365
|
access_grant_should_exist_for(@client, @resource_owner)
|
|
324
366
|
access_grant_should_have_scopes :public
|
|
325
367
|
end
|
|
326
368
|
|
|
327
|
-
scenario
|
|
328
|
-
visit authorization_endpoint_url(client: @client, scope:
|
|
329
|
-
click_on
|
|
369
|
+
scenario "resource owner authorizes the client with required scopes" do
|
|
370
|
+
visit authorization_endpoint_url(client: @client, scope: "public write")
|
|
371
|
+
click_on "Authorize"
|
|
330
372
|
access_grant_should_have_scopes :public, :write
|
|
331
373
|
end
|
|
332
374
|
|
|
333
|
-
scenario
|
|
334
|
-
visit authorization_endpoint_url(client: @client, scope:
|
|
335
|
-
click_on
|
|
375
|
+
scenario "resource owner authorizes the client with required scopes (without defaults)" do
|
|
376
|
+
visit authorization_endpoint_url(client: @client, scope: "write")
|
|
377
|
+
click_on "Authorize"
|
|
336
378
|
access_grant_should_have_scopes :write
|
|
337
379
|
end
|
|
338
380
|
|
|
339
|
-
scenario
|
|
340
|
-
visit authorization_endpoint_url(client: @client, scope:
|
|
341
|
-
click_on
|
|
381
|
+
scenario "new access token matches required scopes" do
|
|
382
|
+
visit authorization_endpoint_url(client: @client, scope: "public write")
|
|
383
|
+
click_on "Authorize"
|
|
342
384
|
|
|
343
385
|
authorization_code = Doorkeeper::AccessGrant.first.token
|
|
344
386
|
create_access_token authorization_code, @client
|
|
@@ -347,36 +389,36 @@ feature 'Authorization Code Flow' do
|
|
|
347
389
|
access_token_should_have_scopes :public, :write
|
|
348
390
|
end
|
|
349
391
|
|
|
350
|
-
scenario
|
|
351
|
-
client_is_authorized(@client, @resource_owner, scopes:
|
|
352
|
-
visit authorization_endpoint_url(client: @client, scope:
|
|
353
|
-
click_on
|
|
392
|
+
scenario "returns new token if scopes have changed" do
|
|
393
|
+
client_is_authorized(@client, @resource_owner, scopes: "public write")
|
|
394
|
+
visit authorization_endpoint_url(client: @client, scope: "public")
|
|
395
|
+
click_on "Authorize"
|
|
354
396
|
|
|
355
397
|
authorization_code = Doorkeeper::AccessGrant.first.token
|
|
356
398
|
create_access_token authorization_code, @client
|
|
357
399
|
|
|
358
400
|
expect(Doorkeeper::AccessToken.count).to be(2)
|
|
359
401
|
|
|
360
|
-
should_have_json
|
|
402
|
+
should_have_json "access_token", Doorkeeper::AccessToken.last.token
|
|
361
403
|
end
|
|
362
404
|
|
|
363
|
-
scenario
|
|
364
|
-
client_is_authorized(@client, @resource_owner, scopes:
|
|
365
|
-
visit authorization_endpoint_url(client: @client, scope:
|
|
366
|
-
click_on
|
|
405
|
+
scenario "resource owner authorizes the client with extra scopes" do
|
|
406
|
+
client_is_authorized(@client, @resource_owner, scopes: "public")
|
|
407
|
+
visit authorization_endpoint_url(client: @client, scope: "public write")
|
|
408
|
+
click_on "Authorize"
|
|
367
409
|
|
|
368
410
|
authorization_code = Doorkeeper::AccessGrant.first.token
|
|
369
411
|
create_access_token authorization_code, @client
|
|
370
412
|
|
|
371
413
|
expect(Doorkeeper::AccessToken.count).to be(2)
|
|
372
414
|
|
|
373
|
-
should_have_json
|
|
415
|
+
should_have_json "access_token", Doorkeeper::AccessToken.last.token
|
|
374
416
|
access_token_should_have_scopes :public, :write
|
|
375
417
|
end
|
|
376
418
|
end
|
|
377
419
|
end
|
|
378
420
|
|
|
379
|
-
describe
|
|
421
|
+
describe "Authorization Code Flow" do
|
|
380
422
|
before do
|
|
381
423
|
Doorkeeper.configure do
|
|
382
424
|
orm DOORKEEPER_ORM
|
|
@@ -386,20 +428,20 @@ describe 'Authorization Code Flow' do
|
|
|
386
428
|
client_exists
|
|
387
429
|
end
|
|
388
430
|
|
|
389
|
-
context
|
|
431
|
+
context "issuing a refresh token" do
|
|
390
432
|
before do
|
|
391
433
|
authorization_code_exists application: @client
|
|
392
434
|
end
|
|
393
435
|
|
|
394
|
-
it
|
|
436
|
+
it "second of simultaneous client requests get an error for revoked acccess token" do
|
|
395
437
|
authorization_code = Doorkeeper::AccessGrant.first.token
|
|
396
438
|
allow_any_instance_of(Doorkeeper::AccessGrant)
|
|
397
439
|
.to receive(:revoked?).and_return(false, true)
|
|
398
440
|
|
|
399
441
|
post token_endpoint_url(code: authorization_code, client: @client)
|
|
400
442
|
|
|
401
|
-
should_not_have_json
|
|
402
|
-
should_have_json
|
|
443
|
+
should_not_have_json "access_token"
|
|
444
|
+
should_have_json "error", "invalid_grant"
|
|
403
445
|
end
|
|
404
446
|
end
|
|
405
447
|
end
|