doorkeeper: Unable to use a custom STI OAuthApplication class
Steps to reproduce
Here is the basic setup:
I added a type column to the oauth_applications database table.
I’m using something similar to the following class setup:
class MyCustomOauthApplicationClass < ApplicationRecord
include ::Doorkeeper::Orm::ActiveRecord::Mixins::Application
end
class MySpecialCustomOauthApplicationClass < MyCustomOauthApplicationClass
end
I have a bit of custom logic for the AuthorizationsController to integrate with our SPA. This shouldn’t be affecting things, since I was able to reproduce the issue in the Rails console. Just adding for some completeness.
class OauthIdpAuthorizationsController < Doorkeeper::AuthorizationsController
def new
if pre_auth.authorizable?
redirect_to spa_app_path(spa_route: 'oauth/spa_authorize', **pre_auth_params)
else
render_error
end
end
end
Expected behavior
pre_auth in Doorkeeper::AuthorizationsController should be able to find and initialize the correct model instance.
Actual behavior
I get the following error:
ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: MySpecialCustomOauthApplicationClass is not a subclass of MyCustomOauthApplicationClass
What I have been able to figure out
I went on a deep dive into ActiveRecord’s internals to hunt down how STI was handled. That lead me to ActiveSupport::DescendantsTracker which, as the name implies, tracks all of the subclasses, recursively, of a class.
It looks like the object that is held in Doorkeeper.config.application_model is different than calling MyCustomOauthApplicationClass.
They actually return different object_ids which is odd. This is a scrubbed version of a console session:
[1] > MyCustomOauthApplicationClass.object_id
=> 97020
[2] > Doorkeeper.config.application_model.name
=> "MyCustomOauthApplicationClass"
[3] > Doorkeeper.config.application_model.object_id
=> 90500
[4] > uid = 'asfsafsdf' # uid of an STI class instance
=> "asfsafsdf"
[5] > MyCustomOauthApplicationClass.find_by(uid: uid)
=> #<MySpecialCustomOauthApplicationClass id: 2, ...>
[6] > Doorkeeper.config.application_model.find_by(uid: uid)
=> ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: MySpecialCustomOauthApplicationClass is not a subclass of MyCustomOauthApplicationClass
[7] > ActiveSupport::DescendantsTracker.descendants(MyCustomOauthApplicationClass)
=> [MySpecialCustomOauthApplicationClass(id: integer, type: string, ...)]
[8] > ActiveSupport::DescendantsTracker.descendants(Doorkeeper.config.application_model)
=> []
ActiveSupport::DescendantsTracker is a critical part of determining the correctness of STI classes. It looks like two different versions of MyCustomOauthApplicationClass are getting loaded and the one associated with Doorkeeper.config.application_model isn’t getting setup correctly.
I’m not an expert on class, constant, or object loading, but I wonder if the fact that Doorkeeper.config.application_model object_id is lower means that it was loaded first but not in a way that ActiveRecord can find it and use it.
Possibly related issues:
System configuration
Doorkeeper initializer:
# config/initializers/doorkeeper.rb
Doorkeeper.configure do
orm :active_record
application_class 'MyCustomOauthApplicationClass'
api_only
end
# routes.rb
use_doorkeeper do
controllers authorizations: 'oauth_idp_authorizations'
skip_controllers :applications, :token_info, :authorized_applications
end
Ruby version: 2.7.3
Gemfile.lock:
Gemfile.lock content
activerecord (6.1.3.2)
doorkeeper (5.5.1)
Let me know if there are any gem versions you need beyond these.
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 22 (7 by maintainers)
@nbulaj nowadays
constantizeis basicallyconst_get(source code). However, if the value is cached in a place that gets reloaded, I believe I’d personally prefer to cache it. Even if the method is cheap, I believe I’d have a sense of doing unnecessary method calls nonetheless.