doorkeeper 5.0.3 → 5.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of doorkeeper might be problematic. Click here for more details.

Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -3
  3. data/Dangerfile +5 -2
  4. data/Gemfile +3 -1
  5. data/NEWS.md +20 -13
  6. data/README.md +1 -1
  7. data/app/controllers/doorkeeper/applications_controller.rb +3 -3
  8. data/app/controllers/doorkeeper/authorized_applications_controller.rb +1 -1
  9. data/app/controllers/doorkeeper/tokens_controller.rb +6 -6
  10. data/app/views/doorkeeper/applications/show.html.erb +1 -1
  11. data/app/views/layouts/doorkeeper/admin.html.erb +5 -3
  12. data/bin/console +15 -0
  13. data/gemfiles/rails_4_2.gemfile +1 -0
  14. data/gemfiles/rails_5_0.gemfile +1 -0
  15. data/gemfiles/rails_5_1.gemfile +1 -0
  16. data/gemfiles/rails_5_2.gemfile +2 -1
  17. data/gemfiles/rails_master.gemfile +1 -0
  18. data/lib/doorkeeper.rb +1 -0
  19. data/lib/doorkeeper/config.rb +73 -6
  20. data/lib/doorkeeper/helpers/controller.rb +3 -2
  21. data/lib/doorkeeper/models/access_grant_mixin.rb +8 -1
  22. data/lib/doorkeeper/models/access_token_mixin.rb +40 -9
  23. data/lib/doorkeeper/models/application_mixin.rb +52 -1
  24. data/lib/doorkeeper/models/concerns/hashable.rb +137 -0
  25. data/lib/doorkeeper/models/concerns/scopes.rb +1 -1
  26. data/lib/doorkeeper/oauth/authorization/code.rb +1 -1
  27. data/lib/doorkeeper/oauth/authorization/token.rb +1 -1
  28. data/lib/doorkeeper/oauth/authorization_code_request.rb +1 -1
  29. data/lib/doorkeeper/oauth/client.rb +1 -1
  30. data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -3
  31. data/lib/doorkeeper/oauth/code_response.rb +2 -2
  32. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +23 -8
  33. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +32 -0
  34. data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -2
  35. data/lib/doorkeeper/oauth/pre_authorization.rb +8 -3
  36. data/lib/doorkeeper/oauth/refresh_token_request.rb +4 -1
  37. data/lib/doorkeeper/oauth/token_response.rb +2 -2
  38. data/lib/doorkeeper/orm/active_record/access_grant.rb +22 -2
  39. data/lib/doorkeeper/orm/active_record/application.rb +12 -53
  40. data/lib/doorkeeper/version.rb +3 -3
  41. data/lib/generators/doorkeeper/templates/initializer.rb +41 -1
  42. data/spec/controllers/application_metal_controller_spec.rb +18 -4
  43. data/spec/controllers/tokens_controller_spec.rb +7 -11
  44. data/spec/dummy/app/controllers/application_controller.rb +1 -1
  45. data/spec/factories.rb +3 -3
  46. data/spec/lib/config_spec.rb +84 -0
  47. data/spec/lib/models/hashable_spec.rb +183 -0
  48. data/spec/lib/oauth/base_request_spec.rb +7 -7
  49. data/spec/lib/oauth/client_credentials/validation_spec.rb +3 -0
  50. data/spec/lib/oauth/helpers/scope_checker_spec.rb +52 -17
  51. data/spec/lib/oauth/helpers/uri_checker_spec.rb +20 -2
  52. data/spec/lib/oauth/password_access_token_request_spec.rb +32 -11
  53. data/spec/lib/oauth/pre_authorization_spec.rb +24 -0
  54. data/spec/lib/oauth/token_response_spec.rb +13 -13
  55. data/spec/lib/oauth/token_spec.rb +14 -0
  56. data/spec/models/doorkeeper/access_grant_spec.rb +61 -0
  57. data/spec/models/doorkeeper/access_token_spec.rb +123 -0
  58. data/spec/models/doorkeeper/application_spec.rb +227 -295
  59. data/spec/requests/flows/authorization_code_spec.rb +40 -0
  60. data/spec/requests/flows/password_spec.rb +4 -2
  61. data/spec/requests/flows/revoke_token_spec.rb +14 -30
  62. data/spec/spec_helper.rb +2 -1
  63. data/spec/support/ruby_2_6_rails_4_2_patch.rb +14 -0
  64. data/spec/support/shared/hashing_shared_context.rb +29 -0
  65. metadata +12 -4
