Skip to content

Commit

Permalink
Added config option to permit and return custom fields in access tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyC-za committed Feb 3, 2023
1 parent 3ad48e0 commit b580c18
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 7 deletions.
2 changes: 1 addition & 1 deletion app/controllers/doorkeeper/authorizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def pre_auth_params
end

def pre_auth_param_fields
%i[
Doorkeeper.configuration.custom_access_token_fields + %i[
client_id
code_challenge
code_challenge_method
Expand Down
9 changes: 9 additions & 0 deletions lib/doorkeeper/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,15 @@ def configure_secrets_for(type, using:, fallback:)
option :access_token_generator,
default: "Doorkeeper::OAuth::Helpers::UniqueToken"

# Allows additional data to be received when granting access to an Application, and for this
# additional data to be sent with subsequently generated access tokens. The access grant and
# access token models will both need to respond to the specified field names.
#
# @param fields [Array] The array of custom field names to be saved
#
option :custom_access_token_fields,
default: []

# Use a custom class for generating the application secret.
# https://doorkeeper.gitbook.io/guides/configuration/other-configurations#custom-application-secret-generator
#
Expand Down
10 changes: 10 additions & 0 deletions lib/doorkeeper/oauth/authorization/code.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ def access_grant_attributes
attributes[:resource_owner_id] = resource_owner.id
end

# Custom access token fields are saved into the access grant,
# and then included in subsequently generated access tokens.
Doorkeeper.config.custom_access_token_fields.each do |field_name|
unless Doorkeeper.config.access_grant_model.has_attribute?(field_name)
raise NotImplementedError, "#{Doorkeeper.config.access_grant_model} does not recognize field: #{field_name}."
end

attributes[field_name] = @pre_auth.custom_access_token_fields[field_name]
end

pkce_attributes.merge(attributes)
end

Expand Down
11 changes: 11 additions & 0 deletions lib/doorkeeper/oauth/authorization_code_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def before_successful_response
grant.application,
resource_owner,
grant.scopes,
custom_token_fields_with_data,
server,
)
end
Expand Down Expand Up @@ -99,6 +100,16 @@ def validate_code_verifier
def generate_code_challenge(code_verifier)
server_config.access_grant_model.generate_code_challenge(code_verifier)
end

private def custom_token_fields_with_data
Doorkeeper.config.custom_access_token_fields.each do |field_name|
unless Doorkeeper.config.access_token_model.has_attribute?(field_name)
raise NotImplementedError, "#{Doorkeeper.config.access_token_model} does not recognize field: #{field_name}."
end
end

grant.attributes.with_indifferent_access.slice(*Doorkeeper.config.custom_access_token_fields).symbolize_keys
end
end
end
end
13 changes: 9 additions & 4 deletions lib/doorkeeper/oauth/base_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,20 @@ def scopes
@scopes ||= build_scopes
end

def find_or_create_access_token(client, resource_owner, scopes, server)
def find_or_create_access_token(client, resource_owner, scopes, custom_fields, server)
context = Authorization::Token.build_context(client, grant_type, scopes, resource_owner)
@access_token = server_config.access_token_model.find_or_create_for(
application: client.is_a?(server_config.application_model) ? client : client&.application,
token_model = server_config.access_token_model
application = client.is_a?(server_config.application_model) ? client : client&.application

token_params = {
application: application,
resource_owner: resource_owner,
scopes: scopes,
expires_in: Authorization::Token.access_token_expires_in(server, context),
use_refresh_token: Authorization::Token.refresh_token_enabled?(server, context),
)
}

@access_token = token_model.find_or_create_for(token_params.merge(custom_fields))
end

def before_successful_response
Expand Down
2 changes: 1 addition & 1 deletion lib/doorkeeper/oauth/password_access_token_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def initialize(server, client, credentials, resource_owner, parameters = {})
private

def before_successful_response
find_or_create_access_token(client, resource_owner, scopes, server)
find_or_create_access_token(client, resource_owner, scopes, {}, server)
super
end

Expand Down
7 changes: 6 additions & 1 deletion lib/doorkeeper/oauth/pre_authorization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class PreAuthorization

attr_reader :client, :code_challenge, :code_challenge_method, :missing_param,
:redirect_uri, :resource_owner, :response_type, :state,
:authorization_response_flow, :response_mode
:authorization_response_flow, :response_mode, :custom_access_token_fields

def initialize(server, parameters = {}, resource_owner = nil)
@server = server
Expand All @@ -31,6 +31,11 @@ def initialize(server, parameters = {}, resource_owner = nil)
@code_challenge = parameters[:code_challenge]
@code_challenge_method = parameters[:code_challenge_method]
@resource_owner = resource_owner

@custom_access_token_fields = {}
Doorkeeper.config.custom_access_token_fields.each do |field|
@custom_access_token_fields[field] = parameters[field]
end
end

def authorizable?
Expand Down
17 changes: 17 additions & 0 deletions lib/generators/doorkeeper/templates/initializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,23 @@
# resource_owner.admin? || client.owners_allowlist.include?(resource_owner)
# end

# Allows additional data fields to be sent while granting access to an application,
# and for this additional data to be included in subsequently generated access tokens.
# The 'authorizations/new' page will need to be overridden to include this additional data
# in the request params when granting access. The access grant and access token models
# will both need to respond to these additional data fields, and have a database column
# to store them in.
#
# Example:
# You have a multi-tenanted platform and want to be able to grant access to a specific
# tenant, rather than all the tenants a user has access to. You can use this config
# option to specify that a ':tenant_id' will be passed when authorizing. This tenant_id
# will be included in the access tokens. When a request is made with one of these access
# tokens, you can check that the requested data belongs to the specified tenant.
#
# Default value is an empty Array: []
# custom_access_token_fields [:tenant_id]

# Hook into the strategies' request & response life-cycle in case your
# application needs advanced customization or logging:
#
Expand Down
14 changes: 14 additions & 0 deletions spec/lib/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,20 @@ class ApplicationWithOwner < ActiveRecord::Base
end
end

describe "custom_access_token_fields" do
it "is '[]' by default" do
expect(Doorkeeper.configuration.custom_access_token_fields).to(eq([]))
end

it "can change the value" do
Doorkeeper.configure do
orm DOORKEEPER_ORM
custom_access_token_fields [:added_field_1, :added_field_2]
end
expect(config.custom_access_token_fields).to eq([:added_field_1, :added_field_2])
end
end

describe "application_secret_generator" do
it "is 'Doorkeeper::OAuth::Helpers::UniqueToken' by default" do
expect(Doorkeeper.configuration.application_secret_generator).to(
Expand Down
4 changes: 4 additions & 0 deletions spec/lib/oauth/base_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
client,
resource_owner,
"public",
{},
server,
)

Expand All @@ -144,6 +145,7 @@
client,
resource_owner,
"public",
{},
server,
)
expect(result.expires_in).to be(500)
Expand All @@ -165,6 +167,7 @@
client,
resource_owner,
"public",
{},
server,
)
expect(result.refresh_token).not_to be_nil
Expand All @@ -173,6 +176,7 @@
client,
resource_owner,
"private",
{},
server,
)
expect(result.refresh_token).to be_nil
Expand Down

0 comments on commit b580c18

Please sign in to comment.