Skip to content

[73320] Create and use OAuth2 auth#22714

Open
shiroginne wants to merge 40 commits intodevfrom
implementation/73320-create-and-use-oauth2-auth
Open

[73320] Create and use OAuth2 auth#22714
shiroginne wants to merge 40 commits intodevfrom
implementation/73320-create-and-use-oauth2-auth

Conversation

@shiroginne
Copy link
Copy Markdown
Contributor

@shiroginne shiroginne commented Apr 10, 2026

Ticket

73320

What are you trying to accomplish?

Add OAuth2 auth for the XWiki provider.
This PR also adds the creation of the OAuth clients for future development.

Screenshots

Screenshot 2026-04-14 at 17 29 03

Merge checklist

  • Added/updated tests
  • Added/updated documentation in Lookbook (patterns, previews, etc)
  • Tested major browsers (Chrome, Firefox, Edge, ...)

Fix merge issue
…73405-add-service-contract-layer-for-wiki-providers
Refactor the rest of the code
Updated specs
…i-providers' into implementation/73320-create-and-use-oauth2-auth
@shiroginne shiroginne self-assigned this Apr 10, 2026
Base automatically changed from implementation/73405-add-service-contract-layer-for-wiki-providers to dev April 13, 2026 14:45
@shiroginne shiroginne marked this pull request as ready for review April 14, 2026 15:32
@shiroginne shiroginne requested a review from a team April 14, 2026 15:32
@github-actions
Copy link
Copy Markdown

Caution

The provided work package version does not match the core version

Details:

Please make sure that:

  • The work package version OR your pull request target branch is correct

Copy link
Copy Markdown
Contributor

@NobodysNightmare NobodysNightmare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some comments during an initial code review.

I didn't yet click through the application and will do that next.

if oauth_application.present?
concat(render(Primer::Beta::Label.new(scheme: :success)) { I18n.t(:label_completed) })
else
concat(render(Primer::Beta::Label.new(scheme: :secondary)) { I18n.t("wikis.admin.wiki_providers.oauth.label_pending") })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 For the translations here (and in any other component): Shouldn't we be able to use t (not I18n.t) and pass translation paths in their relative form.

Example:

Suggested change
concat(render(Primer::Beta::Label.new(scheme: :secondary)) { I18n.t("wikis.admin.wiki_providers.oauth.label_pending") })
concat(render(Primer::Beta::Label.new(scheme: :secondary)) { t("label_pending") })

Note that I chose this line as an example rather randomly, so let me elaborate further:

  • I'd do that on all translation strings that are specific to this current component
    • this way we ensure a consistent way to structure translations
    • translations will be grouped by the component they are used in
  • I'd not do it for translations where we know that we need the exact same translation in multiple places
    • e.g. a label_pending might already be defined somewhere extremely globally
    • but if we only know of a use-case for a pending label exactly in this component, we might only define it here
    • ... and if we only know of cases of reusing it for wiki stuff, then somewhere in the wiki namespace

oauth:
openproject_oauth: OpenProject OAuth
openproject_oauth_description: Allow XWiki to access OpenProject data using an OAuth.
openproject_oauth_description: Allow XWiki to access OpenProject data using an OAuth application. Copy the credentials below into your XWiki instance.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 I see an issue about translation abstraction here.

The current key of this translation is wikis.admin.wiki_providers.oauth.openproject_oauth_description. So apparently this is a generic key describing OAuth authentication in a wiki context. It's certainly usable for any given wiki provider that supports OAuth...

But the translation value is specific to XWiki, so it can't be used generically. The two possible solutions:

  • Replace "XWiki" with "the wiki provider" and accept that the user-facing translation is not as specific as it could be
  • Indicate the specific provider being used in the translation key, ideally in a form that's automatable
    • e.g. wikis.admin.wiki_providers.<provider_type>.oauth.openproject_oauth_description

