From 3c5113ae65efdc201f9a82f0b7605ca139032304 Mon Sep 17 00:00:00 2001 From: Eric Schubert Date: Wed, 3 Apr 2024 11:24:52 +0200 Subject: [PATCH 1/4] [#53718] added access management form section - only for one drive storage configuration - removed previous project folder checkbox --- .../access_management_component.html.erb | 34 ++++++ .../admin/access_management_component.rb | 53 +++++++++ .../access_management_form_component.html.erb | 34 ++++++ .../forms/access_management_form_component.rb | 42 ++++++++ .../general_info_form_component.html.erb | 19 ---- .../forms/general_info_form_component.rb | 10 -- .../admin/storage_view_component.html.erb | 14 +++ .../admin/access_management_controller.rb | 101 ++++++++++++++++++ .../storages/admin/storages_controller.rb | 19 +++- .../storages/admin/access_management_form.rb | 58 ++++++++++ .../app/models/storages/one_drive_storage.rb | 9 ++ .../storages/app/models/storages/storage.rb | 28 ++--- .../access_management/create.turbo_stream.erb | 36 +++++++ .../access_management/edit.turbo_stream.erb | 32 ++++++ .../admin/storages/create.turbo_stream.erb | 4 +- modules/storages/config/locales/en.yml | 15 ++- modules/storages/config/routes.rb | 2 + 17 files changed, 458 insertions(+), 52 deletions(-) create mode 100644 modules/storages/app/components/storages/admin/access_management_component.html.erb create mode 100644 modules/storages/app/components/storages/admin/access_management_component.rb create mode 100644 modules/storages/app/components/storages/admin/forms/access_management_form_component.html.erb create mode 100644 modules/storages/app/components/storages/admin/forms/access_management_form_component.rb create mode 100644 modules/storages/app/controllers/storages/admin/access_management_controller.rb create mode 100644 modules/storages/app/forms/storages/admin/access_management_form.rb create mode 100644 modules/storages/app/views/storages/admin/access_management/create.turbo_stream.erb create mode 100644 modules/storages/app/views/storages/admin/access_management/edit.turbo_stream.erb diff --git a/modules/storages/app/components/storages/admin/access_management_component.html.erb b/modules/storages/app/components/storages/admin/access_management_component.html.erb new file mode 100644 index 000000000000..0f5d89913214 --- /dev/null +++ b/modules/storages/app/components/storages/admin/access_management_component.html.erb @@ -0,0 +1,34 @@ +<%= + grid_layout('op-storage-view--row', tag: :div, align_items: :center) do |grid| + grid.with_area(:item, tag: :div, mr: 3) do + concat(render(Primer::Beta::Text.new(font_weight: :bold, mr: 1, test_selector: 'access-management-label')) do + I18n.t('storages.file_storage_view.access_management.subtitle') + end) + concat(configuration_check_label_for(:access_management_configured)) + end + + grid.with_area(:description, tag: :div, color: :subtle, test_selector: 'access-management-description') do + render(Primer::Beta::Text.new) { access_management_description } + end + + if editable_storage? + grid.with_area(:"icon-button", tag: :div, color: :muted) do + flex_layout(justify_content: :flex_end) do |icons_container| + icons_container.with_column do + render( + Primer::Beta::IconButton.new( + icon: :pencil, + tag: :a, + scheme: :invisible, + href: edit_admin_settings_storage_access_management_path(storage), + aria: { label: I18n.t('storages.label_edit_storage_access_management') }, + test_selector: 'storage-edit-access-management-button', + data: { turbo_stream: true } + ) + ) + end + end + end + end + end +%> diff --git a/modules/storages/app/components/storages/admin/access_management_component.rb b/modules/storages/app/components/storages/admin/access_management_component.rb new file mode 100644 index 000000000000..21adaff978ed --- /dev/null +++ b/modules/storages/app/components/storages/admin/access_management_component.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 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 Storages::Admin + class AccessManagementComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + include StorageViewInformation + + alias_method :storage, :model + + private + + def access_management_description + if storage.automatic_management_new_record? + return I18n.t("storages.file_storage_view.access_management.setup_incomplete") + end + + case storage.automatic_management_enabled + when true + I18n.t("storages.file_storage_view.access_management.automatic_management") + when false + I18n.t("storages.file_storage_view.access_management.manual_management") + end + end + end +end diff --git a/modules/storages/app/components/storages/admin/forms/access_management_form_component.html.erb b/modules/storages/app/components/storages/admin/forms/access_management_form_component.html.erb new file mode 100644 index 000000000000..f8a28e105a90 --- /dev/null +++ b/modules/storages/app/components/storages/admin/forms/access_management_form_component.html.erb @@ -0,0 +1,34 @@ +<%= + render(Primer::Beta::Text.new(tag: :div, test_selector: 'storage-access-management-form')) do + primer_form_with( + model:, + url: admin_settings_storage_access_management_path(storage), + method: :post + ) do |form| + flex_layout do |access_management_row| + access_management_row.with_row(mb: 3) do + render(Primer::Beta::Text.new(font_weight: :bold)) { I18n.t(:'storages.file_storage_view.access_management.subtitle') } + end + + access_management_row.with_row(mb: 3) do + render(Primer::Beta::Text.new) { I18n.t(:'storages.file_storage_view.access_management.description') } + end + + access_management_row.with_row do + render(Storages::Admin::AccessManagementForm.new(form, storage:)) + end + + access_management_row.with_row do + render( + Storages::Admin::SubmitOrCancelForm.new( + form, + storage:, + submit_button_options: {}, + cancel_button_options: { href: cancel_button_path } + ) + ) + end + end + end + end +%> diff --git a/modules/storages/app/components/storages/admin/forms/access_management_form_component.rb b/modules/storages/app/components/storages/admin/forms/access_management_form_component.rb new file mode 100644 index 000000000000..779b1b4fce8c --- /dev/null +++ b/modules/storages/app/components/storages/admin/forms/access_management_form_component.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 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 Storages::Admin::Forms + class AccessManagementFormComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + alias_method :storage, :model + + private + + def cancel_button_path + edit_admin_settings_storage_path(storage) + end + end +end diff --git a/modules/storages/app/components/storages/admin/forms/general_info_form_component.html.erb b/modules/storages/app/components/storages/admin/forms/general_info_form_component.html.erb index 6aa624bb9e3a..ab570f4ff19e 100644 --- a/modules/storages/app/components/storages/admin/forms/general_info_form_component.html.erb +++ b/modules/storages/app/components/storages/admin/forms/general_info_form_component.html.erb @@ -32,25 +32,6 @@ end if storage.provider_type_one_drive? - general_info_row.with_row(mb: 3) do - render(Primer::Beta::Text.new(font_weight: :bold)) { I18n.t(:'storages.label_managed_project_folders.automatically_managed_folders') } - end - - general_info_row.with_row(mb: 3) do - render(Primer::Beta::Text.new) { I18n.t("storages.page_titles.managed_project_folders.subtitle") } - end - - general_info_row.with_row(mb: 3) do - render(Storages::Admin::ManagedProjectFolders::AutomaticManagementCheckbox.new(form, storage:)) - end - - general_info_row.with_row(mb: 3, - data: { - 'storages--automatically-managed-project-folders-form-target': "oneDriveInformationText" - }) do - render(Primer::Beta::Text.new) { information_for_one_drive_automatic_management } - end - general_info_row.with_row(mb: 3) do render(Storages::Admin::ProviderDriveIdInputForm.new(form)) end diff --git a/modules/storages/app/components/storages/admin/forms/general_info_form_component.rb b/modules/storages/app/components/storages/admin/forms/general_info_form_component.rb index e2799ad58107..788c426a4c64 100644 --- a/modules/storages/app/components/storages/admin/forms/general_info_form_component.rb +++ b/modules/storages/app/components/storages/admin/forms/general_info_form_component.rb @@ -89,16 +89,6 @@ def caption_for_provider_type(provider_type) ).html_safe end - def information_for_one_drive_automatic_management - href = ::OpenProject::Static::Links[:storage_docs][:one_drive_drive_id_guide][:href] - I18n.t( - "storages.page_titles.managed_project_folders.one_drive_information", - drive_id_link_text: render(Primer::Beta::Link.new(href:, target: "_blank")) do - I18n.t("storages.instructions.one_drive.documentation_link_text") - end - ).html_safe - end - def application_link_text_for(href, link_text) render(Primer::Beta::Link.new(href:, target: "_blank")) { link_text } end diff --git a/modules/storages/app/components/storages/admin/storage_view_component.html.erb b/modules/storages/app/components/storages/admin/storage_view_component.html.erb index 12c8930ca8c6..fc9a61aca752 100644 --- a/modules/storages/app/components/storages/admin/storage_view_component.html.erb +++ b/modules/storages/app/components/storages/admin/storage_view_component.html.erb @@ -59,6 +59,20 @@ end if storage.provider_type_one_drive? + component.with_row(scheme: :neutral, color: :muted) do + grid_layout('op-storage-view--row', tag: :div, align_items: :center) do |grid| + grid.with_area(:item, tag: :div, mr: 3) do + render(Primer::Beta::Text.new(font_weight: :semibold, mr: 1)) { I18n.t('storages.file_storage_view.access_management.title') } + end + end + end + + component.with_row(scheme: :default) do + render(OpTurbo::FrameComponent.new(id: :access_management_section)) do + render(Storages::Admin::AccessManagementComponent.new(storage)) + end + end + component.with_row(scheme: :neutral, color: :muted) do grid_layout('op-storage-view--row', tag: :div, align_items: :center) do |grid| grid.with_area(:item, tag: :div, mr: 3) do diff --git a/modules/storages/app/controllers/storages/admin/access_management_controller.rb b/modules/storages/app/controllers/storages/admin/access_management_controller.rb new file mode 100644 index 000000000000..bd2e1201fd11 --- /dev/null +++ b/modules/storages/app/controllers/storages/admin/access_management_controller.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 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 Storages::Admin::AccessManagementController < ApplicationController + layout "admin" + + before_action :require_admin + + model_object Storages::OneDriveStorage + before_action :find_model_object, only: %i[new create edit] + + # menu_item is defined in the Redmine::MenuManager::MenuController + # module, included from ApplicationController. + # The menu item is defined in the engine.rb + menu_item :storages_admin_settings + + # Show the admin page to set storage folder automatic management (for an already existing storage). + # Sets the attributes automatically_managed as default true unless already set to false + # renders a form (allowing the user to change automatically_managed bool and password). + # Used by: The OauthClientsController#create, after the user inputs Oauth credentials for the first time. + # Called by: Global app/config/routes.rb to serve Web page + def new + respond_to do |format| + format.turbo_stream + end + end + + def create + service_result = call_update_service + + service_result.on_success do + respond_to { |format| format.turbo_stream } + end + + service_result.on_failure do + respond_to do |format| + format.turbo_stream { render :edit } + end + end + end + + def edit + respond_to do |format| + format.turbo_stream + end + end + + def default_breadcrumb + ActionController::Base.helpers.link_to(t(:project_module_storages), admin_settings_storages_path) + end + + def show_local_breadcrumb + true + end + + private + + def find_model_object(object_id = :storage_id) + super(object_id) + @storage = @object + end + + def call_update_service + ::Storages::Storages::UpdateService + .new(user: current_user, model: @storage) + .call(permitted_storage_params) + end + + def permitted_storage_params + params + .require(:storages_one_drive_storage) + .permit("automatic_management_enabled") + end +end diff --git a/modules/storages/app/controllers/storages/admin/storages_controller.rb b/modules/storages/app/controllers/storages/admin/storages_controller.rb index 7491bd2c6bd7..f412710a9b9b 100644 --- a/modules/storages/app/controllers/storages/admin/storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/storages_controller.rb @@ -112,10 +112,12 @@ def create end service_result.on_success do - service_result.on_success do - respond_to do |format| - format.turbo_stream - end + if @storage.provider_type_one_drive? + prepare_storage_for_access_management_form + end + + respond_to do |format| + format.turbo_stream end end end @@ -213,6 +215,15 @@ def show_local_breadcrumb private + def prepare_storage_for_access_management_form + return unless @storage.automatic_management_unspecified? + + @storage = ::Storages::Storages::SetProviderFieldsAttributesService + .new(user: current_user, model: @storage, contract_class: EmptyContract) + .call + .result + end + def ensure_valid_provider_type_selected short_provider_type = params[:provider] if short_provider_type.blank? || (@provider_type = ::Storages::Storage::PROVIDER_TYPE_SHORT_NAMES[short_provider_type]).blank? diff --git a/modules/storages/app/forms/storages/admin/access_management_form.rb b/modules/storages/app/forms/storages/admin/access_management_form.rb new file mode 100644 index 000000000000..a2fa07bced92 --- /dev/null +++ b/modules/storages/app/forms/storages/admin/access_management_form.rb @@ -0,0 +1,58 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 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 Storages::Admin + class AccessManagementForm < ApplicationForm + form do |access_management_form| + access_management_form.radio_button_group(name: :access_management) do |radio_buttons| + radio_buttons.radio_button( + name: :automatic_management_enabled, + value: true, + checked: @storage.automatic_management_enabled?, + label: I18n.t("storages.file_storage_view.access_management.automatic_management"), + caption: I18n.t("storages.file_storage_view.access_management.automatic_management_description"), + visually_hide_label: false + ) + + radio_buttons.radio_button( + name: :automatic_management_enabled, + value: false, + checked: !@storage.automatic_management_enabled?, + label: I18n.t("storages.file_storage_view.access_management.manual_management"), + caption: I18n.t("storages.file_storage_view.access_management.manual_management_description"), + visually_hide_label: false + ) + end + end + + def initialize(storage:) + super() + @storage = storage + end + end +end diff --git a/modules/storages/app/models/storages/one_drive_storage.rb b/modules/storages/app/models/storages/one_drive_storage.rb index 5365da2b2914..794aeaa620ee 100644 --- a/modules/storages/app/models/storages/one_drive_storage.rb +++ b/modules/storages/app/models/storages/one_drive_storage.rb @@ -47,6 +47,15 @@ def configuration_checks } end + def automatic_management_new_record? + if provider_fields_changed? + previous_configuration = provider_fields_change.first + previous_configuration.values_at("automatically_managed").compact.empty? + else + automatic_management_unspecified? + end + end + def oauth_configuration Peripherals::OAuthConfigurations::OneDriveConfiguration.new(self) end diff --git a/modules/storages/app/models/storages/storage.rb b/modules/storages/app/models/storages/storage.rb index 8901a99b25cb..87ea4b52f4d6 100644 --- a/modules/storages/app/models/storages/storage.rb +++ b/modules/storages/app/models/storages/storage.rb @@ -41,8 +41,8 @@ module Storages class Storage < ApplicationRecord PROVIDER_TYPES = [ - PROVIDER_TYPE_NEXTCLOUD = 'Storages::NextcloudStorage', - PROVIDER_TYPE_ONE_DRIVE = 'Storages::OneDriveStorage' + PROVIDER_TYPE_NEXTCLOUD = "Storages::NextcloudStorage", + PROVIDER_TYPE_ONE_DRIVE = "Storages::OneDriveStorage" ].freeze PROVIDER_TYPE_SHORT_NAMES = { @@ -54,12 +54,12 @@ class Storage < ApplicationRecord store_attribute :provider_fields, :automatically_managed, :boolean - has_many :file_links, class_name: 'Storages::FileLink' - belongs_to :creator, class_name: 'User' - has_many :project_storages, dependent: :destroy, class_name: 'Storages::ProjectStorage' + has_many :file_links, class_name: "Storages::FileLink" + belongs_to :creator, class_name: "User" + has_many :project_storages, dependent: :destroy, class_name: "Storages::ProjectStorage" has_many :projects, through: :project_storages has_one :oauth_client, as: :integration, dependent: :destroy - has_one :oauth_application, class_name: '::Doorkeeper::Application', as: :integration, dependent: :destroy + has_one :oauth_application, class_name: "::Doorkeeper::Application", as: :integration, dependent: :destroy validates_uniqueness_of :host, allow_nil: true validates_uniqueness_of :name @@ -83,9 +83,9 @@ class Storage < ApplicationRecord scope :automatic_management_enabled, -> { where("provider_fields->>'automatically_managed' = 'true'") } enum health_status: { - pending: 'pending', - healthy: 'healthy', - unhealthy: 'unhealthy' + pending: "pending", + healthy: "healthy", + unhealthy: "unhealthy" }.freeze, _prefix: :health def self.shorten_provider_type(provider_type) @@ -107,7 +107,7 @@ def self.one_drive_without_ee_token?(provider_type) def self.extract_part_from_piped_string(text, index) return if text.nil? - split_reason = text.split('|') + split_reason = text.split("|") if split_reason.length > index split_reason[index].strip end @@ -115,8 +115,8 @@ def self.extract_part_from_piped_string(text, index) def automatically_managed? ActiveSupport::Deprecation.warn( - '`#automatically_managed?` is deprecated. Use `#automatic_management_enabled?` instead. ' \ - 'NOTE: The new method name better reflects the actual behavior of the storage. ' \ + "`#automatically_managed?` is deprecated. Use `#automatic_management_enabled?` instead. " \ + "NOTE: The new method name better reflects the actual behavior of the storage. " \ "It's not the storage that is automatically managed, rather the Project (Storage) Folder is. " \ "A storage only has this feature enabled or disabled." ) @@ -160,6 +160,10 @@ def oauth_configuration raise Errors::SubclassResponsibility end + def automatic_management_new_record? + raise Errors::SubclassResponsibility + end + def provider_fields_defaults raise Errors::SubclassResponsibility end diff --git a/modules/storages/app/views/storages/admin/access_management/create.turbo_stream.erb b/modules/storages/app/views/storages/admin/access_management/create.turbo_stream.erb new file mode 100644 index 000000000000..af2284c56a1f --- /dev/null +++ b/modules/storages/app/views/storages/admin/access_management/create.turbo_stream.erb @@ -0,0 +1,36 @@ +<%#-- copyright +OpenProject is an open source project management software. +Copyright (C) 2012-2024 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. + +++#%> + +<%= turbo_stream.update :access_management_section do %> + <%= render(Storages::Admin::AccessManagementComponent.new(@storage)) %> +<% end %> + +<%= turbo_stream.update :storage_oauth_client_section do %> + <%= render(Storages::Admin::Forms::OAuthClientFormComponent.new(oauth_client: @storage.build_oauth_client, storage: @storage)) %> +<% end %> diff --git a/modules/storages/app/views/storages/admin/access_management/edit.turbo_stream.erb b/modules/storages/app/views/storages/admin/access_management/edit.turbo_stream.erb new file mode 100644 index 000000000000..95959714723f --- /dev/null +++ b/modules/storages/app/views/storages/admin/access_management/edit.turbo_stream.erb @@ -0,0 +1,32 @@ +<%#-- copyright +OpenProject is an open source project management software. +Copyright (C) 2012-2024 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. + +++#%> + +<%= turbo_stream.update :access_management_section do %> + <%= render(Storages::Admin::Forms::AccessManagementFormComponent.new(@storage)) %> +<% end %> diff --git a/modules/storages/app/views/storages/admin/storages/create.turbo_stream.erb b/modules/storages/app/views/storages/admin/storages/create.turbo_stream.erb index f679e4ace0df..96000a151d5b 100644 --- a/modules/storages/app/views/storages/admin/storages/create.turbo_stream.erb +++ b/modules/storages/app/views/storages/admin/storages/create.turbo_stream.erb @@ -48,7 +48,7 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <% if @storage.provider_type_one_drive? %> - <%= turbo_stream.update :storage_oauth_client_section do %> - <%= render(Storages::Admin::Forms::OAuthClientFormComponent.new(oauth_client: @storage.build_oauth_client, storage: @storage)) %> + <%= turbo_stream.update :access_management_section do %> + <%= render(Storages::Admin::Forms::AccessManagementFormComponent.new(@storage)) %> <% end %> <% end %> diff --git a/modules/storages/config/locales/en.yml b/modules/storages/config/locales/en.yml index 5f0a157e4645..e0acc1e502e7 100644 --- a/modules/storages/config/locales/en.yml +++ b/modules/storages/config/locales/en.yml @@ -78,6 +78,15 @@ en: storage_delete_result_3: In case this storage has automatically managed project folders, those and their contained files will be deleted forever. error_invalid_provider_type: Please select a valid storage provider. file_storage_view: + access_management: + automatic_management: Automatically managed access and folders + automatic_management_description: Let OpenProject create folders per project automatically and manage its user access. This is recommended as it ensures that every team member has always the correct access permissions. + description: Select the type of management of user access and folder creation. We recommend to use the Automatically managed access to have a more organised structure and guarantee access to all relevant users. + manual_management: Manually managed access and folders + manual_management_description: Create and manage folders per project manually on your own. You will need to manually ensure relevant users have access. + setup_incomplete: Select the type of management of user access and folder creation. + subtitle: Access management + title: Access and project folders automatically_managed_folders: Automatically managed folders general_information: General information nextcloud_oauth: Nextcloud OAuth @@ -144,6 +153,7 @@ en: label_creator: Creator label_delete_storage: Delete storage label_edit_storage: Edit storage + label_edit_storage_access_management: Edit storage access management label_edit_storage_automatically_managed_folders: Edit storage automatically managed folders label_edit_storage_host: Edit storage host label_existing_manual_folder: Existing folder with manually managed permissions @@ -200,11 +210,6 @@ en: delete: Delete file storage subtitle: Add an external file storage in order to upload, link and manage files in work packages. managed_project_folders: - one_drive_information: |- - To enable automatically managed project folders in OneDrive/SharePoint, additional configuration is needed on - the drive that is used for this file storage. If OpenProject manages the permissions of this drive, it will - most likely be unusable for other use cases in SharePoint. For more information and guidance please refer - to the %{drive_id_link_text}. subtitle: |- Let OpenProject create folders per project automatically. This is recommended as it ensures that every team member always has the correct access permissions. diff --git a/modules/storages/config/routes.rb b/modules/storages/config/routes.rb index ff40cca7728d..1d78e0b82300 100644 --- a/modules/storages/config/routes.rb +++ b/modules/storages/config/routes.rb @@ -41,6 +41,8 @@ resource :automatically_managed_project_folders, controller: "/storages/admin/automatically_managed_project_folders", only: %i[new create edit update] + resource :access_management, controller: "/storages/admin/access_management", only: %i[new create edit] + get :select_provider, on: :collection member do From b0c27c09fea2a687e8ca16004da9008631b8b891 Mon Sep 17 00:00:00 2001 From: Eric Schubert Date: Wed, 3 Apr 2024 15:13:53 +0200 Subject: [PATCH 2/4] [#53718] added and adapted unit tests --- .../access_management_component.html.erb | 5 +- .../app/models/storages/one_drive_storage.rb | 3 +- modules/storages/config/locales/en.yml | 4 +- .../admin/access_management_component_spec.rb | 67 +++++++++++++++++++ .../models/storages/one_drive_storage_spec.rb | 5 +- 5 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 modules/storages/spec/components/storages/admin/access_management_component_spec.rb diff --git a/modules/storages/app/components/storages/admin/access_management_component.html.erb b/modules/storages/app/components/storages/admin/access_management_component.html.erb index 0f5d89913214..fad5d11eebcb 100644 --- a/modules/storages/app/components/storages/admin/access_management_component.html.erb +++ b/modules/storages/app/components/storages/admin/access_management_component.html.erb @@ -1,5 +1,8 @@ <%= - grid_layout('op-storage-view--row', tag: :div, align_items: :center) do |grid| + grid_layout('op-storage-view--row', + tag: :div, + test_selector: 'storage-access-management-section', + align_items: :center) do |grid| grid.with_area(:item, tag: :div, mr: 3) do concat(render(Primer::Beta::Text.new(font_weight: :bold, mr: 1, test_selector: 'access-management-label')) do I18n.t('storages.file_storage_view.access_management.subtitle') diff --git a/modules/storages/app/models/storages/one_drive_storage.rb b/modules/storages/app/models/storages/one_drive_storage.rb index 794aeaa620ee..76d20c4aac3e 100644 --- a/modules/storages/app/models/storages/one_drive_storage.rb +++ b/modules/storages/app/models/storages/one_drive_storage.rb @@ -43,7 +43,8 @@ def configuration_checks { storage_oauth_client_configured: oauth_client.present?, storage_tenant_drive_configured: tenant_id.present? && drive_id.present?, - host_name_configured: name.present? + access_management_configured: !automatic_management_unspecified?, + name_configured: name.present? } end diff --git a/modules/storages/config/locales/en.yml b/modules/storages/config/locales/en.yml index e0acc1e502e7..dfbaa453e649 100644 --- a/modules/storages/config/locales/en.yml +++ b/modules/storages/config/locales/en.yml @@ -79,10 +79,10 @@ en: error_invalid_provider_type: Please select a valid storage provider. file_storage_view: access_management: - automatic_management: Automatically managed access and folders + automatic_management: Automatically managed access and folders. automatic_management_description: Let OpenProject create folders per project automatically and manage its user access. This is recommended as it ensures that every team member has always the correct access permissions. description: Select the type of management of user access and folder creation. We recommend to use the Automatically managed access to have a more organised structure and guarantee access to all relevant users. - manual_management: Manually managed access and folders + manual_management: Manually managed access and folders. manual_management_description: Create and manage folders per project manually on your own. You will need to manually ensure relevant users have access. setup_incomplete: Select the type of management of user access and folder creation. subtitle: Access management diff --git a/modules/storages/spec/components/storages/admin/access_management_component_spec.rb b/modules/storages/spec/components/storages/admin/access_management_component_spec.rb new file mode 100644 index 000000000000..9eb73321400f --- /dev/null +++ b/modules/storages/spec/components/storages/admin/access_management_component_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 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 Storages::Admin::AccessManagementComponent, type: :component do + subject(:access_management_component) { described_class.new(storage) } + + before do + render_inline(access_management_component) + end + + context "on a pristine form" do + let(:storage) { Storages::OneDriveStorage.new } + + it "renders the access management description" do + expect(page).to have_text("Select the type of management of user access and folder creation.") + expect(page).not_to have_test_selector("label-access_management_configured-status") + end + end + + context "on a form with access management set to automatic management enabled" do + let(:storage) { create(:one_drive_storage, :as_automatically_managed) } + + it "renders the access management description" do + expect(page).to have_text("Automatically managed access and folders.") + expect(page).to have_test_selector("label-access_management_configured-status", text: "Completed") + end + end + + context "on a form with access management set to manual management enabled" do + let(:storage) { create(:one_drive_storage, :as_not_automatically_managed) } + + it "renders the access management description" do + expect(page).to have_text("Manually managed access and folders.") + expect(page).to have_test_selector("label-access_management_configured-status", text: "Completed") + end + end +end diff --git a/modules/storages/spec/models/storages/one_drive_storage_spec.rb b/modules/storages/spec/models/storages/one_drive_storage_spec.rb index dfbdd1cf64ba..e8cc7df6b7e6 100644 --- a/modules/storages/spec/models/storages/one_drive_storage_spec.rb +++ b/modules/storages/spec/models/storages/one_drive_storage_spec.rb @@ -43,15 +43,16 @@ describe "#configured?" do context "with a complete configuration" do - let(:storage) { build(:one_drive_storage, oauth_client: build(:oauth_client)) } + let(:storage) { build(:one_drive_storage, :as_not_automatically_managed, oauth_client: build(:oauth_client)) } it "returns true" do expect(storage.configured?).to be(true) aggregate_failures "configuration_checks" do expect(storage.configuration_checks) - .to eq(host_name_configured: true, + .to eq(name_configured: true, storage_oauth_client_configured: true, + access_management_configured: true, storage_tenant_drive_configured: true) end end From a7e90720f619df5d50a3e1a5a53356d0362d7653 Mon Sep 17 00:00:00 2001 From: Eric Schubert Date: Wed, 3 Apr 2024 16:25:43 +0200 Subject: [PATCH 3/4] [#53718] amended feature spec to cover new one drive form --- .../access_management_form_component.html.erb | 4 ++- .../admin/storage_view_information.rb | 2 +- .../storages/admin/storages_controller.rb | 2 +- modules/storages/config/locales/en.yml | 4 +-- .../admin/access_management_component_spec.rb | 4 +-- .../spec/factories/storage_factory.rb | 2 ++ .../storages/admin/create_storage_spec.rb | 27 ++++++++++++++++++- .../storages/admin/edit_storage_spec.rb | 2 +- 8 files changed, 38 insertions(+), 9 deletions(-) diff --git a/modules/storages/app/components/storages/admin/forms/access_management_form_component.html.erb b/modules/storages/app/components/storages/admin/forms/access_management_form_component.html.erb index f8a28e105a90..dbb415d7edb2 100644 --- a/modules/storages/app/components/storages/admin/forms/access_management_form_component.html.erb +++ b/modules/storages/app/components/storages/admin/forms/access_management_form_component.html.erb @@ -11,7 +11,9 @@ end access_management_row.with_row(mb: 3) do - render(Primer::Beta::Text.new) { I18n.t(:'storages.file_storage_view.access_management.description') } + render(Primer::Beta::Text.new(test_selector: 'storage-access-management-description')) do + I18n.t(:'storages.file_storage_view.access_management.description') + end end access_management_row.with_row do diff --git a/modules/storages/app/components/storages/admin/storage_view_information.rb b/modules/storages/app/components/storages/admin/storage_view_information.rb index 880c5a064240..baf91d6b529e 100644 --- a/modules/storages/app/components/storages/admin/storage_view_information.rb +++ b/modules/storages/app/components/storages/admin/storage_view_information.rb @@ -18,7 +18,7 @@ def configuration_check_label if storage.provider_type_nextcloud? configuration_check_label_for(:host_name_configured) elsif storage.provider_type_one_drive? - configuration_check_label_for(:host_name_configured, :storage_tenant_drive_configured) + configuration_check_label_for(:name_configured, :storage_tenant_drive_configured) end end diff --git a/modules/storages/app/controllers/storages/admin/storages_controller.rb b/modules/storages/app/controllers/storages/admin/storages_controller.rb index f412710a9b9b..6b06e92efac2 100644 --- a/modules/storages/app/controllers/storages/admin/storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/storages_controller.rb @@ -85,7 +85,7 @@ def upsale; end def select_provider @object = Storages::Storage.new(provider_type: @provider_type) - service_result = ::Storages::Storages::SetProviderFieldsAttributesService + service_result = ::Storages::Storages::SetAttributesService .new(user: current_user, model: @object, contract_class: EmptyContract) diff --git a/modules/storages/config/locales/en.yml b/modules/storages/config/locales/en.yml index dfbaa453e649..e0acc1e502e7 100644 --- a/modules/storages/config/locales/en.yml +++ b/modules/storages/config/locales/en.yml @@ -79,10 +79,10 @@ en: error_invalid_provider_type: Please select a valid storage provider. file_storage_view: access_management: - automatic_management: Automatically managed access and folders. + automatic_management: Automatically managed access and folders automatic_management_description: Let OpenProject create folders per project automatically and manage its user access. This is recommended as it ensures that every team member has always the correct access permissions. description: Select the type of management of user access and folder creation. We recommend to use the Automatically managed access to have a more organised structure and guarantee access to all relevant users. - manual_management: Manually managed access and folders. + manual_management: Manually managed access and folders manual_management_description: Create and manage folders per project manually on your own. You will need to manually ensure relevant users have access. setup_incomplete: Select the type of management of user access and folder creation. subtitle: Access management diff --git a/modules/storages/spec/components/storages/admin/access_management_component_spec.rb b/modules/storages/spec/components/storages/admin/access_management_component_spec.rb index 9eb73321400f..f890954e6aa8 100644 --- a/modules/storages/spec/components/storages/admin/access_management_component_spec.rb +++ b/modules/storages/spec/components/storages/admin/access_management_component_spec.rb @@ -51,7 +51,7 @@ let(:storage) { create(:one_drive_storage, :as_automatically_managed) } it "renders the access management description" do - expect(page).to have_text("Automatically managed access and folders.") + expect(page).to have_text("Automatically managed access and folders") expect(page).to have_test_selector("label-access_management_configured-status", text: "Completed") end end @@ -60,7 +60,7 @@ let(:storage) { create(:one_drive_storage, :as_not_automatically_managed) } it "renders the access management description" do - expect(page).to have_text("Manually managed access and folders.") + expect(page).to have_text("Manually managed access and folders") expect(page).to have_test_selector("label-access_management_configured-status", text: "Completed") end end diff --git a/modules/storages/spec/factories/storage_factory.rb b/modules/storages/spec/factories/storage_factory.rb index bcce415cdc61..ec7232cb53cc 100644 --- a/modules/storages/spec/factories/storage_factory.rb +++ b/modules/storages/spec/factories/storage_factory.rb @@ -163,6 +163,8 @@ factory :sharepoint_dev_drive_storage, parent: :one_drive_storage do + automatically_managed { false } + transient do oauth_client_token_user { association :user } end diff --git a/modules/storages/spec/features/storages/admin/create_storage_spec.rb b/modules/storages/spec/features/storages/admin/create_storage_spec.rb index 85e7892292f3..9df57620b0ef 100644 --- a/modules/storages/spec/features/storages/admin/create_storage_spec.rb +++ b/modules/storages/spec/features/storages/admin/create_storage_spec.rb @@ -227,6 +227,13 @@ "doing the setup. In the portal, you also need to register an " \ "Azure application or use an existing one for authentication.") + # Access Management + wait_for(page).to have_test_selector("access-management-label", text: "Access management") + expect(page).not_to have_test_selector("label-access_management_configured-status") + expect(page).to have_text("Select the type of management of user access and folder creation.") + expect(page).to have_test_selector("access-management-description", + text: "Select the type of management of user access and folder creation.") + # OAuth client wait_for(page).to have_test_selector("storage-oauth-client-label", text: "Azure OAuth") expect(page).not_to have_test_selector("label-storage_oauth_client_configured-status") @@ -249,11 +256,29 @@ click_on "Save and continue" end - wait_for(page).to have_test_selector("label-host_name_configured-storage_tenant_drive_configured-status", + wait_for(page).to have_test_selector("label-name_configured-storage_tenant_drive_configured-status", text: "Completed") expect(page).to have_test_selector("storage-description", text: "OneDrive/SharePoint - My OneDrive") end + aggregate_failures "Access Management" do + within_test_selector("storage-access-management-form") do + expect(page).to have_test_selector("storage-access-management-description", + text: "Select the type of management of user access and folder " \ + "creation. We recommend to use the Automatically managed access " \ + "to have a more organised structure and guarantee access to all " \ + "relevant users.") + expect(page).to have_checked_field("Automatically managed access and folders") + expect(page).to have_unchecked_field("Manually managed access and folders") + + choose "Manually managed access and folders" + click_on "Save and continue" + end + + wait_for(page).to have_test_selector("label-access_management_configured-status", text: "Completed") + expect(page).to have_test_selector("access-management-description", text: "Manually managed access and folders") + end + aggregate_failures "OAuth Client" do within_test_selector("storage-oauth-client-form") do expect(page).to have_test_selector("storage-provider-credentials-instructions", diff --git a/modules/storages/spec/features/storages/admin/edit_storage_spec.rb b/modules/storages/spec/features/storages/admin/edit_storage_spec.rb index bbe471e671a9..6ebf2d488823 100644 --- a/modules/storages/spec/features/storages/admin/edit_storage_spec.rb +++ b/modules/storages/spec/features/storages/admin/edit_storage_spec.rb @@ -255,7 +255,7 @@ aggregate_failures "Storage edit view" do # General information expect(page).to have_test_selector("storage-provider-label", text: "Storage provider") - expect(page).to have_test_selector("label-host_name_configured-storage_tenant_drive_configured-status", + expect(page).to have_test_selector("label-name_configured-storage_tenant_drive_configured-status", text: "Completed") expect(page).to have_test_selector("storage-description", text: "OneDrive/SharePoint - Test Drive") From 52a495f66a9fd05f66604b2290c52ad742217c56 Mon Sep 17 00:00:00 2001 From: Eric Schubert Date: Fri, 5 Apr 2024 10:45:06 +0200 Subject: [PATCH 4/4] [#53718] enabled update action for access management - only needed for updating the access management while first time storage configuration --- .../access_management_form_component.html.erb | 2 +- .../forms/access_management_form_component.rb | 8 +++++ .../admin/access_management_controller.rb | 16 +++++++++- .../access_management/update.turbo_stream.erb | 32 +++++++++++++++++++ modules/storages/config/routes.rb | 2 +- 5 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 modules/storages/app/views/storages/admin/access_management/update.turbo_stream.erb diff --git a/modules/storages/app/components/storages/admin/forms/access_management_form_component.html.erb b/modules/storages/app/components/storages/admin/forms/access_management_form_component.html.erb index dbb415d7edb2..158ea595bc51 100644 --- a/modules/storages/app/components/storages/admin/forms/access_management_form_component.html.erb +++ b/modules/storages/app/components/storages/admin/forms/access_management_form_component.html.erb @@ -3,7 +3,7 @@ primer_form_with( model:, url: admin_settings_storage_access_management_path(storage), - method: :post + method: form_method ) do |form| flex_layout do |access_management_row| access_management_row.with_row(mb: 3) do diff --git a/modules/storages/app/components/storages/admin/forms/access_management_form_component.rb b/modules/storages/app/components/storages/admin/forms/access_management_form_component.rb index 779b1b4fce8c..1e7cca85a6c4 100644 --- a/modules/storages/app/components/storages/admin/forms/access_management_form_component.rb +++ b/modules/storages/app/components/storages/admin/forms/access_management_form_component.rb @@ -35,8 +35,16 @@ class AccessManagementFormComponent < ApplicationComponent private + def form_method + first_time_configuration? ? :post : :patch + end + def cancel_button_path edit_admin_settings_storage_path(storage) end + + def first_time_configuration? + storage.automatic_management_new_record? + end end end diff --git a/modules/storages/app/controllers/storages/admin/access_management_controller.rb b/modules/storages/app/controllers/storages/admin/access_management_controller.rb index bd2e1201fd11..e887fcee1bb7 100644 --- a/modules/storages/app/controllers/storages/admin/access_management_controller.rb +++ b/modules/storages/app/controllers/storages/admin/access_management_controller.rb @@ -34,7 +34,7 @@ class Storages::Admin::AccessManagementController < ApplicationController before_action :require_admin model_object Storages::OneDriveStorage - before_action :find_model_object, only: %i[new create edit] + before_action :find_model_object, only: %i[new create edit update] # menu_item is defined in the Redmine::MenuManager::MenuController # module, included from ApplicationController. @@ -66,6 +66,20 @@ def create end end + def update + service_result = call_update_service + + service_result.on_success do + respond_to { |format| format.turbo_stream } + end + + service_result.on_failure do + respond_to do |format| + format.turbo_stream { render :edit } + end + end + end + def edit respond_to do |format| format.turbo_stream diff --git a/modules/storages/app/views/storages/admin/access_management/update.turbo_stream.erb b/modules/storages/app/views/storages/admin/access_management/update.turbo_stream.erb new file mode 100644 index 000000000000..557a02a53c12 --- /dev/null +++ b/modules/storages/app/views/storages/admin/access_management/update.turbo_stream.erb @@ -0,0 +1,32 @@ +<%#-- copyright +OpenProject is an open source project management software. +Copyright (C) 2012-2024 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. + +++#%> + +<%= turbo_stream.update :access_management_section do %> + <%= render(Storages::Admin::AccessManagementComponent.new(@storage)) %> +<% end %> diff --git a/modules/storages/config/routes.rb b/modules/storages/config/routes.rb index 1d78e0b82300..0e6cd5119c83 100644 --- a/modules/storages/config/routes.rb +++ b/modules/storages/config/routes.rb @@ -41,7 +41,7 @@ resource :automatically_managed_project_folders, controller: "/storages/admin/automatically_managed_project_folders", only: %i[new create edit update] - resource :access_management, controller: "/storages/admin/access_management", only: %i[new create edit] + resource :access_management, controller: "/storages/admin/access_management", only: %i[new create edit update] get :select_provider, on: :collection