Skip to content

Commit

Permalink
[*] Replaces find_matching_token implementation to be performant.
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilipSkinnerStrat7 committed Mar 5, 2021
1 parent bb00082 commit 287985f
Showing 1 changed file with 23 additions and 48 deletions.
71 changes: 23 additions & 48 deletions lib/doorkeeper/models/access_token_mixin.rb
Expand Up @@ -91,22 +91,8 @@ def matching_token_for(application, resource_owner, scopes)
find_matching_token(tokens, application, scopes)
end

# Interface to enumerate access token records in batches in order not
# to bloat the memory. Could be overloaded in any ORM extension.
#
def find_access_token_in_batches(relation, **args, &block)
relation.find_in_batches(**args, &block)
end

# Enumerates AccessToken records in batches to find a matching token.
# Batching is required in order not to pollute the memory if Application
# has huge amount of associated records.
#
# ActiveRecord 5.x - 6.x ignores custom ordering so we can't perform a
# database sort by created_at, so we need to load all the matching records,
# sort them and find latest one. Probably it would be better to rewrite this
# query using Time math if possible, but we n eed to consider ORM and
# different databases support.
# Finds the latest access token with the same scopes for the
# given application.
#
# @param relation [ActiveRecord::Relation]
# Access tokens relation
Expand All @@ -121,43 +107,32 @@ def find_access_token_in_batches(relation, **args, &block)
def find_matching_token(relation, application, scopes)
return nil unless relation

matching_tokens = []
batch_size = Doorkeeper.configuration.token_lookup_batch_size
token = relation
.where(application_id: application.nil? ? nil : application.id)

find_access_token_in_batches(relation, batch_size: batch_size) do |batch|
tokens = batch.select do |token|
scopes_match?(token.scopes, scopes, application&.scopes)
token = token.where(scopes: scopes.to_s) if scopes.all.count > 0
token = token.where(scopes: [nil, ""]) if scopes.all.count == 0

if !application&.nil? && application.scopes.all.count > 0
scopes.each do |requested_scope|
return nil unless application.scopes.exists? requested_scope
end

matching_tokens.concat(tokens)
end
application.scopes.each do |scope_val|
next unless scopes.exists? scope_val

matching_tokens.max_by(&:created_at)
end
token = token.where([
"(scopes LIKE ? OR scopes LIKE ? OR scopes LIKE ? OR scopes = ?)",
"% #{scope_val} %",
"#{scope_val} %",
"% #{scope_val}",
scope_val,
])
end
end

# Checks whether the token scopes match the scopes from the parameters
#
# @param token_scopes [#to_s]
# set of scopes (any object that responds to `#to_s`)
# @param param_scopes [Doorkeeper::OAuth::Scopes]
# scopes from params
# @param app_scopes [Doorkeeper::OAuth::Scopes]
# Application scopes
#
# @return [Boolean] true if the param scopes match the token scopes,
# and all the param scopes are defined in the application (or in the
# server configuration if the application doesn't define any scopes),
# and false in other cases
#
def scopes_match?(token_scopes, param_scopes, app_scopes)
return true if token_scopes.empty? && param_scopes.empty?

(token_scopes.sort == param_scopes.sort) &&
Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(
scope_str: param_scopes.to_s,
server_scopes: Doorkeeper.config.scopes,
app_scopes: app_scopes,
)
token.order(created_at: :desc)
.first
end

# Looking for not expired AccessToken record with a matching set of
Expand Down

0 comments on commit 287985f

Please sign in to comment.