Skip to content

Commit aabe3aa

Browse files
committed
feature: Support max_age parameter
1 parent 741fff2 commit aabe3aa

10 files changed

Lines changed: 137 additions & 25 deletions

File tree

config/locales/en.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ en:
1010
messages:
1111
# Configuration error messages
1212
resource_owner_from_access_token_not_configured: 'Failure due to Doorkeeper::OpenidConnect.configure.resource_owner_from_access_token missing configuration.'
13+
auth_time_from_resource_owner_not_configured: 'Failure due to Doorkeeper::OpenidConnect.configure.auth_time_from_resource_owner missing configuration.'
14+
reauthenticate_resource_owner_not_configured: 'Failure due to Doorkeeper::OpenidConnect.configure.reauthenticate_resource_owner missing configuration.'
1315
subject_not_configured: 'ID Token generation failed due to Doorkeeper::OpenidConnect.configure.subject missing configuration.'

lib/doorkeeper/openid_connect/config.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ def extended(base)
110110
fail ConfigurationError, I18n.translate('doorkeeper.openid_connect.errors.messages.resource_owner_from_access_token_not_configured')
111111
}
112112

113+
option :auth_time_from_resource_owner, default: lambda { |*_|
114+
fail ConfigurationError, I18n.translate('doorkeeper.openid_connect.errors.messages.auth_time_from_resource_owner_not_configured')
115+
}
116+
117+
option :reauthenticate_resource_owner, default: lambda { |*_|
118+
fail ConfigurationError, I18n.translate('doorkeeper.openid_connect.errors.messages.reauthenticate_resource_owner_not_configured')
119+
}
120+
113121
option :subject, default: lambda { |*_|
114122
fail ConfigurationError, I18n.translate('doorkeeper.openid_connect.errors.messages.subject_not_configured')
115123
}

lib/doorkeeper/openid_connect/helpers/controller.rb

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,41 @@ module Controller
66

77
def authenticate_resource_owner!
88
owner = super
9-
10-
if prompt_values.include?('none') && (!owner || owner.is_a?(String))
11-
# clear the previous response body to avoid a DoubleRenderError
12-
# TODO: this is currently broken on Rails 5, see
13-
# https://github.com/rails/rails/issues/25106
14-
self.response_body = nil
15-
16-
error = ::Doorkeeper::OAuth::ErrorResponse.new(name: :login_required)
17-
response.headers.merge!(error.headers)
18-
render json: error.body, status: error.status
19-
else
9+
if validate_prompt_param!(owner) && validate_max_age_param!(owner)
2010
owner
2111
end
2212
end
2313

24-
def prompt_values
25-
@prompt_values ||= params[:prompt].to_s.split(/ +/)
14+
def validate_prompt_param!(owner)
15+
prompt_values ||= params[:prompt].to_s.split(/ +/)
16+
return true unless prompt_values.include?('none') && !owner
17+
18+
# clear the previous response body to avoid a DoubleRenderError
19+
# TODO: this is currently broken on Rails 5, see
20+
# https://github.com/rails/rails/issues/25106
21+
self.response_body = nil
22+
23+
error = ::Doorkeeper::OAuth::ErrorResponse.new(name: :login_required)
24+
response.headers.merge!(error.headers)
25+
render json: error.body, status: error.status
26+
27+
false
28+
end
29+
30+
def validate_max_age_param!(owner)
31+
max_age = params[:max_age].to_i
32+
return true unless max_age.positive?
33+
34+
auth_time = instance_exec owner,
35+
&Doorkeeper::OpenidConnect.configuration.auth_time_from_resource_owner
36+
37+
if !auth_time || (Time.zone.now - auth_time) > max_age
38+
instance_exec owner,
39+
&Doorkeeper::OpenidConnect.configuration.reauthenticate_resource_owner
40+
false
41+
else
42+
true
43+
end
2644
end
2745
end
2846
end

lib/generators/doorkeeper/openid_connect/templates/initializer.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@
1818
# User.find_by(id: access_token.resource_owner_id)
1919
end
2020

21+
auth_time_from_resource_owner do |resource_owner|
22+
# Example implementation:
23+
# resource_owner.current_sign_in_at
24+
end
25+
26+
reauthenticate_resource_owner do |resource_owner|
27+
# Example implementation:
28+
# store_location_for resource_owner, request.fullpath
29+
# sign_out resource_owner
30+
# redirect_to new_user_session_url
31+
end
32+
2133
subject do |resource_owner|
2234
# Example implementation:
2335
# resource_owner.key
Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
require 'rails_helper'
22

33
describe Doorkeeper::AuthorizationsController, type: :controller do
4-
describe '#new' do
5-
context 'without a prompt parameter' do
6-
it 'renders the authorization form if logged in' do
7-
get :new, current_user: 'Joe'
4+
let(:user) { create :user }
85