heading: Inline page links
admin:
wiki_providers:
index_description: Add an external wiki service to link work packages to existing wiki pages or create new ones directly from OpenProject.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: I just saw this and this is a prime example of using t(".description"), which would've probably put the translation key together with other translations for the index view.

label_new_xwiki_instance: New XWiki provider
no_results_title: You don't have any wiki providers set up yet.
no_results_description: Add a wiki provider to see them here.
label_edit: Edit XWiki provider
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more side note: Up until this point the translations were sorted alphabetically.

I am still surprised, that there is no linting rule for that, because non-sorted translations really make it hard to find stuff in longer translation files.

class GeneralInformationContract < BaseContract
attribute :authentication_method
validates :authentication_method, presence: true
validates :authentication_method,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Shouldn't it be possible to combine the two validates statements into one or is there a semantical difference?


def new
@wiki_provider = Wikis::XWikiProvider.new
@wiki_provider = Wikis::XWikiProvider.new if @wiki_provider.blank?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Since ensure_valid_wizard_parameters is only called for new, can we directly call this here? I was surprised to see that @wiki_provider could be set sometimes and didn't know what would set it.

It would be clearer if the code read like this:

@wiki_provider = continue_from_wizard_params || Wikis::XWikiProvider.new

(note that I renamed the method in this example for additional clarity)


def wiki_provider_wizard(wiki_provider)
Wikis::Adapters::Registry.resolve("#{wiki_provider}.components.setup_wizard")
.new(model: wiki_provider, user: current_user)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 This is something that we should collectively keep an eye on. I was surprised that this wasn't written as wiki_provider.resolve("components.setup_wizard"), until I realized that it required passing in the current_user as well.

This means two thing:

  1. Not all registry components can be instantiated the same way
    • This alone can be a cause for concern, if we aimed at a very uniform interface
    • but we might want to accept it
  2. Not all registry components can be instantiated through the resolve method right now

If we accept that the interface to create things might require additional inputs sometimes, we could extend resolve to support that, e.g.

def resolve(registry_path, **init_options)
  Adapters::Registry["#{self.class.registry_prefix}.#{registry_path}"].new(self, **init_options)
end

This would mean we can call it like wiki_provider.resolve("components.setup_wizard", user: current_user)

CC @Kharonus @mereghost

label: I18n.t("activerecord.attributes.wikis/xwiki_provider.authentication_method"),
required: true
) do |select|
Wikis::XWikiProvider::AUTHENTICATION_METHODS.each do |method|
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 This is another case of mixing abstraction layers. We are in a class called Wikis::Admin::AuthenticationMethodSelectForm. Very generic, not XWiki related, but it acts on the things supported by Wikis::XWikiProvider.

The two ways forward that I see:

  1. Make this form generic, but hand in information about the provider, so that the form can know which auth methods are supported for the current provider
  2. Communicate that this is a specific form, by indicating the XWiki in its name

has_many :page_links, dependent: :destroy

scope :enabled, -> { where(enabled: true) }
scope :visible, ->(user = User.current) { user.admin? ? all : none }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Is this what visibility is supposed to be for wiki providers?

This would mean that a non-admin is not allowed to see any wiki providers ever.

Considering that we might want to show UI elements that indicate the name of a wiki provider, this doesn't seem to be feasible to me.

I understand that we don't want to expose all details of a wiki provider to everyone (once an API for wiki providers exists), but the basic ability to see a wiki provider should be given for anyone that's able to view_wiki_links in any project.


def url_is_https
return if url.blank?
def authenticate_via_oauth2?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Note that both authentication methods are OAuth2 based and both have OAuth2 in their name. This was intentional, because OIDC is not a requirement for any of them, still everyone keeps calling one of the two methods "OIDC based" 🤷

This method should be called accordingly. E.g.

Suggested change
def authenticate_via_oauth2?
def authenticate_via_two_way_oauth2?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants