Fix registration of auth providers through register_auth_providers

This is the only and official API to register an auth provider.
However, so far it was optional to create a database entry in the
auth_providers table and only OIDC and SAML did that.

On the other hand, we added expectations about auth providers have
a database entry in more and more places of the codebase.

Now making sure that every auth provider is represented in the database.
This commit is contained in:
Jan Sandbrink
2025-07-21 08:03:18 +02:00
parent 9735846ab1
commit bd4b09592b
7 changed files with 89 additions and 9 deletions
-4
View File
@@ -44,10 +44,6 @@ class AuthProvider < ApplicationRecord
after_destroy :unset_direct_provider
def self.slug_fragment
raise NotImplementedError
end
def user_count
@user_count ||= user_auth_provider_links.count
end
+48
View File
@@ -0,0 +1,48 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
class PluginAuthProvider < AuthProvider
class << self
def create_for_plugin(config)
slug = config[:name]
display_name = config[:display_name] || slug
find_or_create_by!(slug:) do |provider|
provider.available = false
provider.display_name = display_name.to_s
provider.creator = User.system
end
end
end
def human_type
"Plug-in-based authentication provider"
end
end
@@ -103,6 +103,19 @@ module OmniAuth
You can register any number of providers using different strategies (or the same) with different options.
For instance you could configure two OpenID Connect providers using the same strategy (OpenIDConnect) but with different options according to the service to be used (e.g. Google vs Microsoft).
OpenProject expects a database entry in the `auth_providers` table to be stored for each provider registered for SSO login (via subclasses of the
`AuthProvider` model), so that they can be referenced, for example by users logging in through those providers.
By default the call to `register_auth_providers` will make sure that a record exists for each registered provider.
However, if your plugin manages configuration of each provider in such a subclass itself, you can pass `persist: false`, to indicate that:
```ruby
register_auth_providers(persist: false) do
strategy :my_advanced_auth_plugin_strategy do
# ...
end
end
```
### Add your plugin to Gemfile.plugins
All thats that left to do is declaring your plugin in the file `Gemfile.plugins` in your OpenProject applications root directory.
@@ -28,9 +28,9 @@
module OpenProject::Plugins
module AuthPlugin
def register_auth_providers(&)
def register_auth_providers(persist: true, &)
builder = ProviderBuilder.new
initializer "#{engine_name}.middleware" do |app|
builder = ProviderBuilder.new
builder.instance_eval(&)
app.config.middleware.use OmniAuth::FlexibleBuilder do
@@ -39,6 +39,16 @@ module OpenProject::Plugins
end
end
end
config.to_prepare do
if persist
builder.provider_callbacks.each do |callback|
callback.call.each do |config|
PluginAuthProvider.create_for_plugin(config)
end
end
end
end
end
def self.strategies
@@ -125,10 +135,16 @@ module OpenProject::Plugins
AuthPlugin.strategies[key] = [providers]
new_strategies << strategy
end
provider_callbacks.push(providers)
end
def new_strategies
@new_strategies ||= []
end
def provider_callbacks
@provider_callbacks ||= []
end
end
end
@@ -64,6 +64,9 @@ RSpec.describe OpenProject::Plugins::AuthPlugin, with_ee: %i[sso_auth_providers]
without_partial_double_verification do
allow(dummy_engine_klass).to receive(:engine_name).and_return("foobar")
allow(dummy_engine_klass).to receive(:initializer) { |_, &block| app.instance_eval(&block) }
allow(dummy_engine_klass).to receive_message_chain(:config, :to_prepare) { |_, &block| # rubocop:disable RSpec/MessageChain
block.call
}
end
end
@@ -97,7 +100,11 @@ RSpec.describe OpenProject::Plugins::AuthPlugin, with_ee: %i[sso_auth_providers]
expect(strategies.keys.to_a).to eq %i[strategy_a strategy_b]
end
it "registers register each strategy (i.e. middleware) only once" do
it "persists all strategies in the database" do
expect(PluginAuthProvider.pluck(:slug)).to contain_exactly("a1", "a2", "b1", "c1")
end
it "registers each strategy (i.e. middleware) only once" do
expect(middlewares.size).to eq 2
expect(middlewares).to eq %i[strategy_a strategy_b]
end
@@ -37,7 +37,7 @@ module OpenProject
auth_provider-saml.png
)
register_auth_providers do
register_auth_providers(persist: false) do
strategy :saml do
OpenProject::AuthSaml.configuration.values.map do |h|
# Remember saml session values when logging in user
@@ -60,7 +60,7 @@ module OpenProject::OpenIDConnect
class_inflection_override("openid_connect" => "OpenIDConnect")
register_auth_providers do
register_auth_providers(persist: false) do
OmniAuth::OpenIDConnect::Providers.configure custom_options: %i[
display_name?
icon?