@@ -1,6 +1,25 @@
1
1
  # frozen_string_literal: true
2
+ require 'ipaddr'
2
3
 
3
4
  module Doorkeeper
5
+ module IPAddrLoopback
6
+ def loopback?
7
+ case @family
8
+ when Socket::AF_INET
9
+ @addr & 0xff000000 == 0x7f000000
10
+ when Socket::AF_INET6
11
+ @addr == 1
12
+ else
13
+ raise AddressFamilyError, "unsupported address family"
14
+ end
15
+ end
16
+ end
17
+
18
+ # For backward compatibility with old rubies
19
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.5.0")
20
+ IPAddr.send(:include, Doorkeeper::IPAddrLoopback)
21
+ end
22
+
4
23
  module OAuth
5
24
  module Helpers
6
25
  module URIChecker
@@ -23,10 +42,23 @@ module Doorkeeper
23
42
  client_url.query = nil
24
43
  end
25
44
 
45
+ # RFC8252, Paragraph 7.3
46
+ # @see https://tools.ietf.org/html/rfc8252#section-7.3
47
+ if loopback_uri?(url) && loopback_uri?(client_url)
48
+ url.port = nil
49
+ client_url.port = nil
50
+ end
51
+
26
52
  url.query = nil
27
53
  url == client_url
28
54
  end
29
55
 
56
+ def self.loopback_uri?(uri)
57
+ IPAddr.new(uri.host).loopback?
58
+ rescue IPAddr::Error
59
+ false
60
+ end
61
+
30
62
  def self.valid_for_authorization?(url, client_url)
31
63
  valid?(url) && client_url.split.any? { |other_url| matches?(url, other_url) }
32
64
  end
@@ -32,7 +32,12 @@ module Doorkeeper
32
32
  client_scopes = client.try(:scopes)
33
33
  return true if scopes.blank?
34
34
 
35
- ScopeChecker.valid?(scopes.to_s, server.scopes, client_scopes)
35
+ ScopeChecker.valid?(
36
+ scope_str: scopes.to_s,
37
+ server_scopes: server.scopes,
38
+ app_scopes: client_scopes,
39
+ grant_type: grant_type
40
+ )
36
41
  end
37
42
 
38
43
  def validate_resource_owner
@@ -40,7 +45,7 @@ module Doorkeeper
40
45
  end
41
46
 
42
47
  def validate_client
43
- !parameters[:client_id] || !client.nil?
48
+ !parameters[:client_id] || client.present?
44
49
  end
45
50
  end
46
51
  end
@@ -77,12 +77,17 @@ module Doorkeeper
77
77
  return true if scope.blank?
78
78
 
79
79
  Helpers::ScopeChecker.valid?(
80
- scope,
81
- server.scopes,
82
- client.application.scopes
80
+ scope_str: scope,
81
+ server_scopes: server.scopes,
82
+ app_scopes: client.application.scopes,
83
+ grant_type: grant_type
83
84
  )
84
85
  end
85
86
 
87
+ def grant_type
88
+ response_type == 'code' ? AUTHORIZATION_CODE : IMPLICIT
89
+ end
90
+
86
91
  def validate_redirect_uri
87
92
  return false if redirect_uri.blank?
88
93
 
@@ -99,7 +99,10 @@ module Doorkeeper
99
99
 
100
100
  def validate_scope
101
101
  if @original_scopes.present?
102
- ScopeChecker.valid?(@original_scopes, refresh_token.scopes)
102
+ ScopeChecker.valid?(
103
+ scope_str: @original_scopes,
104
+ server_scopes: refresh_token.scopes
105
+ )
103
106
  else
104
107
  true
105
108
  end
@@ -11,10 +11,10 @@ module Doorkeeper
11
11
 
12
12
  def body
13
13
  {
14
- 'access_token' => token.token,
14
+ 'access_token' => token.plaintext_token,
15
15
  'token_type' => token.token_type,
16
16
  'expires_in' => token.expires_in_seconds,
17
- 'refresh_token' => token.refresh_token,
17
+ 'refresh_token' => token.plaintext_refresh_token,
18
18
  'scope' => token.scopes_string,
19
19
  'created_at' => token.created_at.to_i
20
20
  }.reject { |_, value| value.blank? }
@@ -16,11 +16,30 @@ module Doorkeeper
16
16
 
17
17
  belongs_to :application, belongs_to_options
18
18
 
