doorkeeper 4.3.2 → 4.4.0

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/NEWS.md +4 -0
  4. data/app/controllers/doorkeeper/applications_controller.rb +2 -1
  5. data/app/controllers/doorkeeper/tokens_controller.rb +9 -10
  6. data/app/views/doorkeeper/applications/_form.html.erb +11 -0
  7. data/app/views/doorkeeper/applications/index.html.erb +2 -0
  8. data/app/views/doorkeeper/applications/show.html.erb +3 -0
  9. data/config/locales/en.yml +6 -0
  10. data/doorkeeper.gemspec +2 -0
  11. data/lib/doorkeeper/models/application_mixin.rb +8 -1
  12. data/lib/doorkeeper/oauth/client/credentials.rb +3 -1
  13. data/lib/doorkeeper/orm/active_record/application.rb +17 -0
  14. data/lib/doorkeeper/request/password.rb +1 -11
  15. data/lib/doorkeeper/version.rb +23 -2
  16. data/lib/generators/doorkeeper/add_client_confidentiality_generator.rb +31 -0
  17. data/lib/generators/doorkeeper/templates/add_confidential_to_application_migration.rb.erb +11 -0
  18. data/lib/generators/doorkeeper/templates/migration.rb.erb +1 -0
  19. data/spec/controllers/tokens_controller_spec.rb +59 -7
  20. data/spec/dummy/db/migrate/20111122132257_create_users.rb +3 -1
  21. data/spec/dummy/db/migrate/20120312140401_add_password_to_users.rb +3 -1
  22. data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +3 -1
  23. data/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb +3 -1
  24. data/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb +3 -1
  25. data/spec/dummy/db/migrate/20180210183654_add_confidential_to_application.rb +13 -0
  26. data/spec/dummy/db/schema.rb +2 -1
  27. data/spec/lib/oauth/client/credentials_spec.rb +4 -2
  28. data/spec/models/doorkeeper/application_spec.rb +79 -5
  29. data/spec/requests/flows/password_spec.rb +64 -21
  30. metadata +25 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c3122dbcfcc7470b2460319fee999036c26810c3
4
- data.tar.gz: 9c0096009a2af6d32074d63711fb72a2078101a1
3
+ metadata.gz: f0d71c132c7d1ccefc8c14eb694b17390f294153
4
+ data.tar.gz: db67a65aac1502b753acb20ea125fa4b5a87b613
5
5
  SHA512:
6
- metadata.gz: 7178420e74461146ac3525056a9398e51df2e659107532e1e8577e90359d474a96b1275d99f05dc85b43f692ab9eda1e49bb1edc91c80f906d59318c5e788163
7
- data.tar.gz: 5c6a9177e6efd616f7345bc26e15a978ae7d89295600356bf9ec922a0c94f0da1b47fe827328d7edcbcafe0855761a17a77851d3a1b9155d0c8608917c3e4610
6
+ metadata.gz: fd03d7d674e053e08d4eaaa5b71d60290983d1d0376bddf4eacd01c903bffbf495dac6d3c56ff4e42cd91a47084eebb8332332553e239eb964cf79a0949087d0
7
+ data.tar.gz: 12873f75f22927c542c3d3bd739b5b1ec1ed0b89135bd1e492106e5d94d4485f8ca0fc89b6dcedf1d5a8707d9ff3cfc9f6c4d539a51a86c7dd23633477adfc57
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,6 +4,10 @@ User-visible changes worth mentioning.
4
4
 
5
5
  ## master
6
6
 
7
+ ## 4.4.0
8
+
9
+ - [#1120] Backport security fix from 5.x for token revocation when using public clients
10
+
7
11
  ## 4.3.2
8
12
 
9
13
  - [#1053] Support authorizing with query params in the request `redirect_uri` if explicitly present in app's `Application#redirect_uri`
@@ -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
@@ -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
@@ -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 = 2
30
+ MINOR = 4
31
+ TINY = 0
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
 
@@ -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"
@@ -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
@@ -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,80 @@ 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
+ end
270
+
271
+ describe :supports_confidentiality? do
272
+ context 'when no column' do
273
+ it 'returns false' do
274
+ expect(Application).to receive(:column_names).and_return(%w[foo bar])
275
+ expect(Application.supports_confidentiality?).to eq(false)
276
+ end
277
+ end
278
+ context 'when column' do
279
+ it 'returns true' do
280
+ expect(Application).to receive(:column_names).and_return(%w[foo bar confidential])
281
+ expect(Application.supports_confidentiality?).to eq(true)
282
+ end
209
283
  end
210
284
  end
211
285
  end
@@ -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.2
4
+ version: 4.4.0
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-28 00:00:00.000000000 Z
14
+ date: 2018-07-17 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