From 7c5fbab312689b6255c80d4a5bd3041cc7760dcc Mon Sep 17 00:00:00 2001 From: Thomas Dy Date: Tue, 5 Jun 2018 10:55:06 +0900 Subject: [PATCH] Implement custom use_refresh_token --- NEWS.md | 1 + lib/doorkeeper/config.rb | 16 +++++-- lib/doorkeeper/oauth/authorization/token.rb | 45 +++++++++++-------- lib/doorkeeper/oauth/base_request.rb | 5 ++- .../oauth/client_credentials/issuer.rb | 4 +- lib/doorkeeper/oauth/refresh_token_request.rb | 4 +- .../doorkeeper/templates/initializer.rb | 8 +++- spec/lib/config_spec.rb | 18 ++++++++ spec/lib/oauth/base_request_spec.rb | 24 ++++++++++ 9 files changed, 95 insertions(+), 30 deletions(-) diff --git a/NEWS.md b/NEWS.md index 04c4d5a65..3c98a289c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,7 @@ upgrade guides. User-visible changes worth mentioning. ## master +- [#646] Allow customizing use_refresh_token - [#1102] Expiration time based on scopes - [#1099] All the configuration variables in `Doorkeeper.configuration` now always return a non-nil value (`true` or `false`) diff --git a/lib/doorkeeper/config.rb b/lib/doorkeeper/config.rb index 9ce2ad621..9e75c4157 100644 --- a/lib/doorkeeper/config.rb +++ b/lib/doorkeeper/config.rb @@ -115,9 +115,13 @@ def enable_pkce_without_secret @config.instance_variable_set(:@pkce_without_secret_enabled, true) end - # Issue access tokens with refresh token (disabled by default) - def use_refresh_token - @config.instance_variable_set(:@refresh_token_enabled, true) + # Issue access tokens with refresh token (disabled if not set) + def use_refresh_token(enabled = true, &custom) + if custom.nil? + @config.instance_variable_set(:@refresh_token_enabled, enabled) + else + @config.instance_variable_set(:@refresh_token_enabled, custom) + end end # Reuse access token for the same resource owner within an application @@ -301,7 +305,11 @@ def enforce_content_type end def refresh_token_enabled? - !!(defined?(@refresh_token_enabled) && @refresh_token_enabled) + if defined?(@refresh_token_enabled) + @refresh_token_enabled + else + false + end end def pkce_without_secret_enabled? diff --git a/lib/doorkeeper/oauth/authorization/token.rb b/lib/doorkeeper/oauth/authorization/token.rb index 4c53cae07..40639a1a6 100644 --- a/lib/doorkeeper/oauth/authorization/token.rb +++ b/lib/doorkeeper/oauth/authorization/token.rb @@ -5,30 +5,37 @@ class Token attr_accessor :pre_auth, :resource_owner, :token class << self - def access_token_expires_in(server, pre_auth_or_oauth_client, grant_type, scopes) - if (expiration = custom_expiration(server, pre_auth_or_oauth_client, grant_type, scopes)) - expiration - else - server.access_token_expires_in - end - end - - private - - def custom_expiration(server, pre_auth_or_oauth_client, grant_type, scopes) + def make_context(pre_auth_or_oauth_client, grant_type, scopes) oauth_client = if pre_auth_or_oauth_client.respond_to?(:client) pre_auth_or_oauth_client.client else pre_auth_or_oauth_client end - context = Doorkeeper::OAuth::Authorization::Context.new( + + Doorkeeper::OAuth::Authorization::Context.new( oauth_client, grant_type, scopes ) + end + + def access_token_expires_in(server, context) + if (expiration = server.custom_access_token_expires_in.call(context)) + expiration + else + server.access_token_expires_in + end + end - server.custom_access_token_expires_in.call(context) + def refresh_token_enabled?(server, context) + if server.refresh_token_enabled?.respond_to? :call + server.refresh_token_enabled?.call(context) + else + !!server.refresh_token_enabled? + end end + + private end def initialize(pre_auth, resource_owner) @@ -37,16 +44,16 @@ def initialize(pre_auth, resource_owner) end def issue_token + context = self.class.make_context( + pre_auth.client, + Doorkeeper::OAuth::IMPLICIT, + pre_auth.scopes + ) @token ||= AccessToken.find_or_create_for( pre_auth.client, resource_owner.id, pre_auth.scopes, - self.class.access_token_expires_in( - configuration, - pre_auth, - Doorkeeper::OAuth::IMPLICIT, - pre_auth.scopes - ), + self.class.access_token_expires_in(configuration, context), false ) end diff --git a/lib/doorkeeper/oauth/base_request.rb b/lib/doorkeeper/oauth/base_request.rb index 38fee52a0..c28896b3d 100644 --- a/lib/doorkeeper/oauth/base_request.rb +++ b/lib/doorkeeper/oauth/base_request.rb @@ -31,12 +31,13 @@ def valid? end def find_or_create_access_token(client, resource_owner_id, scopes, server) + context = Authorization::Token.make_context(client, grant_type, scopes) @access_token = AccessToken.find_or_create_for( client, resource_owner_id, scopes, - Authorization::Token.access_token_expires_in(server, client, grant_type, scopes), - server.refresh_token_enabled? + Authorization::Token.access_token_expires_in(server, context), + Authorization::Token.refresh_token_enabled?(server, context) ) end diff --git a/lib/doorkeeper/oauth/client_credentials/issuer.rb b/lib/doorkeeper/oauth/client_credentials/issuer.rb index 14d0d60e4..180764f02 100644 --- a/lib/doorkeeper/oauth/client_credentials/issuer.rb +++ b/lib/doorkeeper/oauth/client_credentials/issuer.rb @@ -25,12 +25,12 @@ def create(client, scopes, creator = Creator.new) private def create_token(client, scopes, creator) - ttl = Authorization::Token.access_token_expires_in( - @server, + context = Authorization::Token.make_context( client, Doorkeeper::OAuth::CLIENT_CREDENTIALS, scopes ) + ttl = Authorization::Token.access_token_expires_in(@server, context) creator.call( client, diff --git a/lib/doorkeeper/oauth/refresh_token_request.rb b/lib/doorkeeper/oauth/refresh_token_request.rb index cb1458be7..32db38e36 100644 --- a/lib/doorkeeper/oauth/refresh_token_request.rb +++ b/lib/doorkeeper/oauth/refresh_token_request.rb @@ -65,12 +65,12 @@ def access_token_attributes end def access_token_expires_in - Authorization::Token.access_token_expires_in( - server, + context = Authorization::Token.make_context( client, Doorkeeper::OAuth::REFRESH_TOKEN, scopes ) + Authorization::Token.access_token_expires_in(server, context) end def validate_token_presence diff --git a/lib/generators/doorkeeper/templates/initializer.rb b/lib/generators/doorkeeper/templates/initializer.rb index f30bd0bf6..aba4b9a3c 100644 --- a/lib/generators/doorkeeper/templates/initializer.rb +++ b/lib/generators/doorkeeper/templates/initializer.rb @@ -66,7 +66,13 @@ # # reuse_access_token - # Issue access tokens with refresh token (disabled by default) + # Issue access tokens with refresh token (disabled by default), you may also + # pass a block which accepts `context` to customize when to give a refresh token + # or not. Similar to `custom_access_token_expires_in`, `context` has the properties: + # + # `client` - the OAuth client application (see Doorkeeper::OAuth::Client) + # `grant_type` - the grant type of the request (see Doorkeeper::OAuth) + # `scopes` - the requested scopes (see Doorkeeper::OAuth::Scopes) # # use_refresh_token diff --git a/spec/lib/config_spec.rb b/spec/lib/config_spec.rb index d0364879f..f5b61887a 100644 --- a/spec/lib/config_spec.rb +++ b/spec/lib/config_spec.rb @@ -144,6 +144,24 @@ expect(subject.refresh_token_enabled?).to eq(true) end + it 'can accept a boolean parameter' do + Doorkeeper.configure do + orm DOORKEEPER_ORM + use_refresh_token false + end + + expect(subject.refresh_token_enabled?).to eq(false) + end + + it 'can accept a block parameter' do + Doorkeeper.configure do + orm DOORKEEPER_ORM + use_refresh_token { |_context| nil } + end + + expect(subject.refresh_token_enabled?).to be_a(Proc) + end + it "does not includes 'refresh_token' in authorization_response_types" do expect(subject.token_grant_types).not_to include 'refresh_token' end diff --git a/spec/lib/oauth/base_request_spec.rb b/spec/lib/oauth/base_request_spec.rb index f0f83ed76..d14175680 100644 --- a/spec/lib/oauth/base_request_spec.rb +++ b/spec/lib/oauth/base_request_spec.rb @@ -119,6 +119,30 @@ module Doorkeeper::OAuth ) expect(result.expires_in).to eql(500) end + + it "respects use_refresh_token with a block" do + server = double(:server, + access_token_expires_in: 100, + custom_access_token_expires_in: ->(_context) { nil }, + refresh_token_enabled?: lambda { |context| + context.scopes == "public" ? true : false + }) + result = subject.find_or_create_access_token( + client, + "1", + "public", + server + ) + expect(result.refresh_token).to_not be_nil + + result = subject.find_or_create_access_token( + client, + "1", + "private", + server + ) + expect(result.refresh_token).to be_nil + end end describe "#scopes" do