diff --git a/app/components/my/access_token/oauth_client/row_component.rb b/app/components/my/access_token/oauth_client/row_component.rb index 8567c2482741..2a0b83f98468 100644 --- a/app/components/my/access_token/oauth_client/row_component.rb +++ b/app/components/my/access_token/oauth_client/row_component.rb @@ -43,12 +43,7 @@ def name end def integration_type - integration_class_name = client_token.oauth_client.integration_type - integration_class = begin - integration_class_name.constantize - rescue NameError - nil - end + integration_class = client_token.oauth_client.integration&.class return I18n.t("my_account.access_tokens.oauth_client.unknown_integration") unless integration_class diff --git a/modules/wikis/app/components/wikis/admin/side_panel/health_status_component.html.erb b/modules/wikis/app/components/wikis/admin/side_panel/health_status_component.html.erb new file mode 100644 index 000000000000..6cb38e5857cf --- /dev/null +++ b/modules/wikis/app/components/wikis/admin/side_panel/health_status_component.html.erb @@ -0,0 +1,87 @@ +<%#-- 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. + +++#%> + +<%= + component_wrapper(tag: :turbo_frame, refresh: :morph) do + render(Primer::OpenProject::SidePanel::Section.new) do |section| + section.with_title { t(".title") } + + flex_layout do |container| + if report.present? + container.with_row do + header = summary_header + + concat(render(Primer::Beta::Octicon.new(icon: header[:icon], color: header[:icon_color], mr: 2))) + concat(render(Primer::Beta::Text.new(font_weight: :bold)) { header[:text] }) + end + + container.with_row(mt: 2) do + render(Primer::Beta::Text.new(color: :muted)) { summary_description } + end + + container.with_row(mt: 2) do + render( + Primer::Beta::Button.new( + scheme: :link, + color: :default, + font_weight: :bold, + tag: :a, + href: admin_settings_wiki_provider_health_status_report_path(model) + ) + ) do |button| + button.with_leading_visual_icon(icon: :meter) + t(".open_report") + end + end + else + container.with_row do + primer_form_with( + model:, + url: create_health_status_report_admin_settings_wiki_provider_health_status_report_path(model), + method: :post, + data: { turbo: true } + ) do + render( + Primer::Beta::Button.new( + scheme: :link, + color: :default, + font_weight: :bold, + type: :submit + ) + ) do |button| + button.with_leading_visual_icon(icon: :meter) + t(".run_checks") + end + end + end + end + end + end + end +%> diff --git a/modules/wikis/app/components/wikis/admin/side_panel/health_status_component.rb b/modules/wikis/app/components/wikis/admin/side_panel/health_status_component.rb new file mode 100644 index 000000000000..edbde4c557c5 --- /dev/null +++ b/modules/wikis/app/components/wikis/admin/side_panel/health_status_component.rb @@ -0,0 +1,79 @@ +# 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. +#++ + +module Wikis + module Admin + module SidePanel + class HealthStatusComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + private + + def report + model.health_reports.order(created_at: :asc).last + end + + def summary_header + tally = report.tally + case tally + in { failure: 1.. } + { + icon: :alert, + icon_color: :danger, + text: t(".checks.failures", count: tally[:failure]) + } + in { warning: 1.. } + { + icon: :alert, + icon_color: :attention, + text: t(".checks.warnings", count: tally[:warning]) + } + else + { icon: :"check-circle", icon_color: :success, text: t(".checks.success") } + end + end + + def summary_description + text = if report.healthy? + t(".summary.success") + elsif report.unhealthy? + t(".summary.failure") + else + t(".summary.warning") + end + + "#{text} #{t('.last_check', datetime: helpers.format_time(report.created_at))}" + end + end + end + end +end diff --git a/modules/wikis/app/components/wikis/admin/side_panel_component.html.erb b/modules/wikis/app/components/wikis/admin/side_panel_component.html.erb new file mode 100644 index 000000000000..ffef27476342 --- /dev/null +++ b/modules/wikis/app/components/wikis/admin/side_panel_component.html.erb @@ -0,0 +1,7 @@ +<%= + component_wrapper do + render(Primer::OpenProject::SidePanel.new) do |panel| + panel.with_section(Wikis::Admin::SidePanel::HealthStatusComponent.new(wiki_provider)) + end + end +%> diff --git a/modules/wikis/app/components/wikis/admin/side_panel_component.rb b/modules/wikis/app/components/wikis/admin/side_panel_component.rb new file mode 100644 index 000000000000..794377b4c449 --- /dev/null +++ b/modules/wikis/app/components/wikis/admin/side_panel_component.rb @@ -0,0 +1,41 @@ +# 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. +#++ + +module Wikis + module Admin + class SidePanelComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + alias wiki_provider model + end + end +end diff --git a/modules/wikis/app/controllers/wikis/admin/health_status_controller.rb b/modules/wikis/app/controllers/wikis/admin/health_status_controller.rb new file mode 100644 index 000000000000..53f95e59b235 --- /dev/null +++ b/modules/wikis/app/controllers/wikis/admin/health_status_controller.rb @@ -0,0 +1,102 @@ +# 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. +#++ + +module Wikis + module Admin + class HealthStatusController < ApplicationController + include OpTurbo::ComponentStream + + layout :admin_or_frame_layout + + before_action :require_admin + before_action :find_provider + + def admin_or_frame_layout + return "turbo_rails/frame" if turbo_frame_request? + + "admin" + end + + def show + @report = @provider.health_reports.order(created_at: :asc).last + + respond_to do |format| + format.html + format.text do + return head :not_found if @report.nil? + + timestamp = @report.created_at.iso8601 + filename = "#{@provider.name.underscore}_health_report_#{timestamp}.txt" + send_data text_report(timestamp), filename:, type: "text/plain", disposition: :attachment + end + end + end + + def create + create_and_store_report + + redirect_to admin_settings_wiki_provider_health_status_report_path(@provider), status: :see_other + end + + def create_health_status_report + create_and_store_report + + update_via_turbo_stream(component: SidePanel::HealthStatusComponent.new(@provider)) + respond_to_with_turbo_streams + end + + private + + def text_report(timestamp) + { + provider: @provider.name, + provider_type: @provider.to_s, + configuration: @provider.non_confidential_configuration, + ran_at: timestamp, + results: @report ? @report.results.map(&:to_h) : [] + }.to_yaml(stringify_names: true) + end + + def find_provider + @provider = ::Wikis::Provider.visible.find(params[:wiki_provider_id]) + end + + def create_and_store_report + report = validator.call + report.save! + report + end + + def validator + @validator ||= @provider.resolve("validators.connection") + end + end + end +end diff --git a/modules/wikis/app/controllers/wikis/admin/wiki_providers_controller.rb b/modules/wikis/app/controllers/wikis/admin/wiki_providers_controller.rb index 6f78a699a422..a6037eb8e1b5 100644 --- a/modules/wikis/app/controllers/wikis/admin/wiki_providers_controller.rb +++ b/modules/wikis/app/controllers/wikis/admin/wiki_providers_controller.rb @@ -42,7 +42,7 @@ class WikiProvidersController < ApplicationController menu_item :wiki_providers def index - @wiki_providers = Wikis::Provider.visible + @wiki_providers = editable_wiki_providers end def new @@ -150,13 +150,13 @@ def current_step_name end def find_wiki_provider - @wiki_provider = Wikis::XWikiProvider.visible.find(params[:id]) + @wiki_provider = editable_wiki_providers.find(params[:id]) end def continue_from_wizard_params return if params[:continue_wizard].blank? - Wikis::Provider.visible.find(params[:continue_wizard]) + editable_wiki_providers.find(params[:continue_wizard]) end def wiki_provider_params @@ -166,6 +166,10 @@ def wiki_provider_params def wiki_provider_wizard(wiki_provider) wiki_provider.resolve("components.setup_wizard", user: current_user) end + + def editable_wiki_providers + Wikis::Provider.visible.where.not(type: InternalProvider.name) + end end end end diff --git a/modules/wikis/app/models/wikis/internal_provider.rb b/modules/wikis/app/models/wikis/internal_provider.rb index 776eb15eff50..684546a28dad 100644 --- a/modules/wikis/app/models/wikis/internal_provider.rb +++ b/modules/wikis/app/models/wikis/internal_provider.rb @@ -36,6 +36,8 @@ def registry_prefix = "internal" def user_connected?(_user) = true + def configured? = true + def name model_name.human end diff --git a/modules/wikis/app/models/wikis/provider.rb b/modules/wikis/app/models/wikis/provider.rb index 4d591e843304..5961ad3a6d6d 100644 --- a/modules/wikis/app/models/wikis/provider.rb +++ b/modules/wikis/app/models/wikis/provider.rb @@ -33,6 +33,7 @@ class Provider < ApplicationRecord self.table_name = "wiki_providers" has_many :page_links, dependent: :destroy + has_many :health_reports, as: :subject, dependent: :delete_all scope :enabled, -> { where(enabled: true) } scope :visible, ->(_user = User.current) { all } @@ -41,6 +42,14 @@ class Provider < ApplicationRecord before_create :generate_universal_identifier + def configured? = raise SubclassResponsibilityError + + def non_confidential_configuration + { + enabled: + } + end + def to_s = self.class.registry_prefix def user_connected?(_user) = raise SubclassResponsibilityError diff --git a/modules/wikis/app/models/wikis/xwiki_provider.rb b/modules/wikis/app/models/wikis/xwiki_provider.rb index bf2c980bf830..a10731fec2cf 100644 --- a/modules/wikis/app/models/wikis/xwiki_provider.rb +++ b/modules/wikis/app/models/wikis/xwiki_provider.rb @@ -48,6 +48,20 @@ def registry_prefix = "xwiki" def generate_client_id = SecureRandom.uuid end + def configured? + url.present? && + oauth_client.present? && + oauth_application.present? + end + + def non_confidential_configuration + super.merge( + url:, + oauth_client_id: oauth_client&.client_id, + oauth_application_client_id: oauth_application&.uid + ) + end + def user_connected?(user) return true if oauth_client.blank? diff --git a/modules/wikis/app/services/wikis/adapters/providers/xwiki/registry.rb b/modules/wikis/app/services/wikis/adapters/providers/xwiki/registry.rb index d05f417febfd..77399a60b4da 100644 --- a/modules/wikis/app/services/wikis/adapters/providers/xwiki/registry.rb +++ b/modules/wikis/app/services/wikis/adapters/providers/xwiki/registry.rb @@ -65,6 +65,10 @@ module XWiki register(:referencing_pages, Queries::ReferencingPages) register(:relation_page_links, Queries::RelationPageLinks) end + + namespace("validators") do + register("connection", Validators::ConnectionValidator) + end end end end diff --git a/modules/wikis/app/services/wikis/adapters/providers/xwiki/validators/authentication_validator.rb b/modules/wikis/app/services/wikis/adapters/providers/xwiki/validators/authentication_validator.rb new file mode 100644 index 000000000000..8719d866e717 --- /dev/null +++ b/modules/wikis/app/services/wikis/adapters/providers/xwiki/validators/authentication_validator.rb @@ -0,0 +1,90 @@ +# 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. +#++ + +module Wikis + module Adapters + module Providers + module XWiki + module Validators + class AuthenticationValidator < HealthReports::ValidatorGroup + def self.key = :authentication + + private + + def validate + register_checks( + :existing_token, + :user_bound_request + ) + + existing_token + user_bound_request + end + + def existing_token + if OAuthClientToken.for_user_and_client(user, subject.oauth_client).exists? + pass_check(:existing_token) + else + warn_check(:existing_token, :xwiki_oauth_token_missing, halt_validation: true) + end + end + + def user_bound_request + # TODO: how to perform this call for real? how to authenticate? + access_token = OAuthClientToken.for_user_and_client(user, subject.oauth_client).first&.access_token + + subject.resolve("queries.user") + .call(Wikis::Adapters::Input::UserQuery.new(access_token:)) + .or { fail_check(:user_bound_request, oauth_request_error_code(it)) } + + pass_check(:user_bound_request) + end + + def oauth_request_error_code(error) + case error.code + when :connection_error + :xwiki_oauth_connection_error + when :unauthorized + :xwiki_oauth_unauthorized + else + # :request_failed (wrong status code) and other unexpected error codes + :xwiki_oauth_request_error + end + end + + def user + User.current + end + end + end + end + end + end +end diff --git a/modules/wikis/app/services/wikis/adapters/providers/xwiki/validators/configuration_validator.rb b/modules/wikis/app/services/wikis/adapters/providers/xwiki/validators/configuration_validator.rb new file mode 100644 index 000000000000..7ebdc22454f6 --- /dev/null +++ b/modules/wikis/app/services/wikis/adapters/providers/xwiki/validators/configuration_validator.rb @@ -0,0 +1,62 @@ +# 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. +#++ + +module Wikis + module Adapters + module Providers + module XWiki + module Validators + class ConfigurationValidator < HealthReports::ValidatorGroup + def self.key = :base_configuration + + private + + def validate + register_checks( + :provider_configured + ) + + provider_configured + # TODO: check dependencies (e.g. OpenProject extension) and version requirements + end + + def provider_configured + if subject.configured? + pass_check(:provider_configured) + else + fail_check(:provider_configured, :not_configured) + end + end + end + end + end + end + end +end diff --git a/modules/wikis/app/services/wikis/adapters/providers/xwiki/validators/connection_validator.rb b/modules/wikis/app/services/wikis/adapters/providers/xwiki/validators/connection_validator.rb new file mode 100644 index 000000000000..ece00dfea3f5 --- /dev/null +++ b/modules/wikis/app/services/wikis/adapters/providers/xwiki/validators/connection_validator.rb @@ -0,0 +1,50 @@ +# 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. +#++ + +module Wikis + module Adapters + module Providers + module XWiki + module Validators + class ConnectionValidator < HealthReports::Validator + register_group ConfigurationValidator + register_group AuthenticationValidator, precondition: ->(_, result) { result.group(:base_configuration).non_failure? } + + def initialize(model:) + # adapting interface expected by wiki module's resolve interface to + # the interface of the HealthReports::Validator base class + super(model) + end + end + end + end + end + end +end diff --git a/modules/wikis/app/views/wikis/admin/health_status/show.html.erb b/modules/wikis/app/views/wikis/admin/health_status/show.html.erb new file mode 100644 index 000000000000..2efcc90ddad2 --- /dev/null +++ b/modules/wikis/app/views/wikis/admin/health_status/show.html.erb @@ -0,0 +1,113 @@ +<%#-- 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. + +++#%> + +<% page_title = t(".title") %> + +<% html_title(t(:label_administration), t(:project_module_wiki_platforms), @provider.name, page_title) -%> + +<%= turbo_frame_tag "health_status_report", refresh: :morph do %> + <%= + render(Primer::OpenProject::PageHeader.new) do |header| + header.with_title { page_title } + header.with_description do + if @report.present? + t(".last_check", datetime: format_time(@report.created_at)) + else + t(".no_health_report") + end + end + header.with_breadcrumbs( + [ + { href: admin_index_path, text: t("label_administration") }, + { href: admin_settings_wiki_providers_path, text: t(:project_module_wiki_platforms) }, + { href: edit_admin_settings_wiki_provider_path(@provider), text: @provider.name }, + page_title + ] + ) + end + %> + + <% if @report.present? %> + <%= + rerun_label = t(".actions.rerun_checks") + download_label = t(".actions.download_report") + + render(Primer::OpenProject::SubHeader.new) do |subheader| + subheader.with_action_button( + scheme: :secondary, + leading_icon: :download, + label: download_label, + tag: :a, + href: admin_settings_wiki_provider_health_status_report_path(@provider, format: :txt), + data: { turbo: true }, + ) do + download_label + end + + subheader.with_action_button( + scheme: :primary, + leading_icon: :plus, + label: rerun_label, + tag: :a, + href: admin_settings_wiki_provider_health_status_report_path(@provider), + data: { turbo_method: :post, turbo: true }, + ) do + rerun_label + end + end + %> + <% end %> + + <%= + render(Primer::Alpha::Layout.new(stacking_breakpoint: :lg)) do |page| + page.with_sidebar(col_placement: :end, row_placement: :end) + page.with_main do %> + + <% if @report.present? %> + <%= render(HealthReports::ReportComponent.new(@report, i18n_scope: "wikis.health_checks")) %> + <% else %> + <%= + render(Primer::Beta::Blankslate.new(border: true)) do |placeholder| + placeholder.with_visual_icon(icon: :meter) + placeholder.with_heading(tag: :h3) { t(".no_health_report") } + placeholder.with_description { t(".no_health_report_description") } + placeholder.with_primary_action( + href: admin_settings_wiki_provider_health_status_report_path(@provider), + data: { turbo_method: :post, turbo: true }, + aria: { label: t(".actions.run_checks") } + ) do + t(".actions.run_checks") + end + end + %> + <% end %> + <% end %> + <% end %> +<% end %> + diff --git a/modules/wikis/app/views/wikis/admin/wiki_providers/edit.html.erb b/modules/wikis/app/views/wikis/admin/wiki_providers/edit.html.erb index 57d37d749d32..7b0dfc73ae08 100644 --- a/modules/wikis/app/views/wikis/admin/wiki_providers/edit.html.erb +++ b/modules/wikis/app/views/wikis/admin/wiki_providers/edit.html.erb @@ -76,5 +76,15 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <%= content_tag("turbo-frame", id: "wiki_provider_content") do %> - <%= render(Wikis::Admin::WikiProviderViewComponent.new(@wiki_provider, wizard: @wizard)) %> + <%= + render(Primer::Alpha::Layout.new(stacking_breakpoint: :lg)) do |page| + page.with_main do + render(Wikis::Admin::WikiProviderViewComponent.new(@wiki_provider, wizard: @wizard)) + end + + page.with_sidebar(col_placement: :end, row_placement: :start) do + render(Wikis::Admin::SidePanelComponent.new(@wiki_provider)) + end + end + %> <% end %> diff --git a/modules/wikis/config/locales/en.yml b/modules/wikis/config/locales/en.yml index 4ee55a28be5c..6aad568dd434 100644 --- a/modules/wikis/config/locales/en.yml +++ b/modules/wikis/config/locales/en.yml @@ -18,10 +18,15 @@ en: one: Inline page link other: Inline page links wikis/internal_provider: Internal wiki + wikis/provider: + one: Wiki provider + other: Wiki providers wikis/relation_page_link: one: Relation page link other: Relation page links - wikis/xwiki_provider: XWiki provider + wikis/xwiki_provider: + one: XWiki provider + other: XWiki providers permission_manage_wiki_page_links: Manage Wiki Page Links project_module_wiki_platforms: Wiki providers wikis: @@ -30,6 +35,20 @@ en: done_continue: Done, continue save_and_continue: Save and continue wiki_page: Wiki page + health_checks: + authentication: + existing_token: User token + header: Authentication + user_bound_request: User-based request authentication + base_configuration: + header: Configuration + provider_configured: Configuration complete + errors: + not_configured: The connection could not be validated. Please finish configuration first. + xwiki_oauth_connection_error: OpenProject could not connect to the configured XWiki instance. + xwiki_oauth_request_error: An unexpected error occured when trying to communicate with the XWiki instance. + xwiki_oauth_token_missing: OpenProject cannot test the user-level communication with XWiki as the user did not yet link their XWiki account. + xwiki_oauth_unauthorized: The user token was not recognized by XWiki. work_package_wikis_tab_component: inline_page_links: Inline page links referencing_pages: Referenced in @@ -55,6 +74,16 @@ en: application_secret: Application secret oauth_client_form_component: client_id: Client ID + health_status: + show: + actions: + download_report: Download + rerun_checks: Re-run all checks + run_checks: Run checks now + last_check: 'Last check: %{datetime}' + no_health_report: No report available + no_health_report_description: Run the checks now for a full health status report for this wiki provider. + title: Health Report oauth_application_info_component: confirm_replace_oauth_application: This action will reset the current OAuth credentials. After confirming you will have to reenter the credentials in your XWiki instance and all users will have to reauthorize. Are you sure you want to proceed? label_pending: Pending @@ -63,6 +92,24 @@ en: confirm_replace_oauth_client: This action will reset the current XWiki OAuth credentials. All users will need to reauthorize against XWiki. Are you sure you want to proceed? label_pending: Pending replace_oauth_client: Replace XWiki OAuth application + side_panel: + health_status_component: + checks: + failures: + one: "%{count} check failed" + other: "%{count} checks failed" + success: All checks passed + warnings: + one: "%{count} check returned a warning" + other: "%{count} checks returned a warning" + last_check: 'Last check: %{datetime}' + open_report: Open full health report + run_checks: Run checks now + summary: + failure: Some checks failed and the system does not work as expected. + success: All connections and systems are working as expected. + warning: Some checks returned a warning. This can lead to unexpected behaviour. + title: Health status report wiki_provider_list_component: label_creation_time: Created label_name: Name diff --git a/modules/wikis/config/routes.rb b/modules/wikis/config/routes.rb index ae26ce82433d..1851e1f4eac3 100644 --- a/modules/wikis/config/routes.rb +++ b/modules/wikis/config/routes.rb @@ -37,6 +37,11 @@ get :edit_general_info delete :replace_oauth_application end + + resource :health_status_report, controller: "/wikis/admin/health_status", only: %i[show create] do + post :create_health_status_report + end + resource :oauth_client, controller: "/wikis/admin/oauth_clients", only: %i[new create] do patch :update, on: :member end diff --git a/modules/wikis/spec/factories/wiki_provider_factory.rb b/modules/wikis/spec/factories/wiki_provider_factory.rb index bc7af84b3884..45a96b8ec831 100644 --- a/modules/wikis/spec/factories/wiki_provider_factory.rb +++ b/modules/wikis/spec/factories/wiki_provider_factory.rb @@ -42,5 +42,12 @@ factory :xwiki_provider, class: "Wikis::XWikiProvider", parent: :wiki_provider do url { "https://xwiki.example.com/" } + + trait :with_oauth_configured do + after(:create) do |provider, _evaluator| + create(:oauth_client, integration: provider) + create(:oauth_application, integration: provider) + end + end end end diff --git a/modules/wikis/spec/services/wikis/adapters/providers/xwiki/validators/authentication_validator_spec.rb b/modules/wikis/spec/services/wikis/adapters/providers/xwiki/validators/authentication_validator_spec.rb new file mode 100644 index 000000000000..411bb895fac6 --- /dev/null +++ b/modules/wikis/spec/services/wikis/adapters/providers/xwiki/validators/authentication_validator_spec.rb @@ -0,0 +1,114 @@ +# 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. +#++ + +require "spec_helper" +require_module_spec_helper + +RSpec.describe Wikis::Adapters::Providers::XWiki::Validators::AuthenticationValidator do + subject(:validation_result) { described_class.new(provider).call } + + let(:provider) { create(:xwiki_provider, :with_oauth_configured) } + let(:query_double) { instance_double(Wikis::Adapters::Providers::XWiki::Queries::UserQuery, call: Success()) } + + let(:client_token) { create(:oauth_client_token, user: current_user, oauth_client: provider.oauth_client) } + + current_user { create(:user) } + + before do + query_class_double = class_double(Wikis::Adapters::Providers::XWiki::Queries::UserQuery, new: query_double) + Wikis::Adapters::Registry.stub("xwiki.queries.user", query_class_double) + + client_token + end + + it "returns a ResultGroup" do + expect(validation_result).to be_a(HealthReport::ResultGroup) + expect(validation_result).to be_success + end + + context "when the user has no token" do + let(:client_token) { nil } + + it { is_expected.to be_warning } + + it "indicates that the user has no token" do + expect(validation_result[:existing_token].code).to eq(:xwiki_oauth_token_missing) + end + end + + context "when the user-bound request fails" do + let(:query_double) do + instance_double( + Wikis::Adapters::Providers::XWiki::Queries::UserQuery, + call: Failure(Wikis::Adapters::Results::Error.new(source: self, code: error_code)) + ) + end + + context "with a connection error (network timeout, etc)" do + let(:error_code) { :connection_error } + + it { is_expected.to be_failure } + + it "indicates that a connection error occured" do + expect(validation_result[:user_bound_request].code).to eq(:xwiki_oauth_connection_error) + end + end + + context "with an authorization error" do + let(:error_code) { :unauthorized } + + it { is_expected.to be_failure } + + it "indicates that an authorization error occured" do + expect(validation_result[:user_bound_request].code).to eq(:xwiki_oauth_unauthorized) + end + end + + context "with an unexpected response code" do + let(:error_code) { :request_failed } + + it { is_expected.to be_failure } + + it "indicates that an unexpected error occured" do + expect(validation_result[:user_bound_request].code).to eq(:xwiki_oauth_request_error) + end + end + + context "with an unexpected error" do + let(:error_code) { :error } + + it { is_expected.to be_failure } + + it "indicates that an unexpected error occured" do + expect(validation_result[:user_bound_request].code).to eq(:xwiki_oauth_request_error) + end + end + end +end diff --git a/modules/wikis/spec/services/wikis/adapters/providers/xwiki/validators/configuration_validator_spec.rb b/modules/wikis/spec/services/wikis/adapters/providers/xwiki/validators/configuration_validator_spec.rb new file mode 100644 index 000000000000..cb19771732d0 --- /dev/null +++ b/modules/wikis/spec/services/wikis/adapters/providers/xwiki/validators/configuration_validator_spec.rb @@ -0,0 +1,54 @@ +# 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. +#++ + +require "spec_helper" + +RSpec.describe Wikis::Adapters::Providers::XWiki::Validators::ConfigurationValidator do + subject(:validation_result) { described_class.new(provider).call } + + let(:provider) { create(:xwiki_provider, :with_oauth_configured) } + + it "returns a ResultGroup" do + expect(validation_result).to be_a(HealthReport::ResultGroup) + expect(validation_result).to be_success + end + + context "when the provider is not configured completely" do + before do + allow(provider).to receive(:configured?).and_return(false) + end + + it { is_expected.to be_failure } + + it "indicates that the provider is not configured" do + expect(validation_result[:provider_configured].code).to eq(:not_configured) + end + end +end diff --git a/modules/wikis/spec/spec_helper.rb b/modules/wikis/spec/spec_helper.rb new file mode 100644 index 000000000000..72f308d2e316 --- /dev/null +++ b/modules/wikis/spec/spec_helper.rb @@ -0,0 +1,43 @@ +# 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. +#++ + +require "dry/container/stub" +require "dry/monads" + +RSpec.configure do |config| + config.include Dry::Monads[:result] + + config.prepend_before do + Wikis::Adapters::Registry.enable_stubs! + end + config.append_after do + Wikis::Adapters::Registry.unstub + end +end