Module: Doorkeeper::AccessTokenMixin::ClassMethods

Defined in:
lib/doorkeeper/models/access_token_mixin.rb

Instance Method Summary collapse

Instance Method Details

#authorized_tokens_for(application_id, resource_owner) ⇒ ActiveRecord::Relation

Looking for not revoked Access Token records that belongs to specific Application and Resource Owner.

Parameters:

  • application_id (Integer)

    ID of the Application model instance

  • resource_owner (ActiveRecord::Base, Integer)

    Resource Owner model instance or it's ID

Returns:

  • (ActiveRecord::Relation)

    collection of matching AccessToken objects



364
365
366
367
368
369
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 364

def authorized_tokens_for(application_id, resource_owner)
  by_resource_owner(resource_owner).where(
    application_id: application_id,
    revoked_at: nil,
  )
end

#by_previous_refresh_token(previous_refresh_token) ⇒ Doorkeeper::AccessToken?

Returns an instance of the Doorkeeper::AccessToken found by previous refresh token. Keep in mind that value of the previous_refresh_token isn't encrypted using secrets strategy.

Parameters:

  • previous_refresh_token (#to_s)

    previous refresh token value (any object that responds to #to_s)

Returns:



57
58
59
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 57

def by_previous_refresh_token(previous_refresh_token)
  find_by(refresh_token: previous_refresh_token)
end

#by_refresh_token(refresh_token) ⇒ Doorkeeper::AccessToken?

Returns an instance of the Doorkeeper::AccessToken with specific token value.

Parameters:

  • refresh_token (#to_s)

    refresh token value (any object that responds to #to_s)

Returns:



42
43
44
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 42

def by_refresh_token(refresh_token)
  find_by_plaintext_token(:refresh_token, refresh_token)
end

#by_token(token) ⇒ Doorkeeper::AccessToken?

Returns an instance of the Doorkeeper::AccessToken with specific plain text token value.

Parameters:

  • token (#to_s)

    Plain text token value (any object that responds to #to_s)

Returns:



29
30
31
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 29

def by_token(token)
  find_by_plaintext_token(:token, token)
end

#create_for(application:, resource_owner:, scopes:, **token_attributes) ⇒ Doorkeeper::AccessToken

Creates a not expired AccessToken record with a matching set of scopes that belongs to specific Application and Resource Owner.

Parameters:

  • application (Doorkeeper::Application)

    Application instance

  • resource_owner (ActiveRecord::Base, Integer)

    Resource Owner model instance or it's ID

  • scopes (#to_s)

    set of scopes (any object that responds to #to_s)

  • token_attributes (Hash)

    Additional attributes to use when creating a token

Options Hash (**token_attributes):

  • :expires_in (Integer)

    token lifetime in seconds

  • :use_refresh_token (Boolean)

    whether to use the refresh token

Returns:



338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 338

def create_for(application:, resource_owner:, scopes:, **token_attributes)
  token_attributes[:application] = application
  token_attributes[:scopes] = scopes.to_s

  if Doorkeeper.config.polymorphic_resource_owner?
    token_attributes[:resource_owner] = resource_owner
  else
    token_attributes[:resource_owner_id] = resource_owner_id_for(resource_owner)
  end

  with_primary_role do
    create!(token_attributes)
  end
end

#custom_attributes_match?(token, custom_attributes) ⇒ Boolean

Checks whether the token custom attribute values match the custom attributes from the parameters.

Parameters:

  • token (Doorkeeper::AccessToken)

    The access token whose custom attributes are being compared to the custom_attributes.

  • custom_attributes (Hash)

    A hash of the attributes for which we want to determine whether the token's custom attributes match.

Returns:

  • (Boolean)

    true if the token's custom attribute values match those in the custom_attributes, or if both are empty/blank. False otherwise.



221
222
223
224
225
226
227
228
229
230
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 221

def custom_attributes_match?(token, custom_attributes)
  return true if custom_attributes.nil?

  token_attribs = token.custom_attributes
  return true if token_attribs.blank? && custom_attributes.blank?

  Doorkeeper.config.custom_access_token_attributes.all? do |attribute|
    token_attribs[attribute] == custom_attributes[attribute]
  end
end

#extract_custom_attributes(attributes) ⇒ Hash

Extracts the token's custom attributes (defined by the custom_access_token_attributes config option) from the token's attributes.

Parameters:

  • attributes (Hash)

    A hash of the access token's attributes.

Returns:

  • (Hash)

    A hash containing only the custom access token attributes.



412
413
414
415
416
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 412

def extract_custom_attributes(attributes)
  attributes.with_indifferent_access.slice(
    *Doorkeeper.configuration.custom_access_token_attributes,
  )
end

#fallback_secret_strategyObject

Determine the fallback storing strategy Unless configured, there will be no fallback



401
402
403
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 401

def fallback_secret_strategy
  ::Doorkeeper.config.token_secret_fallback_strategy
end

#find_access_token_in_batches(relation, **args, &block) ⇒ Object

Interface to enumerate access token records in batches in order not to bloat the memory. Could be overloaded in any ORM extension.



110
111
112
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 110

def find_access_token_in_batches(relation, **args, &block)
  relation.find_in_batches(**args, &block)
end

#find_matching_token(relation, application, custom_attributes, scopes, &filter) ⇒ Doorkeeper::AccessToken?

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.

Parameters:

  • relation (ActiveRecord::Relation)

    Access tokens relation

  • application (Doorkeeper::Application)

    Application instance

  • scopes (String, Doorkeeper::OAuth::Scopes)

    set of scopes

  • custom_attributes (Nilable Hash)

    A nil value, or hash with keys corresponding to the custom attributes configured with the custom_access_token_attributes config option. A nil value will ignore custom attributes, while an empty hash will only match tokens that have no custom attributes set.

Returns:



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 137

def find_matching_token(relation, application, custom_attributes, scopes, &filter)
  return nil unless relation

  matching_tokens = []
  batch_size = Doorkeeper.configuration.token_lookup_batch_size

  find_access_token_in_batches(relation, batch_size: batch_size) do |batch|
    tokens = batch.select do |token|
      scopes_match?(token.scopes, scopes, application&.scopes) &&
        custom_attributes_match?(token, custom_attributes) &&
        (filter.nil? || filter.call(token))
    end

    matching_tokens.concat(tokens)
  end

  matching_tokens.max_by(&:created_at)
end

#find_or_create_for(application:, resource_owner:, scopes:, **token_attributes) ⇒ Doorkeeper::AccessToken

Looking for not expired AccessToken record with a matching set of scopes that belongs to specific Application and Resource Owner. If it doesn't exists - then creates it.

Parameters:

  • application (Doorkeeper::Application)

    Application instance

  • resource_owner (ActiveRecord::Base, Integer)

    Resource Owner model instance or it's ID

  • scopes (#to_s)

    set of scopes (any object that responds to #to_s)

  • token_attributes (Hash)

    Additional attributes to use when creating a token

Options Hash (**token_attributes):

  • :expires_in (Integer)

    token lifetime in seconds

  • :use_refresh_token (Boolean)

    whether to use the refresh token

Returns:



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 284

def find_or_create_for(application:, resource_owner:, scopes:, **token_attributes)
  scopes = Doorkeeper::OAuth::Scopes.from_string(scopes) if scopes.is_a?(String)

  if Doorkeeper.config.reuse_access_token
    # An empty hash must stay distinct from nil here: nil ignores custom
    # attributes when matching, while an empty hash only matches tokens
    # that have no custom attributes set.
    custom_attributes = extract_custom_attributes(token_attributes)
    # The refresh token requirement participates in the matching itself
    # rather than being checked on its single newest result: the newest
    # matching token may carry the wrong refresh token presence (e.g. it
    # was issued through a grant with a different `use_refresh_token`)
    # while an older token satisfies the request and can still be reused.
    #
    # RFC 8707: resource indicators must also match so that a token
    # audience-restricted to one resource is never reused for another.
    requested_resource = token_attributes[:resource]

    access_token = matching_token_for(
      application, resource_owner, scopes, custom_attributes: custom_attributes, include_expired: false,
    ) do |token|
      refresh_token_matches?(token, token_attributes) &&
        resource_indicators_match?(token, requested_resource)
    end

    return access_token if access_token&.reusable?
  end

  create_for(
    application: application,
    resource_owner: resource_owner,
    scopes: scopes,
    **token_attributes,
  )
end

#last_authorized_token_for(application_id, resource_owner) ⇒ Doorkeeper::AccessToken?

Convenience method for backwards-compatibility, return the last matching token for the given Application and Resource Owner.

Parameters:

  • application_id (Integer)

    ID of the Application model instance

  • resource_owner (ActiveRecord::Base, Integer)

    ID of the Resource Owner model instance

Returns:



382
383
384
385
386
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 382

def last_authorized_token_for(application_id, resource_owner)
  authorized_tokens_for(application_id, resource_owner)
    .ordered_by(:created_at, :desc)
    .first
end

#matching_token_for(application, resource_owner, scopes, custom_attributes: nil, include_expired: true) {|token| ... } ⇒ Doorkeeper::AccessToken?

Looking for not revoked Access Token with a matching set of scopes that belongs to specific Application and Resource Owner.

Parameters:

  • application (Doorkeeper::Application)

    Application instance

  • resource_owner (ActiveRecord::Base, Integer)

    Resource Owner model instance or it's ID

  • scopes (String, Doorkeeper::OAuth::Scopes)

    set of scopes

  • custom_attributes (Nilable Hash) (defaults to: nil)

    A nil value, or hash with keys corresponding to the custom attributes configured with the custom_access_token_attributes config option. A nil value will ignore custom attributes, while an empty hash will only match tokens that have no custom attributes set.

Yields:

  • (token)

    optional additional predicate a token must satisfy to count as a match (e.g. the refresh token requirement of the request).

Returns:



101
102
103
104
105
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 101

def matching_token_for(application, resource_owner, scopes, custom_attributes: nil, include_expired: true, &filter)
  tokens = authorized_tokens_for(application&.id, resource_owner)
  tokens = tokens.not_expired unless include_expired
  find_matching_token(tokens, application, custom_attributes, scopes, &filter)
end

#refresh_token_matches?(access_token, token_attributes) ⇒ Boolean

Checks whether a candidate token for reuse matches the refresh token requirement of the current request.

The candidate's refresh token presence must match what the request asks for, in both directions. A request that expects a refresh token (e.g. an authorization_code or password grant while use_refresh_token is enabled) must not reuse a token issued without one, or the response would silently omit the refresh token. Conversely, a request that does not ask for a refresh token must not reuse a token that carries one, or the response would return a refresh token the request never requested (this is reachable when refresh_token_enabled is a per-request callable). When no matching token satisfies the requirement a fresh token is issued instead.

Parameters:

  • access_token (Doorkeeper::AccessToken)

    the candidate token for reuse

  • token_attributes (Hash)

    attributes for the token being requested

Returns:

  • (Boolean)

    true if the candidate token's refresh token presence matches the request's refresh token requirement, false otherwise



178
179
180
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 178

def refresh_token_matches?(access_token, token_attributes)
  access_token.refresh_token.present? == !!token_attributes[:use_refresh_token]
end

#resource_indicators_match?(token, requested_resource) ⇒ Boolean

RFC 8707: checks whether an existing token's audience matches the requested resource indicators. Used during token reuse to prevent returning a token audience-restricted to one resource for a request targeting a different resource.

The comparison runs whenever either side carries a resource, even when no validator is configured: a grant bound to resources still restricts the token's audience (see AuthorizationCodeRequest), so reuse must not silently widen it by matching an unrestricted or differently-scoped token. When both sides are blank the tokens are unrestricted and match.

Parameters:

  • token (Doorkeeper::AccessToken)

    existing token

  • requested_resource (String, nil)

    space-delimited resource URIs

Returns:

  • (Boolean)


246
247
248
249
250
251
252
253
254
255
256
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 246

def resource_indicators_match?(token, requested_resource)
  token_resource = token.try(:resource)

  # Both blank — neither is audience-restricted, match.
  return true if token_resource.blank? && requested_resource.blank?
  # One blank, the other not — mismatch.
  return false if token_resource.blank? || requested_resource.blank?

  # Both present — compare as sorted sets.
  token_resource.split.sort == requested_resource.split.sort
end

#resource_indicators_supported?Boolean

RFC 8707: resource indicators are supported only when the resource column exists (added by the doorkeeper:resource_indicators generator).

Returns:

  • (Boolean)


261
262
263
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 261

def resource_indicators_supported?
  column_names.include?("resource")
end

#revoke_all_for(application_id, resource_owner, clock = Time) ⇒ Object

Revokes AccessToken records that have not been revoked and associated with the specific Application and Resource Owner.

Parameters:

  • application_id (Integer)

    ID of the Application

  • resource_owner (ActiveRecord::Base, Integer)

    instance of the Resource Owner model or it's ID



69
70
71
72
73
74
75
76
77
78
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 69

def revoke_all_for(application_id, resource_owner, clock = Time)
  with_primary_role do
    by_resource_owner(resource_owner)
      .where(
        application_id: application_id,
        revoked_at: nil,
      )
      .update_all(revoked_at: clock.now.utc)
  end
end

#scopes_match?(token_scopes, param_scopes, app_scopes) ⇒ Boolean

Checks whether the token scopes match the scopes from the parameters

Parameters:

Returns:

  • (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



196
197
198
199
200
201
202
203
204
205
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 196

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,
    )
end

#secret_strategyDoorkeeper::SecretStoring::Base

Determines the secret storing transformer Unless configured otherwise, uses the plain secret strategy



394
395
396
# File 'lib/doorkeeper/models/access_token_mixin.rb', line 394

def secret_strategy
  ::Doorkeeper.config.token_secret_strategy
end