doorkeeper 4.3.1 → 4.4.2

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/NEWS.md +16 -2
  4. data/README.md +2 -1
  5. data/app/controllers/doorkeeper/applications_controller.rb +2 -1
  6. data/app/controllers/doorkeeper/tokens_controller.rb +9 -10
  7. data/app/views/doorkeeper/applications/_form.html.erb +11 -0
  8. data/app/views/doorkeeper/applications/index.html.erb +2 -0
  9. data/app/views/doorkeeper/applications/show.html.erb +3 -0
  10. data/config/locales/en.yml +6 -0
  11. data/doorkeeper.gemspec +2 -0
  12. data/lib/doorkeeper/models/access_token_mixin.rb +1 -1
  13. data/lib/doorkeeper/models/application_mixin.rb +8 -1
  14. data/lib/doorkeeper/oauth/client/credentials.rb +3 -1
  15. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +15 -0
  16. data/lib/doorkeeper/oauth/pre_authorization.rb +5 -3
  17. data/lib/doorkeeper/orm/active_record/application.rb +17 -0
  18. data/lib/doorkeeper/request/password.rb +1 -11
  19. data/lib/doorkeeper/version.rb +23 -2
  20. data/lib/generators/doorkeeper/add_client_confidentiality_generator.rb +31 -0
  21. data/lib/generators/doorkeeper/templates/add_confidential_to_application_migration.rb.erb +11 -0
  22. data/lib/generators/doorkeeper/templates/migration.rb.erb +1 -0
  23. data/spec/controllers/authorizations_controller_spec.rb +2 -2
  24. data/spec/controllers/tokens_controller_spec.rb +59 -7
  25. data/spec/dummy/db/migrate/20111122132257_create_users.rb +3 -1
  26. data/spec/dummy/db/migrate/20120312140401_add_password_to_users.rb +3 -1
  27. data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +3 -1
  28. data/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb +3 -1
  29. data/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb +3 -1
  30. data/spec/dummy/db/migrate/20180210183654_add_confidential_to_application.rb +13 -0
  31. data/spec/dummy/db/schema.rb +2 -1
  32. data/spec/lib/oauth/authorization_code_request_spec.rb +15 -0
  33. data/spec/lib/oauth/client/credentials_spec.rb +4 -2
  34. data/spec/lib/oauth/helpers/uri_checker_spec.rb +110 -1
  35. data/spec/lib/oauth/pre_authorization_spec.rb +12 -7
  36. data/spec/models/doorkeeper/application_spec.rb +96 -5
  37. data/spec/requests/flows/authorization_code_spec.rb +1 -1
  38. data/spec/requests/flows/password_spec.rb +64 -21
  39. metadata +25 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8d76a49e22bc442139a55528594bc6a3849fc53d
4
- data.tar.gz: eeaca97997f68ca0017c2bbc350323cb1bc9df32
3
+ metadata.gz: c92e80cb652104645d4ec7634af2c3699f90da68
4
+ data.tar.gz: 3d3e22b8dfa7288c8a120645db4cc4193809d60f
5
5
  SHA512:
6
- metadata.gz: 961beb33acfb9ba24831ec3e4fbcc2e4cbcc58ccd1a47bd0672aafe9e8e1d993918f1f5bd1ce7df4f3e20ae70a455c6cf698b697489b1247ff6eeecd89950ec0
7
- data.tar.gz: ed000ab156a89e92973bc0d22896250ec73ddc52fd7adc6a229b9eb82d1517372b521477af5c394031b9af6164d373967937429093f24179104a6743f2778b87
6
+ metadata.gz: e6546445a2ab9db13ff53fe16633b8769f1c70b1b76931e9709346984cb8aed18eec91a5a4fa29c2f06786999c0e87697897064603a918b868bd55b32874faa8
7
+ data.tar.gz: 7717301a83ea02e249e8cc121bd0894cb08fafa272340715829de78020bb6428d3d8c7764ed5200c7e22ecf9bb99c5f206503ae938e89ed1817620c1f42388cd
data/.rubocop.yml CHANGED
@@ -2,6 +2,10 @@ AllCops:
2
2
  Exclude:
3
3
  - "spec/dummy/db/*"
4
4
 
5
+ Metrics/BlockLength:
6
+ Exclude:
7
+ - spec/**/*
8
+
5
9
  LineLength:
6
10
  Exclude:
7
11
  - spec/**/*
data/NEWS.md CHANGED
@@ -4,7 +4,21 @@ User-visible changes worth mentioning.
4
4
 
5
5
  ## master
6
6
 