19
- validates :resource_owner_id, :application_id, :token, :expires_in, :redirect_uri, presence: true
19
+ validates :resource_owner_id,
20
+ :application_id,
21
+ :token,
22
+ :expires_in,
23
+ :redirect_uri,
24
+ presence: true
25
+
20
26
  validates :token, uniqueness: true
21
27
 
22
28
  before_validation :generate_token, on: :create
23
29
 
30
+ # Keep a reference to the generated token during generation
31
+ # of this access grant. The actual token may be mapped by
32
+ # the configuration hasher and may not be available in plaintext.
33
+ #
34
+ # If hash tokens are enabled, this will return nil on fetched tokens
35
+ def plaintext_token
36
+ if perform_secret_hashing?
37
+ @raw_token
38
+ else
39
+ token
40
+ end
41
+ end
42
+
24
43
  private
25
44
 
26
45
  # Generates token value with UniqueToken class.
@@ -28,7 +47,8 @@ module Doorkeeper
28
47
  # @return [String] token value
29
48
  #
30
49
  def generate_token
31
- self.token = UniqueToken.generate
50
+ @raw_token = UniqueToken.generate
51
+ self.token = hashed_or_plain_token(@raw_token)
32
52
  end
33
53
  end
34
54
  end
@@ -45,26 +45,13 @@ module Doorkeeper
45
45
  AccessGrant.revoke_all_for(id, resource_owner)
46
46
  end
47
47
 
48
- # Represents client as set of it's attributes in JSON format.
49
- # This is the right way how we want to override ActiveRecord #to_json.
50
- #
51
- # Respects privacy settings and serializes minimum set of attributes
52
- # for public/private clients and full set for authorized owners.
53
- #
54
- # @return [Hash] entity attributes for JSON
55
- #
56
- def as_json(options = {})
57
- # if application belongs to some owner we need to check if it's the same as
58
- # the one passed in the options or check if we render the client as an owner
59
- if (respond_to?(:owner) && owner && owner == options[:current_resource_owner]) ||
60
- options[:as_owner]
61
- # Owners can see all the client attributes, fallback to ActiveModel serialization
62
- super
48
+ # We keep a volatile copy of the raw client_secret for initial communication
49
+ # The stored secret may be mapped and not available in cleartext.
50
+ def plaintext_secret
51
+ if perform_secret_hashing?
52
+ @raw_secret
63
53
  else
64
- # if application has no owner or it's owner doesn't match one from the options
65
- # we render only minimum set of attributes that could be exposed to a public
66
- only = extract_serializable_attributes(options)
67
- super(options.merge(only: only))
54
+ secret
68
55
  end
69
56
  end
70
57
 
@@ -75,12 +62,16 @@ module Doorkeeper
75
62
  end
76
63
 
77
64
  def generate_secret
78
- self.secret = UniqueToken.generate if secret.blank?
65
+ return unless secret.blank?
66
+
67
+ @raw_secret = UniqueToken.generate
68
+ self.secret = hashed_or_plain_token(@raw_secret)
79
69
  end
80
70
 
81
71
  def scopes_match_configured
82
72
  if scopes.present? &&
83
- !ScopeChecker.valid?(scopes.to_s, Doorkeeper.configuration.scopes)
73
+ !ScopeChecker.valid?(scope_str: scopes.to_s,
74
+ server_scopes: Doorkeeper.configuration.scopes)
84
75
  errors.add(:scopes, :not_match_configured)
85
76
  end
86
77
  end
@@ -88,37 +79,5 @@ module Doorkeeper
88
79
  def enforce_scopes?
89
80
  Doorkeeper.configuration.enforce_configured_scopes?
90
81
  end
91
-
92
- # Helper method to extract collection of serializable attribute names
93
- # considering serialization options (like `only`, `except` and so on).
94
- #
95
- # @param options [Hash] serialization options
96
- #
97
- # @return [Array<String>]
98
- # collection of attributes to be serialized using #as_json
99
- #
100
- def extract_serializable_attributes(options = {})
101
- opts = options.try(:dup) || {}
102
- only = Array.wrap(opts[:only]).map(&:to_s)
103
-
104
- only = if only.blank?
105
- serializable_attributes
106
- else
107
- only & serializable_attributes
108
- end
109
-
110
- only -= Array.wrap(opts[:except]).map(&:to_s) if opts.key?(:except)
111
- only.uniq
112
- end
113
-
114
- # Collection of attributes that could be serialized for public.
115
- # Override this method if you need additional attributes to be serialized.
116
- #
117
- # @return [Array<String>] collection of serializable attributes
118
- def serializable_attributes
119
- attributes = %w[id name created_at]
120
- attributes << "uid" unless confidential?
121
- attributes
122
- end
123
82
  end