9-
expect(response).to be_successful
10-
end
6+
describe '#resource_owner_authenticator' do
7+
it 'renders the authorization form if logged in' do
8+
get :new, current_user: user.id
119

12-
it 'redirects to login form when not logged in' do
13-
get :new
10+
expect(response).to be_successful
11+
end
1412

15-
expect(response).to redirect_to '/login'
16-
end
13+
it 'redirects to login form when not logged in' do
14+
get :new
15+
16+
expect(response).to redirect_to '/login'
1717
end
18+
end
1819

20+
describe '#validate_prompt_param!' do
1921
context 'with a prompt=none parameter' do
2022
it 'renders the authorization form if logged in' do
21-
get :new, current_user: 'Joe', prompt: 'none'
23+
get :new, current_user: user.id, prompt: 'none'
2224

2325
expect(response).to be_successful
2426
end
@@ -34,4 +36,39 @@
3436
end
3537
end
3638
end
39+
40+
describe '#validate_max_age_param!' do
41+
context 'with an invalid max_age parameter' do
42+
it 'renders the authorization form' do
43+
%w[ 0 -1 -23 foobar ].each do |max_age|
44+
get :new, current_user: user.id, max_age: max_age
45+
46+
expect(response).to be_successful
47+
end
48+
end
49+
end
50+
51+
context 'with a max_age=10 parameter' do
52+
it 'renders the authorization form if the users last login was within 10 seconds' do
53+
user.update! current_sign_in_at: 5.seconds.ago
54+
get :new, current_user: user.id, max_age: 10
55+
56+
expect(response).to be_successful
57+
end
58+
59+
it 'calls reauthenticate_resource_owner if the last login was longer than 10 seconds ago' do
60+
user.update! current_sign_in_at: 5.minutes.ago
61+
get :new, current_user: user.id, max_age: 10
62+
63+
expect(response).to redirect_to '/reauthenticate'
64+
end
65+
66+
it 'calls reauthenticate_resource_owner if the last login is unknown' do
67+
user.update! current_sign_in_at: nil
68+
get :new, current_user: user.id, max_age: 10
69+
70+
expect(response).to redirect_to '/reauthenticate'
71+
end
72+
end
73+
end
3774
end

spec/dummy/config/initializers/doorkeeper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
User.new name: params[:current_user]
55
else
66
redirect_to('/login')
7+
nil
78
end
89
end
910
end

spec/dummy/config/initializers/doorkeeper_openid_connect.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@
4747
User.find_by(id: access_token.resource_owner_id)
4848
end
4949

50+
auth_time_from_resource_owner do |resource_owner|
51+
resource_owner.current_sign_in_at
52+
end
53+
54+
reauthenticate_resource_owner do |_resource_owner|
55+
redirect_to '/reauthenticate'
56+
end
57+
5058
subject do |resource_owner|
5159
resource_owner.id
5260
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class AddCurrentSignInAtToUsers < ActiveRecord::Migration
2+
def change
3+
add_column :users, :current_sign_in_at, :datetime
4+
end
5+
end

spec/dummy/db/schema.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#
1212
# It's strongly recommended that you check this file into your version control system.
1313

14-
ActiveRecord::Schema.define(version: 20161031135842) do
14+
ActiveRecord::Schema.define(version: 20161102204540) do
1515

1616
create_table "oauth_access_grants", force: :cascade do |t|
1717
t.integer "resource_owner_id", null: false
@@ -67,6 +67,7 @@
6767
t.datetime "created_at"
6868
t.datetime "updated_at"
6969
t.string "password"
70+
t.datetime "current_sign_in_at"
7071
end
7172

7273
end

spec/lib/config_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,26 @@
6868
end
6969
end
7070

71+
describe 'auth_time_from_resource_owner' do
72+
it 'sets the block that is accessible via auth_time_from_resource_owner' do
73+
block = proc {}
74+
Doorkeeper::OpenidConnect.configure do
75+
auth_time_from_resource_owner(&block)
76+
end
77+
expect(subject.auth_time_from_resource_owner).to eq(block)
78+
end
79+
end
80+
81+
describe 'reauthenticate_resource_owner' do
82+
it 'sets the block that is accessible via reauthenticate_resource_owner' do
83+
block = proc {}
84+
Doorkeeper::OpenidConnect.configure do
85+
reauthenticate_resource_owner(&block)
86+
end
87+
expect(subject.reauthenticate_resource_owner).to eq(block)
88+
end
89+
end
90+
7191
describe 'subject' do
7292
it 'sets the block that is accessible via subject' do
7393
block = proc {}

0 commit comments

Comments
 (0)