7
- Replace this text with you changelog entry. Look at the examples below.
7
+ ## 4.4.2
8
+ - [#1130] Backport fix for native redirect_uri from 5.x.
9
+
10
+ ## 4.4.1
11
+
12
+ - [#1127] Backport token type to comply with the RFC6750 specification.
13
+ - [#1125] Backport Quote surround I18n yes/no keys
14
+
15
+ ## 4.4.0
16
+
17
+ - [#1120] Backport security fix from 5.x for token revocation when using public clients
18
+
19
+ ## 4.3.2
20
+
21
+ - [#1053] Support authorizing with query params in the request `redirect_uri` if explicitly present in app's `Application#redirect_uri`
8
22
 
9
23
  ## 4.3.1
10
24
 
@@ -39,7 +53,7 @@ Replace this text with you changelog entry. Look at the examples below.
39
53
  - [#1023] Update Ruby versions and test against 2.5.0 on Travis CI.
40
54
  - [#1024] Migrate from FactoryGirl to FactoryBot.
41
55
  - [#1025] Improve documentation for adding foreign keys
42
- - [#1028] Make it possible to have composit strategy names.
56
+ - [#1028] Make it possible to have composite strategy names.
43
57
 
44
58
  ## 4.2.6
45
59
 
data/README.md CHANGED
@@ -56,6 +56,7 @@ https://github.com/doorkeeper-gem/doorkeeper/releases
56
56
  - [Authenticated resource owner](#authenticated-resource-owner)
57
57
  - [Applications list](#applications-list)
58
58
  - [Other customizations](#other-customizations)
59
+ - [Testing](#testing)
59
60
  - [Upgrading](#upgrading)
60
61
  - [Development](#development)
61
62
  - [Contributing](#contributing)
@@ -408,7 +409,7 @@ For more information see the page
408
409
 
409
410
  You can use Doorkeeper models in your application test suite. Note that starting from
410
411
  Doorkeeper 4.3.0 it uses [ActiveSupport lazy loading hooks](http://api.rubyonrails.org/classes/ActiveSupport/LazyLoadHooks.html)
411
- to load models. There are [known issues](https://github.com/doorkeeper-gem/doorkeeper/issues/1043)
412
+ to load models. There are [known issue](https://github.com/doorkeeper-gem/doorkeeper/issues/1043)
412
413
  with the `factory_bot_rails` gem (it executes factories building before `ActiveRecord::Base`
413
414
  is initialized using hooks in gem railtie, so you can catch a `uninitialized constant` error).
414
415
  It is recommended to use pure `factory_bot` gem to solve this problem.
@@ -57,7 +57,8 @@ module Doorkeeper
57
57
  end
58
58
 
59
59
  def application_params
60
- params.require(:doorkeeper_application).permit(:name, :redirect_uri, :scopes)
60
+ params.require(:doorkeeper_application).
61
+ permit(:name, :redirect_uri, :scopes, :confidential)
61
62
  end
62
63
  end
63
64
  end
@@ -58,16 +58,15 @@ module Doorkeeper
58
58
  # https://tools.ietf.org/html/rfc6749#section-2.1
59
59
  # https://tools.ietf.org/html/rfc7009
60
60
  def authorized?
61
- if token.present?
62
- # Client is confidential, therefore client authentication & authorization
63
- # is required
64
- if token.application_id?
65
- # We authorize client by checking token's application
66
- server.client && server.client.application == token.application
67
- else
68
- # Client is public, authentication unnecessary
69
- true
70
- end
61
+ return unless token.present?
62
+ # Client is confidential, therefore client authentication & authorization
63
+ # is required
64
+ if token.application_id? && token.application.confidential?
65
+ # We authorize client by checking token's application
66
+ server.client && server.client.application == token.application
67
+ else
68
+ # Client is public, authentication unnecessary
69
+ true
71
70
  end
72
71
  end
73
72
 
@@ -27,6 +27,17 @@
27
27
  </div>
28
28
  <% end %>
29
29
 
30
+ <%= content_tag :div, class: "form-group#{' has-error' if application.errors[:confidential].present?}" do %>
31
+ <%= f.label :confidential, class: 'col-sm-2 control-label' %>
32
+ <div class="col-sm-10">
33
+ <%= f.check_box :confidential, class: 'form-control', disabled: !Doorkeeper::Application.supports_confidentiality? %>
34
+ <%= doorkeeper_errors_for application, :confidential %>
35
+ <span class="help-block">
36
+ <%= t('doorkeeper.applications.help.confidential') %>
37
+ </span>
38
+ </div>
39
+ <% end %>
40
+
30
41
  <%= content_tag :div, class: "form-group#{' has-error' if application.errors[:scopes].present?}" do %>
31
42
  <%= f.label :scopes, class: 'col-sm-2 control-label' %>
32
43
  <div class="col-sm-10">
@@ -9,6 +9,7 @@
9
9
  <tr>
10
10
  <th><%= t('.name') %></th>
11
11
  <th><%= t('.callback_url') %></th>
12
+ <th><%= t('.confidential') %></th>
12
13
  <th></th>
13
14
  <th></th>
14
15
  </tr>
@@ -18,6 +19,7 @@
18
19
  <tr id="application_<%= application.id %>">
19
20
  <td><%= link_to application.name, oauth_application_path(application) %></td>
20
21
  <td><%= application.redirect_uri %></td>
22
+ <td><%= application.confidential? ? t('doorkeeper.applications.index.confidentiality.yes') : t('doorkeeper.applications.index.confidentiality.no') %></td>
21
23
  <td><%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(application), class: 'btn btn-link' %></td>
22
24
  <td><%= render 'delete_form', application: application %></td>
23
25
  </tr>
@@ -13,6 +13,9 @@
13
13
  <h4><%= t('.scopes') %>:</h4>
14
14
  <p><code id="scopes"><%= @application.scopes %></code></p>
15
15
 
16
+ <h4><%= t('.confidential') %>:</h4>
17
+ <p><code id="confidential"><%= @application.confidential? %></code></p>
18
+
16
19
  <h4><%= t('.callback_urls') %>:</h4>
17
20
 
18
21
  <table>
@@ -28,6 +28,7 @@ en:
28
28
  form:
29
29
  error: 'Whoops! Check your form for possible errors'
30
30
  help:
31
+ confidential: 'Application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential.'
31
32
  redirect_uri: 'Use one line per URI'
32
33
  native_redirect_uri: 'Use %{native_redirect_uri} if you want to add localhost URIs for development purposes'
33
34
  scopes: 'Separate scopes with spaces. Leave blank to use the default scopes.'
@@ -38,6 +39,10 @@ en:
38
39
  new: 'New Application'
39
40
  name: 'Name'
40
41
  callback_url: 'Callback URL'
42
+ confidential: 'Confidential?'
43
+ confidentiality:
44
+ 'yes': 'Yes'
45
+ 'no': 'No'
41
46
  new:
42
47
  title: 'New Application'
43
48
  show:
@@ -45,6 +50,7 @@ en:
45
50
  application_id: 'Application Id'
46
51
  secret: 'Secret'
47
52
  scopes: 'Scopes'
53
+ confidential: 'Confidential'
48
54
  callback_urls: 'Callback urls'
49
55
  actions: 'Actions'
50
56
 
data/doorkeeper.gemspec CHANGED
@@ -27,4 +27,6 @@ Gem::Specification.new do |s|
27
27
  s.add_development_dependency "generator_spec", "~> 0.9.3"
28
28
  s.add_development_dependency "rake", ">= 11.3.0"
29
29
  s.add_development_dependency "rspec-rails"
30
+
31
+ s.post_install_message = Doorkeeper::CVE_2018_1000211_WARNING
30
32
  end
@@ -156,7 +156,7 @@ module Doorkeeper
156
156
  # The OAuth 2.0 Authorization Framework: Bearer Token Usage
157
157
  #
158
158
  def token_type
159
- 'bearer'
159
+ 'Bearer'
160
160
  end
161
161
 
162
162
  def use_refresh_token?
@@ -10,6 +10,9 @@ module Doorkeeper
10
10
  # Returns an instance of the Doorkeeper::Application with
11
11
  # specific UID and secret.
12
12
  #
13
+ # Public/Non-confidential applications will only find by uid if secret is
14
+ # blank.
15
+ #
13
16
  # @param uid [#to_s] UID (any object that responds to `#to_s`)
14
17
  # @param secret [#to_s] secret (any object that responds to `#to_s`)
15
18
  #
@@ -17,7 +20,11 @@ module Doorkeeper
17
20
  # if there is no record with such credentials
18
21
  #
19
22
  def by_uid_and_secret(uid, secret)
20
- find_by(uid: uid.to_s, secret: secret.to_s)
23
+ app = by_uid(uid)
24
+ return unless app
25
+ return app if secret.blank? && !app.confidential?
26
+ return unless app.secret == secret
27
+ app
21
28
  end
22
29
 
23
30
  # Returns an instance of the Doorkeeper::Application with specific UID.
@@ -23,8 +23,10 @@ module Doorkeeper
23
23
  end
24
24
  end
25
25
 
26
+ # Public clients may have their secret blank, but "credentials" are
27
+ # still present
26
28
  def blank?
27
- uid.blank? || secret.blank?
29
+ uid.blank?
28
30
  end
29
31
  end
30
32
  end
@@ -3,6 +3,7 @@ module Doorkeeper
3
3
  module Helpers
4
4
  module URIChecker
5
5
  def self.valid?(url)
6
+ return true if native_uri?(url)
6
7
  uri = as_uri(url)
7
8
  uri.fragment.nil? && !uri.host.nil? && !uri.scheme.nil?
8
9
  rescue URI::InvalidURIError
@@ -12,6 +13,13 @@ module Doorkeeper
12
13
  def self.matches?(url, client_url)
13
14
  url = as_uri(url)
14
15
  client_url = as_uri(client_url)
16
+
17
+ if client_url.query.present?
18
+ return false unless query_matches?(url.query, client_url.query)
19
+ # Clear out queries so rest of URI can be tested. This allows query
20
+ # params to be in the request but order not mattering.
21
+ client_url.query = nil
22
+ end
15
23
  url.query = nil
16
24
  url == client_url
17
25
  end
@@ -24,6 +32,13 @@ module Doorkeeper
24
32
  URI.parse(url)
25
33
  end
26
34
 
35
+ def self.query_matches?(query, client_query)
36
+ return true if client_query.nil? && query.nil?
37
+ return false if client_query.nil? || query.nil?
38
+ # Will return true independent of query order
39
+ client_query.split('&').sort == query.split('&').sort
40
+ end
41
+
27
42
  def self.native_uri?(url)
28
43
  url == Doorkeeper.configuration.native_redirect_uri
29
44
  end
@@ -57,9 +57,11 @@ module Doorkeeper
57
57
 
58
58
  # TODO: test uri should be matched against the client's one
59
59
  def validate_redirect_uri
60
- return false unless redirect_uri.present?
61
- Helpers::URIChecker.native_uri?(redirect_uri) ||
62
- Helpers::URIChecker.valid_for_authorization?(redirect_uri, client.redirect_uri)
60
+ return false if redirect_uri.blank?
61
+
62
+ Helpers::URIChecker.valid_for_authorization?(
63
+ redirect_uri, client.redirect_uri
64
+ )
63
65
  end
64
66
  end
65
67
  end
@@ -11,6 +11,7 @@ module Doorkeeper
11
11
  validates :name, :secret, :uid, presence: true
12
12
  validates :uid, uniqueness: true
13
13
  validates :redirect_uri, redirect_uri: true
14
+ validates :confidential, inclusion: { in: [true, false] }
14
15
 
15
16
  before_validation :generate_uid, :generate_secret, on: :create
16
17
 
@@ -31,6 +32,22 @@ module Doorkeeper
31
32
  where(id: resource_access_tokens.select(:application_id).distinct)
32
33
  end
33
34
 
35
+ # Fallback to existing, default behaviour of assuming all apps to be
36
+ # confidential if the migration hasn't been run
37
+ def confidential
38
+ return super if self.class.supports_confidentiality?
39
+ ActiveSupport::Deprecation.warn 'You are susceptible to security bug ' \
40
+ 'CVE-2018-1000211. Please follow instructions outlined in ' \
41
+ 'Doorkeeper::CVE_2018_1000211_WARNING'
42
+ true
43
+ end
44
+
45
+ alias_method :confidential?, :confidential
46
+
47
+ def self.supports_confidentiality?
48
+ column_names.include?('confidential')
49
+ end
50
+
34
51
  private
35
52
 
36
53
  def generate_uid
@@ -3,7 +3,7 @@ require 'doorkeeper/request/strategy'
3
3
  module Doorkeeper
4
4
  module Request
5
5
  class Password < Strategy
6
- delegate :credentials, :resource_owner, :parameters, to: :server
6
+ delegate :credentials, :resource_owner, :parameters, :client, to: :server
7
7
 
8
8
  def request
9
9
  @request ||= OAuth::PasswordAccessTokenRequest.new(
@@ -13,16 +13,6 @@ module Doorkeeper
13
13
  parameters
14
14
  )
15
15
  end
16
-
17
- private
18
-
19
- def client
20
- if credentials
21
- server.client
22
- elsif parameters[:client_id]
23
- server.client_via_uid
24
- end
25
- end
26
16
  end
27
17
  end
28
18
  end
@@ -1,4 +1,25 @@
1
1
  module Doorkeeper
2
+ CVE_2018_1000211_WARNING = <<-HEREDOC.freeze
3
+
4
+
5
+ WARNING: This is a security release that addresses token revocation not working for public apps (CVE-2018-1000211)
6
+
7
+ There is no breaking change in this release, however to take advantage of the security fix you must:
8
+
9
+ 1. Run `rails generate doorkeeper:add_client_confidentiality` for the migration
10
+ 2. Review your OAuth apps and determine which ones exclusively use public grant flows (eg implicit)
11
+ 3. Update their `confidential` column to `false` for those public apps
12
+
13
+ This is a backported security release.
14
+
15
+ For more information:
16
+
17
+ * https://github.com/doorkeeper-gem/doorkeeper/pull/1119
18
+ * https://github.com/doorkeeper-gem/doorkeeper/issues/891
19
+
20
+
21
+ HEREDOC
22
+
2
23
  def self.gem_version
3
24
  Gem::Version.new VERSION::STRING
4
25
  end
@@ -6,8 +27,8 @@ module Doorkeeper
6
27
  module VERSION
7
28
  # Semantic versioning
8
29
  MAJOR = 4
9
- MINOR = 3
10
- TINY = 1
30
+ MINOR = 4
31
+ TINY = 2
11
32
 
12
33
  # Full version number
13
34
  STRING = [MAJOR, MINOR, TINY].compact.join('.')
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/active_record'
4
+
5
+ module Doorkeeper
6
+ class AddClientConfidentialityGenerator < ::Rails::Generators::Base
7
+ include ::Rails::Generators::Migration
8
+ source_root File.expand_path('templates', __dir__)
9
+ desc 'Adds a migration to fix CVE-2018-1000211.'
10
+
11
+ def install
12
+ migration_template(
13
+ 'add_confidential_to_application_migration.rb.erb',
14
+ 'db/migrate/add_confidential_to_doorkeeper_application.rb',
15
+ migration_version: migration_version
16
+ )
17
+ end
18
+
19
+ def self.next_migration_number(dirname)
20
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
21
+ end
22
+
23
+ private
24
+
25
+ def migration_version
26
+ if ::ActiveRecord::VERSION::MAJOR >= 5
27
+ "[#{::ActiveRecord::VERSION::MAJOR}.#{::ActiveRecord::VERSION::MINOR}]"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ class AddConfidentialToDoorkeeperApplication < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ add_column(
4
+ :oauth_applications,
5
+ :confidential,
6
+ :boolean,
7
+ null: false,
8
+ default: true # maintaining backwards compatibility: require secrets
9
+ )
10
+ end
11
+ end
@@ -6,6 +6,7 @@ class CreateDoorkeeperTables < ActiveRecord::Migration<%= migration_version %>
6
6
  t.string :secret, null: false
7
7
  t.text :redirect_uri, null: false
8
8
  t.string :scopes, null: false, default: ''
9
+ t.boolean :confidential, null: false, default: true
9
10
  t.timestamps null: false
10
11
  end
11
12
 
@@ -54,7 +54,7 @@ describe Doorkeeper::AuthorizationsController, 'implicit grant flow' do
54
54
  end
55
55
 
56
56
  it 'includes token type in fragment' do
57
- expect(response.query_params['token_type']).to eq('bearer')
57
+ expect(response.query_params['token_type']).to eq('Bearer')
58
58
  end
59
59
 
60
60
  it 'includes token expiration in fragment' do
@@ -184,7 +184,7 @@ describe Doorkeeper::AuthorizationsController, 'implicit grant flow' do
184
184
  end
185
185
 
186
186
  it 'includes token type in fragment' do
187
- expect(response.query_params['token_type']).to eq('bearer')
187
+ expect(response.query_params['token_type']).to eq('Bearer')
188
188
  end
189
189
 
190
190
  it 'includes token expiration in fragment' do
@@ -59,15 +59,67 @@ describe Doorkeeper::TokensController do
59
59
  end
60
60
  end
61
61
 
62
- describe 'when revoke authorization has failed' do
63
- # http://tools.ietf.org/html/rfc7009#section-2.2
64
- it 'returns no error response' do
65
- token = double(:token, authorize: false, application_id?: true)
66
- allow(controller).to receive(:token) { token }
62
+ # http://tools.ietf.org/html/rfc7009#section-2.2
63
+ describe 'revoking tokens' do
64
+ let(:client) { FactoryBot.create(:application) }
65
+ let(:access_token) { FactoryBot.create(:access_token, application: client) }
66
+
67
+ before(:each) do
68
+ allow(controller).to receive(:token) { access_token }
69
+ end
70
+
71
+ context 'when associated app is public' do
72
+ let(:client) { FactoryBot.create(:application, confidential: false) }
73
+
74
+ it 'returns 200' do
75
+ post :revoke
76
+
77
+ expect(response.status).to eq 200
78
+ end
79
+
80
+ it 'revokes the access token' do
81
+ post :revoke
82
+
83
+ expect(access_token.reload).to have_attributes(revoked?: true)
84
+ end
85
+ end
86
+
87
+ context 'when associated app is confidential' do
88
+ let(:client) { FactoryBot.create(:application, confidential: true) }
89
+ let(:oauth_client) { Doorkeeper::OAuth::Client.new(client) }
67
90
 
68
- post :revoke
91
+ before(:each) do
92
+ allow_any_instance_of(Doorkeeper::Server).to receive(:client) { oauth_client }
93
+ end
94
+
95
+ it 'returns 200' do
96
+ post :revoke
97
+
98
+ expect(response.status).to eq 200
99
+ end
100
+
101
+ it 'revokes the access token' do
102
+ post :revoke
103
+
104
+ expect(access_token.reload).to have_attributes(revoked?: true)
105
+ end
106
+
107
+ context 'when authorization fails' do
108
+ let(:some_other_client) { FactoryBot.create(:application, confidential: true) }
109
+ let(:oauth_client) { Doorkeeper::OAuth::Client.new(some_other_client) }
110
+
111
+ it 'returns 200' do
112
+ post :revoke
69
113
 
70
- expect(response.status).to eq 200
114
+ expect(response.status).to eq 200
115
+ end
116
+
117
+ it 'does not revoke the access token' do
118
+ post :revoke
119
+
120
+ expect(access_token.reload).to have_attributes(revoked?: false)
121
+ end
122
+ end
71
123
  end
72
124
  end
73
125
 
@@ -1,4 +1,6 @@
1
- class CreateUsers < ActiveRecord::Migration
1
+ # frozen_string_literal: true
2
+
3
+ class CreateUsers < ActiveRecord::Migration[4.2]
2
4
  def change
3
5
  create_table :users do |t|
4
6
  t.string :name
@@ -1,4 +1,6 @@
1
- class AddPasswordToUsers < ActiveRecord::Migration
1
+ # frozen_string_literal: true
2
+
3
+ class AddPasswordToUsers < ActiveRecord::Migration[4.2]
2
4
  def change
3
5
  add_column :users, :password, :string
4
6
  end
@@ -1,4 +1,6 @@
1
- class CreateDoorkeeperTables < ActiveRecord::Migration
1
+ # frozen_string_literal: true
2
+
3
+ class CreateDoorkeeperTables < ActiveRecord::Migration[4.2]
2
4
  def change
3
5
  create_table :oauth_applications do |t|
4
6
  t.string :name, null: false
@@ -1,4 +1,6 @@
1
- class AddOwnerToApplication < ActiveRecord::Migration
1
+ # frozen_string_literal: true
2
+
3
+ class AddOwnerToApplication < ActiveRecord::Migration[4.2]
2
4
  def change
3
5
  add_column :oauth_applications, :owner_id, :integer, null: true
4
6
  add_column :oauth_applications, :owner_type, :string, null: true
@@ -1,4 +1,6 @@
1
- class AddPreviousRefreshTokenToAccessTokens < ActiveRecord::Migration
1
+ # frozen_string_literal: true
2
+
3
+ class AddPreviousRefreshTokenToAccessTokens < ActiveRecord::Migration[4.2]
2
4
  def change
3
5
  add_column(
4
6
  :oauth_access_tokens,
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddConfidentialToApplication < ActiveRecord::Migration[5.1]
4
+ def change
5
+ add_column(
6
+ :oauth_applications,
7
+ :confidential,
8
+ :boolean,
9
+ null: false,
10
+ default: true # maintaining backwards compatibility: require secrets
11
+ )
12
+ end
13
+ end
@@ -11,7 +11,7 @@
11
11
  #
12
12
  # It's strongly recommended that you check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(version: 20160320211015) do
14
+ ActiveRecord::Schema.define(version: 20180210183654) do
15
15
 
16
16
  create_table "oauth_access_grants", force: :cascade do |t|
17
17
  t.integer "resource_owner_id", null: false
@@ -52,6 +52,7 @@ ActiveRecord::Schema.define(version: 20160320211015) do
52
52
  t.datetime "updated_at"
53
53
  t.integer "owner_id"
54
54
  t.string "owner_type"
55
+ t.boolean "confidential", default: true, null: false
55
56
  end
56
57
 
57
58
  add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type"
@@ -104,5 +104,20 @@ module Doorkeeper::OAuth
104
104
  expect(subject.error).to eq(:invalid_grant)
105
105
  end
106
106
  end
107
+
108
+ context "when redirect_uri is the native one" do
109
+ let(:redirect_uri) { 'urn:ietf:wg:oauth:2.0:oob' }
110
+
111
+ it "invalidates when redirect_uri of the grant is not native" do
112
+ subject.validate
113
+ expect(subject.error).to eq(:invalid_grant)
114
+ end
115
+
116
+ it "validates when redirect_uri of the grant is also native" do
117
+ allow(grant).to receive(:redirect_uri) { redirect_uri }
118
+ subject.validate
119
+ expect(subject.error).to eq(nil)
120
+ end
121
+ end
107
122
  end
108
123
  end
@@ -7,9 +7,11 @@ class Doorkeeper::OAuth::Client
7
7
  let(:client_id) { 'some-uid' }
8
8
  let(:client_secret) { 'some-secret' }
9
9
 
10
- it 'is blank when any of the credentials is blank' do
10
+ it 'is blank when the uid in credentials is blank' do
11
+ expect(Credentials.new(nil, nil)).to be_blank
11
12
  expect(Credentials.new(nil, 'something')).to be_blank
12
- expect(Credentials.new('something', nil)).to be_blank
13
+ expect(Credentials.new('something', nil)).to be_present
14
+ expect(Credentials.new('something', 'something')).to be_present
13
15
  end
14
16
 
15
17
  describe :from_request do
@@ -5,6 +5,11 @@ require 'doorkeeper/oauth/helpers/uri_checker'
5
5
  module Doorkeeper::OAuth::Helpers
6
6
  describe URIChecker do
7
7
  describe '.valid?' do
8
+ it 'is valid for native uris' do
9
+ uri = 'urn:ietf:wg:oauth:2.0:oob'
10
+ expect(URIChecker.valid?(uri)).to be_truthy
11
+ end
12
+
8
13
  it 'is valid for valid uris' do
9
14
  uri = 'http://app.co'
10
15
  expect(URIChecker.valid?(uri)).to be_truthy
@@ -69,6 +74,44 @@ module Doorkeeper::OAuth::Helpers
69
74
  client_uri = 'http://example.com?app.co=test'
70
75
  expect(URIChecker.matches?(uri, client_uri)).to be_falsey
71
76
  end
77
+
78
+ context "client registered query params" do
79
+ it "doesn't allow query being absent" do
80
+ uri = 'http://app.co'
81
+ client_uri = 'http://app.co/?vendorId=AJ4L7XXW9'
82
+ expect(URIChecker.matches?(uri, client_uri)).to be_falsey
83
+ end
84
+
85
+ it "is false if query values differ but key same" do
86
+ uri = 'http://app.co/?vendorId=pancakes'
87
+ client_uri = 'http://app.co/?vendorId=waffles'
88
+ expect(URIChecker.matches?(uri, client_uri)).to be_falsey
89
+ end
90
+
91
+ it "is false if query values same but key differs" do
92
+ uri = 'http://app.co/?foo=pancakes'
93
+ client_uri = 'http://app.co/?bar=pancakes'
94
+ expect(URIChecker.matches?(uri, client_uri)).to be_falsey
95
+ end
96
+
97
+ it "is false if query present and match, but unknown queries present" do
98
+ uri = 'http://app.co/?vendorId=pancakes&unknown=query'
99
+ client_uri = 'http://app.co/?vendorId=waffles'
100
+ expect(URIChecker.matches?(uri, client_uri)).to be_falsey
101
+ end
102
+
103
+ it "is true if queries are present and matche" do
104
+ uri = 'http://app.co/?vendorId=AJ4L7XXW9&foo=bar'
105
+ client_uri = 'http://app.co/?vendorId=AJ4L7XXW9&foo=bar'
106
+ expect(URIChecker.matches?(uri, client_uri)).to be_truthy
107
+ end
108
+
109
+ it "is true if queries are present, match and in different order" do
110
+ uri = 'http://app.co/?bing=bang&foo=bar'
111
+ client_uri = 'http://app.co/?foo=bar&bing=bang'
112
+ expect(URIChecker.matches?(uri, client_uri)).to be_truthy
113
+ end
114
+ end
72
115
  end
73
116
 
74
117
  describe '.valid_for_authorization?' do
@@ -101,9 +144,75 @@ module Doorkeeper::OAuth::Helpers
101
144
  end
102
145
 
103
146
  it 'is false if invalid' do
104
- uri = client_uri = 'http://app.co/aaa?waffles=abc'
147
+ uri = 'http://app.co/aaa?pankcakes=abc'
148
+ client_uri = 'http://app.co/aaa?waffles=abc'
105
149
  expect(URIChecker.valid_for_authorization?(uri, client_uri)).to be false
106
150
  end
151
+
152
+ it 'calls .matches?' do
153
+ uri = 'http://app.co/aaa?pankcakes=abc'
154
+ client_uri = 'http://app.co/aaa?waffles=abc'
155
+ expect(URIChecker).to receive(:matches?).with(uri, client_uri).once
156
+ URIChecker.valid_for_authorization?(uri, client_uri)
157
+ end
158
+
159
+ it 'calls .valid?' do
160
+ uri = 'http://app.co/aaa?pankcakes=abc'
161
+ client_uri = 'http://app.co/aaa?waffles=abc'
162
+ expect(URIChecker).to receive(:valid?).with(uri).once
163
+ URIChecker.valid_for_authorization?(uri, client_uri)
164
+ end
165
+ end
166
+
167
+ describe '.query_matches?' do
168
+ it 'is true if no queries' do
169
+ expect(URIChecker.query_matches?('', '')).to be_truthy
170
+ expect(URIChecker.query_matches?(nil, nil)).to be_truthy
171
+ end
172
+
173
+ it 'is true if same query' do
174
+ expect(URIChecker.query_matches?('foo', 'foo')).to be_truthy
175
+ end
176
+
177
+ it 'is false if different query' do
178
+ expect(URIChecker.query_matches?('foo', 'bar')).to be_falsey
179
+ end
180
+
181
+ it 'is true if same queries' do
182
+ expect(URIChecker.query_matches?('foo&bar', 'foo&bar')).to be_truthy
183
+ end
184
+
185
+ it 'is true if same queries, different order' do
186
+ expect(URIChecker.query_matches?('foo&bar', 'bar&foo')).to be_truthy
187
+ end
188
+
189
+ it 'is false if one different query' do
190
+ expect(URIChecker.query_matches?('foo&bang', 'foo&bing')).to be_falsey
191
+ end
192
+
193
+ it 'is true if same query with same value' do
194
+ expect(URIChecker.query_matches?('foo=bar', 'foo=bar')).to be_truthy
195
+ end
196
+
197
+ it 'is true if same queries with same values' do
198
+ expect(URIChecker.query_matches?('foo=bar&bing=bang', 'foo=bar&bing=bang')).to be_truthy
199
+ end
200
+
201
+ it 'is true if same queries with same values, different order' do
202
+ expect(URIChecker.query_matches?('foo=bar&bing=bang', 'bing=bang&foo=bar')).to be_truthy
203
+ end
204
+
205
+ it 'is false if same query with different value' do
206
+ expect(URIChecker.query_matches?('foo=bar', 'foo=bang')).to be_falsey
207
+ end
208
+
209
+ it 'is false if some queries missing' do
210
+ expect(URIChecker.query_matches?('foo=bar', 'foo=bar&bing=bang')).to be_falsey
211
+ end
212
+
213
+ it 'is false if some queries different value' do
214
+ expect(URIChecker.query_matches?('foo=bar&bing=bang', 'foo=bar&bing=banana')).to be_falsey
215
+ end
107
216
  end
108
217
  end
109
218
  end
@@ -123,14 +123,19 @@ module Doorkeeper::OAuth
123
123
  expect(subject.scopes).to eq(Scopes.from_string('default'))
124
124
  end
125
125
 
126
- it 'accepts test uri' do
127
- subject.redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
128
- expect(subject).to be_authorizable
129
- end
126
+ context 'with native redirect uri' do
127
+ let(:native_redirect_uri) { 'urn:ietf:wg:oauth:2.0:oob' }
130
128
 
131
- it 'matches the redirect uri against client\'s one' do
132
- subject.redirect_uri = 'http://nothesame.com'
133
- expect(subject).not_to be_authorizable
129
+ it 'accepts redirect_uri when it matches with the client' do
130
+ subject.redirect_uri = native_redirect_uri
131
+ allow(subject.client).to receive(:redirect_uri) { native_redirect_uri }
132
+ expect(subject).to be_authorizable
133
+ end
134
+
135
+ it 'invalidates redirect_uri when it does\'n match with the client' do
136
+ subject.redirect_uri = native_redirect_uri
137
+ expect(subject).not_to be_authorizable
138
+ end
134
139
  end
135
140
 
136
141
  it 'stores the state' do
@@ -49,6 +49,11 @@ module Doorkeeper
49
49
  expect(new_application).not_to be_valid
50
50
  end
51
51
 
52
+ it 'is invalid without determining confidentiality' do
53
+ new_application.confidential = nil
54
+ expect(new_application).not_to be_valid
55
+ end
56
+
52
57
  it 'generates uid on create' do
53
58
  expect(new_application.uid).to be_nil
54
59
  new_application.save
@@ -201,11 +206,97 @@ module Doorkeeper
201
206
  end
202
207
  end
203
208
 
204
- describe :authenticate do
205
- it 'finds the application via uid/secret' do
206
- app = FactoryBot.create :application
207
- authenticated = Application.by_uid_and_secret(app.uid, app.secret)
208
- expect(authenticated).to eq(app)
209
+ describe :by_uid_and_secret do
210
+ context "when application is private/confidential" do
211
+ it "finds the application via uid/secret" do
212
+ app = FactoryBot.create :application
213
+ authenticated = Application.by_uid_and_secret(app.uid, app.secret)
214
+ expect(authenticated).to eq(app)
215
+ end
216
+ context "when secret is wrong" do
217
+ it "should not find the application" do
218
+ app = FactoryBot.create :application
219
+ authenticated = Application.by_uid_and_secret(app.uid, 'bad')
220
+ expect(authenticated).to eq(nil)
221
+ end
222
+ end
223
+ end
224
+
225
+ context "when application is public/non-confidential" do
226
+ context "when secret is blank" do
227
+ it "should find the application" do
228
+ app = FactoryBot.create :application, confidential: false
229
+ authenticated = Application.by_uid_and_secret(app.uid, nil)
230
+ expect(authenticated).to eq(app)
231
+ end
232
+ end
233
+ context "when secret is wrong" do
234
+ it "should not find the application" do
235
+ app = FactoryBot.create :application, confidential: false
236
+ authenticated = Application.by_uid_and_secret(app.uid, 'bad')
237
+ expect(authenticated).to eq(nil)
238
+ end
239
+ end
240
+ end
241
+ end
242
+
243
+ describe :confidential? do
244
+ subject { FactoryBot.create(:application, confidential: confidential).confidential? }
245
+
246
+ context 'when application is private/confidential' do
247
+ let(:confidential) { true }
248
+ it { expect(subject).to eq(true) }
249
+ end
250
+
251
+ context 'when application is public/non-confidential' do
252
+ let(:confidential) { false }
253
+ it { expect(subject).to eq(false) }
254
+ end
255
+ end
256
+
257
+ describe :confidential do
258
+ subject { FactoryBot.create(:application, confidential: confidential).confidential }
259
+
260
+ context 'when application is private/confidential' do
261
+ let(:confidential) { true }
262
+ it { expect(subject).to eq(true) }
263
+ end
264
+
265
+ context 'when application is public/non-confidential' do
266
+ let(:confidential) { false }
267
+ it { expect(subject).to eq(false) }
268
+ end
269
+
270
+ context 'when the application does not support confidentiality' do
271
+ let(:confidential) { false }
272
+
273
+ before { allow(Application).to receive(:supports_confidentiality?).and_return(false) }
274
+
275
+ it 'warns of the CVE' do
276
+ expect(ActiveSupport::Deprecation).to receive(:warn).with(
277
+ 'You are susceptible to security bug ' \
278
+ 'CVE-2018-1000211. Please follow instructions outlined in ' \
279
+ 'Doorkeeper::CVE_2018_1000211_WARNING'
280
+ )
281
+ Application.new.confidential
282
+ end
283
+
284
+ it { expect(subject).to eq(true) }
285
+ end
286
+ end
287
+
288
+ describe :supports_confidentiality? do
289
+ context 'when no column' do
290
+ it 'returns false' do
291
+ expect(Application).to receive(:column_names).and_return(%w[foo bar])
292
+ expect(Application.supports_confidentiality?).to eq(false)
293
+ end
294
+ end
295
+ context 'when column' do
296
+ it 'returns true' do
297
+ expect(Application).to receive(:column_names).and_return(%w[foo bar confidential])
298
+ expect(Application.supports_confidentiality?).to eq(true)
299
+ end
209
300
  end
210
301
  end
211
302
  end
@@ -53,7 +53,7 @@ feature 'Authorization Code Flow' do
53
53
  should_not_have_json 'error'
54
54
 
55
55
  should_have_json 'access_token', Doorkeeper::AccessToken.first.token
56
- should_have_json 'token_type', 'bearer'
56
+ should_have_json 'token_type', 'Bearer'
57
57
  should_have_json_within 'expires_in', Doorkeeper::AccessToken.first.expires_in, 1
58
58
  end
59
59
 
@@ -10,46 +10,89 @@ describe 'Resource Owner Password Credentials Flow not set up' do
10
10
  it 'doesn\'t issue new token' do
11
11
  expect do
12
12
  post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
13
- end.to_not change { Doorkeeper::AccessToken.count }
13
+ end.to_not(change { Doorkeeper::AccessToken.count })
14
14
  end
15
15
  end
16
16
  end
17
17
 
18
18
  describe 'Resource Owner Password Credentials Flow' do
19
+ let(:client_attributes) { {} }
20
+
19
21
  before do
20
22
  config_is_set(:grant_flows, ["password"])
21
23
  config_is_set(:resource_owner_from_credentials) { User.authenticate! params[:username], params[:password] }
22
- client_exists
24
+ client_exists(client_attributes)
23
25
  create_resource_owner
24
26
  end
25
27
 
26
28
  context 'with valid user credentials' do
27
- it 'should issue new token with confidential client' do
28
- expect do
29
- post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
30
- end.to change { Doorkeeper::AccessToken.count }.by(1)
29
+ context "with non-confidential/public client" do
30
+ let(:client_attributes) { { confidential: false } }
31
31
 
32
- token = Doorkeeper::AccessToken.first
32
+ context "when client_secret absent" do
33
+ it "should issue new token" do
34
+ expect do
35
+ post password_token_endpoint_url(client_id: @client.uid, resource_owner: @resource_owner)
36
+ end.to change { Doorkeeper::AccessToken.count }.by(1)
33
37
 
34
- expect(token.application_id).to eq @client.id
35
- should_have_json 'access_token', token.token
38
+ token = Doorkeeper::AccessToken.first
39
+
40
+ expect(token.application_id).to eq @client.id
41
+ should_have_json 'access_token', token.token
42
+ end
43
+ end
44
+
45
+ context "when client_secret present" do
46
+ it "should issue new token" do
47
+ expect do
48
+ post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
49
+ end.to change { Doorkeeper::AccessToken.count }.by(1)
50
+
51
+ token = Doorkeeper::AccessToken.first
52
+
53
+ expect(token.application_id).to eq @client.id
54
+ should_have_json 'access_token', token.token
55
+ end
56
+
57
+ context "when client_secret incorrect" do
58
+ it "should not issue new token" do
59
+ expect do
60
+ post password_token_endpoint_url(client_id: @client.uid, client_secret: 'foobar', resource_owner: @resource_owner)
61
+ end.not_to(change { Doorkeeper::AccessToken.count })
62
+
63
+ expect(response).not_to be_ok
64
+ end
65
+ end
66
+ end
36
67
  end
37
68
 
38
- it 'should issue new token with public client (only client_id present)' do
39
- expect do
40
- post password_token_endpoint_url(client_id: @client.uid, resource_owner: @resource_owner)
41
- end.to change { Doorkeeper::AccessToken.count }.by(1)
69
+ context "with confidential/private client" do
70
+ it "should issue new token" do
71
+ expect do
72
+ post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
73
+ end.to change { Doorkeeper::AccessToken.count }.by(1)
42
74
 
43
- token = Doorkeeper::AccessToken.first
75
+ token = Doorkeeper::AccessToken.first
44
76
 
45
- expect(token.application_id).to eq @client.id
46
- should_have_json 'access_token', token.token
77
+ expect(token.application_id).to eq @client.id
78
+ should_have_json 'access_token', token.token
79
+ end
80
+
81
+ context "when client_secret absent" do
82
+ it "should not issue new token" do
83
+ expect do
84
+ post password_token_endpoint_url(client_id: @client.uid, resource_owner: @resource_owner)
85
+ end.not_to(change { Doorkeeper::AccessToken.count })
86
+
87
+ expect(response).not_to be_ok
88
+ end
89
+ end
47
90
  end
48
91
 
49
92
  it 'should issue new token without client credentials' do
50
93
  expect do
51
94
  post password_token_endpoint_url(resource_owner: @resource_owner)
52
- end.to change { Doorkeeper::AccessToken.count }.by(1)
95
+ end.to(change { Doorkeeper::AccessToken.count }.by(1))
53
96
 
54
97
  token = Doorkeeper::AccessToken.first
55
98
 
@@ -124,13 +167,13 @@ describe 'Resource Owner Password Credentials Flow' do
124
167
  post password_token_endpoint_url(client: @client,
125
168
  resource_owner_username: @resource_owner.name,
126
169
  resource_owner_password: 'wrongpassword')
127
- end.to_not change { Doorkeeper::AccessToken.count }
170
+ end.to_not(change { Doorkeeper::AccessToken.count })
128
171
  end
129
172
 
130
173
  it 'should not issue new token without credentials' do
131
174
  expect do
132
175
  post password_token_endpoint_url(client: @client)
133
- end.to_not change { Doorkeeper::AccessToken.count }
176
+ end.to_not(change { Doorkeeper::AccessToken.count })
134
177
  end
135
178
  end
136
179
 
@@ -140,7 +183,7 @@ describe 'Resource Owner Password Credentials Flow' do
140
183
  post password_token_endpoint_url(client_id: @client.uid,
141
184
  client_secret: 'bad_secret',
142
185
  resource_owner: @resource_owner)
143
- end.to_not change { Doorkeeper::AccessToken.count }
186
+ end.to_not(change { Doorkeeper::AccessToken.count })
144
187
  end
145
188
  end
146
189
 
@@ -148,7 +191,7 @@ describe 'Resource Owner Password Credentials Flow' do
148
191
  it 'should not issue new token with bad client id' do
149
192
  expect do
150
193
  post password_token_endpoint_url(client_id: 'bad_id', resource_owner: @resource_owner)
151
- end.to_not change { Doorkeeper::AccessToken.count }
194
+ end.to_not(change { Doorkeeper::AccessToken.count })
152
195
  end
153
196
  end
154
197
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doorkeeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.1
4
+ version: 4.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felipe Elias Philipp
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2018-03-03 00:00:00.000000000 Z
14
+ date: 2018-08-20 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: railties
@@ -259,11 +259,13 @@ files:
259
259
  - lib/doorkeeper/server.rb
260
260
  - lib/doorkeeper/validations.rb
261
261
  - lib/doorkeeper/version.rb
262
+ - lib/generators/doorkeeper/add_client_confidentiality_generator.rb
262
263
  - lib/generators/doorkeeper/application_owner_generator.rb
263
264
  - lib/generators/doorkeeper/install_generator.rb
264
265
  - lib/generators/doorkeeper/migration_generator.rb
265
266
  - lib/generators/doorkeeper/previous_refresh_token_generator.rb
266
267
  - lib/generators/doorkeeper/templates/README
268
+ - lib/generators/doorkeeper/templates/add_confidential_to_application_migration.rb.erb
267
269
  - lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb.erb
268
270
  - lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb.erb
269
271
  - lib/generators/doorkeeper/templates/initializer.rb
@@ -307,6 +309,7 @@ files:
307
309
  - spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb
308
310
  - spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb
309
311
  - spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb
312
+ - spec/dummy/db/migrate/20180210183654_add_confidential_to_application.rb
310
313
  - spec/dummy/db/schema.rb
311
314
  - spec/dummy/public/404.html
312
315
  - spec/dummy/public/422.html
@@ -397,7 +400,25 @@ homepage: https://github.com/doorkeeper-gem/doorkeeper
397
400
  licenses:
398
401
  - MIT
399
402
  metadata: {}
400
- post_install_message:
403
+ post_install_message: |2+
404
+
405
+
406
+ WARNING: This is a security release that addresses token revocation not working for public apps (CVE-2018-1000211)
407
+
408
+ There is no breaking change in this release, however to take advantage of the security fix you must:
409
+
410
+ 1. Run `rails generate doorkeeper:add_client_confidentiality` for the migration
411
+ 2. Review your OAuth apps and determine which ones exclusively use public grant flows (eg implicit)
412
+ 3. Update their `confidential` column to `false` for those public apps
413
+
414
+ This is a backported security release.
415
+
416
+ For more information:
417
+
418
+ * https://github.com/doorkeeper-gem/doorkeeper/pull/1119
419
+ * https://github.com/doorkeeper-gem/doorkeeper/issues/891
420
+
421
+
401
422
  rdoc_options: []
402
423
  require_paths:
403
424
  - lib
@@ -456,6 +477,7 @@ test_files:
456
477
  - spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb
457
478
  - spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb
458
479
  - spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb
480
+ - spec/dummy/db/migrate/20180210183654_add_confidential_to_application.rb
459
481
  - spec/dummy/db/schema.rb
460
482
  - spec/dummy/public/404.html
461
483
  - spec/dummy/public/422.html