I'm trying to create a feature in my OAuth2 service where developers can create, read, and destroy scopes for use by their applications that use my service.
In order to do this, I've created a basic Scope
model and I want Doorkeeper to update its @optional_scopes
/ @scopes
with whatever scopes a user comes and creates/destroys. (Note: scopes can only be destroyed if they aren't in use.)
Note (TL;DR): This all works perfectly in development, but it isn't working in production on Heroku -- so the crux of the question is really surrounding how to update the instance variables inside of Doorkeeper that are normally set upon the app's initialization.... And if it's possible at all!
I've set the initializer to grab all the scopes in the DB and set them to optional_scopes
.
In config/initializers/doorkeeper.rb:
Doorkeeper.configure do
...
default_scopes :public
optional_scopes( *Scope.where.not(name: 'public').map{ |s| s.name } )
...
end
I have a basic controller for my "CRD" of scopes which has a filter to reset the scopes list after one has been created or destroyed:
class ScopesController < ApplicationController
after_action :set_optional_scopes, only: [ :create, :destroy ]
...
def set_optional_scopes
Doorkeeper.configuration.instance_variable_set(
'@optional_scopes',
Scope.where.not(name: 'public').map{ |s| s.name }
)
end
end
In the view for my linked applications, I have a loop of the scopes which offers the user checkboxes for the scopes.
views/doorkeeper/applications/_form.html.erb:
<% Doorkeeper.configuration.optional_scopes.each do |scope| %>
<%= check_box_tag(
scope,
'true',
application_has_scope?( application, scope.to_s )
) %>
<%= label_tag(
scope,
scope.to_s,
class: 'no-style display-inline-block'
) %>
<br>
<% end %>
Note how I'm calling Doorkeeper.configuration.optional_scopes to populate the checkboxes.
Concerned with this code updating appropriately across Heroku instances, I also overwrote Doorkeeper's self.configuration
method from:
module Doorkeeper
...
def self.configuration
@config || (fail MissingConfiguration)
end
...
end
to:
module Doorkeeper
def self.configuration
if @config
# Reset the scopes every time the config is called
@config.instance_variable_set(
'@scopes',
Scope.all.map{ |s| s.name }
)
@config
else
(fail MissingConfiguration)
end
end
end
So, as I said above, this is working well in development. However, in production it fails to update the list of checkboxes, which means that Doorkeeper.configuration.optional_scopes
doesn't get appropriately reset after the create action.
Thanks so much for your time and any help!
Okay, well, in the process of writing this, I slowed down and figured out the solution, which was right in front of my nose...
In the override of Doorkeeper's self.configuration
method, all I needed to do was reset optional_scopes
instead of scopes
as scopes
gets defined as default_scopes + optional_scopes
anyway.
So it looks like this:
def self.configuration
if @config
# Reset the scopes every time the config is called
@config.instance_variable_set(
'@optional_scopes',
Scope.where.not(name: 'public').map{ |s| s.name }
)
@config
else
(fail MissingConfiguration)
end
end
This caused all my tests to fail due to a NoMethodError
for the super class of Doorkeeper::OAuth::Scopes
then I realized I needed to rewrite that method to include an elsif for Array. So, here's that method:
module OAuth
class Scopes
def +(other)
if other.is_a? Scopes
self.class.from_array(all + other.all)
elsif other.is_a? Array
self.class.from_array(all + other)
else
super(other)
end
end
end
end
You can see the original here.
I hope all of this helps someone someday!