Merge pull request #21349 from opf/fix/admin-pir-permissions

Too many permissions required to fill out wizard
This commit is contained in:
Jens Ulferts
2025-12-05 09:20:04 +01:00
committed by GitHub
3 changed files with 242 additions and 18 deletions
@@ -48,39 +48,50 @@ class Projects::CreationWizardController < ApplicationController
respond_with_turbo_streams
end
def update # rubocop:disable Metrics/AbcSize
def update
service_call = Projects::UpdateService
.new(user: current_user, model: @project)
.new(user: current_user, model: @project, contract_options: { project_attributes_only: true })
.call(permitted_params.project)
if service_call.success?
if last_page?
creation_call = Projects::CreateArtifactWorkPackageService.new(user: current_user, model: @project).call
# even when successful, there can be errors related to the artifact
# upload to Nextcloud that needs to be shown to the user
flash[:error] = creation_call.errors.full_messages # rubocop:disable Rails/ActionControllerFlashBeforeRender
if creation_call.success?
redirect_to project_work_packages_path(@project, @project.project_creation_wizard_artifact_work_package_id),
notice: I18n.t("projects.wizard.success")
else
render :show,
locals: { menu_name: :none },
status: :unprocessable_entity
end
create_work_package_artifact
else
redirect_to project_creation_wizard_path(@project, section: params[:next_section])
end
else
@project = service_call.result
render :show,
locals: { menu_name: :none },
status: :unprocessable_entity
render_wizard_error_step
end
end
private
def render_wizard_error_step
render :show,
locals: { menu_name: :none },
status: :unprocessable_entity
end
def create_work_package_artifact # rubocop:disable Metrics/AbcSize
creation_call = User.execute_as_admin(current_user) do
Projects::CreateArtifactWorkPackageService
.new(user: current_user, model: @project)
.call
end
# even when successful, there can be errors related to the artifact
# upload to Nextcloud that needs to be shown to the user
if creation_call.success?
flash[:error] = creation_call.errors.full_messages if creation_call.errors.any?
redirect_to project_work_packages_path(@project, @project.project_creation_wizard_artifact_work_package_id),
notice: I18n.t("projects.wizard.success")
else
flash.now[:error] = creation_call.errors.full_messages
render_wizard_error_step
end
end
def last_page?
params[:finish]
end
@@ -0,0 +1,213 @@
# 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 "Project creation wizard from a template",
:js,
:with_cuprite,
with_good_job_batches: [CopyProjectJob, SendCopyProjectStatusEmailJob] do
include Components::Autocompleter::NgSelectAutocompleteHelpers
# Role for template access - only copy_projects permission
shared_let(:template_role) do
create(:project_role, permissions: %i[copy_projects])
end
# Role assigned to users in newly created projects - only wizard permissions, NO add_work_packages
shared_let(:new_project_role) do
create(:project_role,
permissions: %i[view_work_packages view_project_attributes edit_project_attributes])
end
# Role for the assignee user - assigned via the user custom field role assignment
# This role gives work_package_assigned permission needed for artifact WP creation
shared_let(:assignee_role) do
create(:project_role, permissions: %i[work_package_assigned view_work_packages])
end
shared_let(:status_new) { create(:status, name: "New") }
shared_let(:status_in_progress) { create(:status, name: "In Progress") }
shared_let(:type) { create(:type, name: "Project initiation") }
shared_let(:default_priority) { create(:default_priority) }
shared_let(:workflow) do
create(:workflow,
type:,
role: assignee_role,
old_status: status_new,
new_status: status_in_progress)
end
shared_let(:section) do
create(:project_custom_field_section, name: "Project Information")
end
shared_let(:string_custom_field) do
create(:string_project_custom_field,
name: "Project Code",
project_custom_field_section: section)
end
shared_let(:user_custom_field) do
create(:user_project_custom_field,
name: "Project Validator",
project_custom_field_section: section,
role_id: assignee_role.id)
end
shared_let(:user_assignee) do
create(:user, firstname: "Assignee", lastname: "User")
end
shared_let(:template) do
create(:template_project,
name: "Wizard Template",
types: [type],
project_creation_wizard_enabled: true,
project_creation_wizard_work_package_type_id: type.id,
project_creation_wizard_status_when_submitted_id: status_new.id,
project_creation_wizard_assignee_custom_field_id: user_custom_field.id)
end
# Enable custom fields for the template with wizard enabled
shared_let(:string_cf_mapping) do
create(:project_custom_field_project_mapping,
project: template,
project_custom_field: string_custom_field,
creation_wizard: true)
end
shared_let(:user_cf_mapping) do
create(:project_custom_field_project_mapping,
project: template,
project_custom_field: user_custom_field,
creation_wizard: true)
end
# User with only copy_projects on template, and add_project globally
# Will get new_project_role in the created project (which lacks add_work_packages)
current_user do
create(:user,
firstname: "Regular",
lastname: "User",
member_with_roles: { template => template_role },
global_permissions: %i[add_project view_all_principals])
end
before do
# Configure the role that new project creators get
allow(Setting)
.to receive(:new_project_user_role_id)
.and_return(new_project_role.id.to_s)
end
it "creates a project from template and completes the wizard, " \
"creating an artifact work package despite lacking add_work_packages permission" do
# Verify user does NOT have add_work_packages permission through their role
expect(new_project_role.permissions).not_to include(:add_work_packages)
# Verify the user custom field has role assignment configured
expect(user_custom_field.role_id).to eq(assignee_role.id)
# Start project creation from template
visit new_project_path(template_id: template.id)
# Step 2: Project details
expect(page).to have_heading "New project"
fill_in "Name", with: "My New Project"
click_on "Complete"
# Background job dialog appears
expect(page).to have_dialog "Background job status"
within_dialog "Background job status" do
expect(page).to have_heading "Applying template"
end
# Run background jobs
GoodJob.perform_inline
# Should redirect to the creation wizard (because project_creation_wizard_enabled is true)
expect(page).to have_current_path(/\/projects\/my-new-project\/creation_wizard/, wait: 20)
# Verify we're on the wizard page
expect(page).to have_css("h3", text: "Project Information")
# Find the created project
project = Project.find_by(identifier: "my-new-project")
expect(project).to be_present
# Verify user is a member with the new_project_role (no add_work_packages)
user_member = project.members.find_by(user_id: current_user.id)
expect(user_member).to be_present
expect(user_member.roles).to include(new_project_role)
expect(current_user).not_to be_allowed_in_project(:add_work_packages, project)
# Fill in the wizard fields
fill_in "Project Code", with: "NEW-001"
# Select the assignee user - this should also assign them the assignee_role
# via the CustomFieldsRole mechanism since user_custom_field has role_id set
select_autocomplete page.find("[data-custom-field-id='#{user_custom_field.id}']"),
results_selector: "body",
query: user_assignee.name
# Complete the wizard - this should create the artifact work package
# via User.execute_as_admin despite lacking add_work_packages permission
click_button "Complete"
expect(page).to have_text("Project attributes saved and artifact work package created successfully.")
# Verify we're redirected to the artifact work package
project.reload
expect(project.project_creation_wizard_artifact_work_package_id).to be_present
expect(page).to have_current_path(
"/projects/#{project.identifier}/work_packages/#{project.project_creation_wizard_artifact_work_package_id}/activity"
)
# Verify the work package was created correctly
artifact_wp = WorkPackage.find(project.project_creation_wizard_artifact_work_package_id)
expect(artifact_wp).to be_present
expect(artifact_wp.type).to eq(type)
expect(artifact_wp.status).to eq(status_new)
expect(artifact_wp.assigned_to).to eq(user_assignee)
# Verify custom field values were saved
expect(project.typed_custom_value_for(string_custom_field)).to eq("NEW-001")
expect(project.typed_custom_value_for(user_custom_field)).to eq(user_assignee)
# Verify the assignee was added as a member via the role assignment from the custom field
assignee_member = project.members.find_by(user_id: user_assignee.id)
expect(assignee_member).to be_present
expect(assignee_member.roles).to include(assignee_role)
end
end