124
83
  end
@@ -8,9 +8,9 @@ module Doorkeeper
8
8
  module VERSION
9
9
  # Semantic versioning
10
10
  MAJOR = 5
11
- MINOR = 0
12
- TINY = 3
13
- PRE = nil
11
+ MINOR = 1
12
+ TINY = 0
13
+ PRE = 'rc1'
14
14
 
15
15
  # Full version number
16
16
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
@@ -75,8 +75,39 @@ Doorkeeper.configure do
75
75
  # doesn't updates existing token expiration time, it will create a new token instead.
76
76
  # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
77
77
  #
78
+ # You can not enable this option together with +hash_token_secrets+.
79
+ #
78
80
  # reuse_access_token
79
81
 
82
+ # Hash access and refresh tokens before persisting them.
83
+ # Note: This will disable the possibility to use +reuse_access_token+
84
+ # since plain values can no longer be retrieved.
85
+ #
86
+ # hash_token_secrets
87
+
88
+ # Hash application secrets before persisting them.
89
+ #
90
+ # hash_application_secrets
91
+
92
+ # When the above option is enabled,
93
+ # and a hashed token or secret is not found,
94
+ # look up the plain text token as a fallback.
95
+ #
96
+ # This will ensure that old access tokens and secrets
97
+ # will remain valid even if the hashing above is enabled
98
+ #
99
+ # fallback_to_plain_secrets
100
+
101
+ #
102
+ # Since old values will not be re-hashed, lookups to tokens and secrets
103
+ # will fall back to plain value comparison so any existing tokens will
104
+ # not be invalidated.
105
+ #
106
+ # For example, to use SHA256 digests on plain values, uncomment these lines:
107
+ # hash_secrets do |plain_value|
108
+ # Digest::SHA256.hexdigest plain_value
109
+ # end
110
+
80
111
  # Issue access tokens with refresh token (disabled by default), you may also
81
112
  # pass a block which accepts `context` to customize when to give a refresh
82
113
  # token or not. Similar to `custom_access_token_expires_in`, `context` has
@@ -108,6 +139,15 @@ Doorkeeper.configure do
108
139
  # default_scopes :public
109
140
  # optional_scopes :write, :update
110
141
 
142
+ # Define scopes_by_grant_type to restrict only certain scopes for grant_type
143
+ # By default, all the scopes will be available for all the grant types.
144
+ #
145
+ # Keys to this hash should be the name of grant_type and
146
+ # values should be the array of scopes for that grant type.
147
+ # Note: scopes should be from configured_scopes(i.e. deafult or optional)
148
+ #
149
+ # scopes_by_grant_type password: [:write], client_credentials: [:update]
150
+
111
151
  # Change the way client credentials are retrieved from the request object.
112
152
  # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
113
153
  # falls back to the `:client_id` and `:client_secret` params from the `params` object.
@@ -195,7 +235,7 @@ Doorkeeper.configure do
195
235
  # end
196
236
 
197
237
  # Hook into Authorization flow in order to implement Single Sign Out
198
- # or add ny other functionality.
238
+ # or add any other functionality.
199
239
  #
200
240
  # before_successful_authorization do |controller|
201
241
  # Rails.logger.info(params.inspect)
@@ -7,6 +7,10 @@ describe Doorkeeper::ApplicationMetalController do
7
7
  def index
8
8
  render json: {}, status: 200
9
9
  end
10
+
11
+ def create
12
+ render json: {}, status: 200
13
+ end
10
14
  end
11
15
 
12
16
  it "lazy run hooks" do
@@ -22,13 +26,18 @@ describe Doorkeeper::ApplicationMetalController do
22
26
  context 'enabled' do
23
27
  let(:flag) { true }
24
28
 
25
- it '200 for the correct media type' do
26
- get :index, params: {}, as: :url_encoded_form
29
+ it 'returns a 200 for the requests without body' do
30
+ get :index, params: {}
27
31
  expect(response).to have_http_status 200
28
32
  end
29
33
 
30
- it 'returns a 415 for an incorrect media type' do
31
- get :index, as: :json
34
+ it 'returns a 200 for the requests with body and correct media type' do
35
+ post :create, params: {}, as: :url_encoded_form
36
+ expect(response).to have_http_status 200
37
+ end
38
+
39
+ it 'returns a 415 for the requests with body and incorrect media type' do
40
+ post :create, params: {}, as: :json
32
41
  expect(response).to have_http_status 415
33
42
  end
34
43
  end
@@ -45,6 +54,11 @@ describe Doorkeeper::ApplicationMetalController do
45
54
  get :index, as: :json
46
55
  expect(response).to have_http_status 200
47
56
  end
57
+
58
+ it 'returns a 200 for the requests with body and incorrect media type' do
59
+ post :create, params: {}, as: :json
60
+ expect(response).to have_http_status 200
61
+ end
48
62
  end
49
63
  end
50
64
  end
@@ -28,7 +28,7 @@ describe Doorkeeper::TokensController do
28
28
  describe 'when there is a failure due to a custom error' do
29
29
  it 'returns the error response with a custom message' do
30
30
  # I18n looks for `doorkeeper.errors.messages.custom_message` in locale files
31
- custom_message = "my_message"
31
+ custom_message = 'my_message'
32
32
  allow(I18n).to receive(:translate)
33
33
  .with(
34
34
  custom_message,
@@ -60,21 +60,17 @@ describe Doorkeeper::TokensController do
60
60
  let(:client) { FactoryBot.create(:application) }
61
61
  let(:access_token) { FactoryBot.create(:access_token, application: client) }
62
62
 
63
- before(:each) do
64
- allow(controller).to receive(:token) { access_token }
65
- end
66
-
67
63
  context 'when associated app is public' do
68
64
  let(:client) { FactoryBot.create(:application, confidential: false) }
69
65
 
70
66
  it 'returns 200' do
71
- post :revoke
67
+ post :revoke, params: { token: access_token.token }
72
68
 
73
69
  expect(response.status).to eq 200
74
70
  end
75
71
 
76
72
  it 'revokes the access token' do
77
- post :revoke
73
+ post :revoke, params: { token: access_token.token }
78
74
 
79
75
  expect(access_token.reload).to have_attributes(revoked?: true)
80
76
  end
@@ -89,13 +85,13 @@ describe Doorkeeper::TokensController do
89
85
  end
90
86
 
91
87
  it 'returns 200' do
92
- post :revoke
88
+ post :revoke, params: { token: access_token.token }
93
89
 
94
90
  expect(response.status).to eq 200
95
91
  end
96
92
 
97
93
  it 'revokes the access token' do
98
- post :revoke
94
+ post :revoke, params: { token: access_token.token }
99
95
 
100
96
  expect(access_token.reload).to have_attributes(revoked?: true)
101
97
  end
@@ -105,13 +101,13 @@ describe Doorkeeper::TokensController do
105
101
  let(:oauth_client) { Doorkeeper::OAuth::Client.new(some_other_client) }
106
102
 
107
103
  it 'returns 200' do
108
- post :revoke
104
+ post :revoke, params: { token: access_token.token }
109
105
 
110
106
  expect(response.status).to eq 200
111
107
  end
112
108
 
113
109
  it 'does not revoke the access token' do
114
- post :revoke
110
+ post :revoke, params: { token: access_token.token }
115
111
 
116
112
  expect(access_token.reload).to have_attributes(revoked?: false)
117
113
  end
@@ -1,3 +1,3 @@
1
1
  class ApplicationController < ActionController::Base
2
- protect_from_forgery
2
+ protect_from_forgery with: :exception
3
3
  end
@@ -1,5 +1,5 @@
1
1
  FactoryBot.define do
2
- factory :access_grant, class: Doorkeeper::AccessGrant do
2
+ factory :access_grant, class: "Doorkeeper::AccessGrant" do
3
3
  sequence(:resource_owner_id) { |n| n }
4
4
  application
5
5
  redirect_uri { 'https://app.com/callback' }
@@ -7,7 +7,7 @@ FactoryBot.define do
7
7
  scopes { 'public write' }
8
8
  end
9
9
 
10
- factory :access_token, class: Doorkeeper::AccessToken do
10
+ factory :access_token, class: "Doorkeeper::AccessToken" do
11
11
  sequence(:resource_owner_id) { |n| n }
12
12
  application
13
13
  expires_in { 2.hours }
@@ -17,7 +17,7 @@ FactoryBot.define do
17
17
  end
18
18
  end
19
19
 
20
- factory :application, class: Doorkeeper::Application do
20
+ factory :application, class: "Doorkeeper::Application" do
21
21
  sequence(:name) { |n| "Application #{n}" }
22
22
  redirect_uri { 'https://app.com/callback' }
23
23
  end