Merge branch 'release/17.0' into dev

This commit is contained in:
Oliver Günther
2026-01-09 11:58:26 +01:00
41 changed files with 426 additions and 58 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
permissions:
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
if: github.repository == 'opf/openproject'
if: github.repository_owner == 'opf'
name: Brakeman Scan
runs-on: ubuntu-latest
env:
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
trigger_downstream_workflow:
permissions:
contents: none
if: github.repository == 'opf/openproject'
if: github.repository_owner == 'opf'
runs-on: ubuntu-latest
steps:
- name: Trigger Flavours workflow
+1 -1
View File
@@ -11,7 +11,7 @@ on:
jobs:
danger:
if: github.repository == 'opf/openproject'
if: github.repository_owner == 'opf'
runs-on: [ubuntu-latest]
timeout-minutes: 10
steps:
+1 -1
View File
@@ -116,7 +116,7 @@ jobs:
docker_tags: ${{ steps.extract_version.outputs.docker_tags }}
registry_image: ${{ steps.extract_version.outputs.registry_image }}
build:
if: github.repository == 'opf/openproject'
if: github.repository_owner == 'opf'
needs:
- setup
runs-on:
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
trigger_saas_tests:
permissions:
contents: none
if: github.repository == 'opf/openproject'
if: github.repository_owner == 'opf'
name: SaaS tests
runs-on: ubuntu-latest
steps:
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
i18n-tasks:
permissions:
contents: read
if: github.repository == 'opf/openproject'
if: github.repository_owner == 'opf'
name: I18n inconsistency check
runs-on: ubuntu-latest
steps:
+1 -1
View File
@@ -15,7 +15,7 @@ on:
jobs:
api-spec:
name: APIv3 specification (OpenAPI 3.0)
if: github.repository == 'opf/openproject'
if: github.repository_owner == 'opf'
runs-on: [ubuntu-latest]
steps:
- uses: actions/checkout@v6
+2 -2
View File
@@ -1,5 +1,5 @@
name: Package
on:
on:
push:
branches:
- packaging/*
@@ -11,7 +11,7 @@ on:
jobs:
build:
if: github.repository == 'opf/openproject'
if: github.repository_owner == 'opf'
name: ${{ matrix.target }}
runs-on: ubuntu-latest
services:
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
pull-requests: write # to remove labels
statuses: write # to create commit status
if: github.repository == 'opf/openproject' && ( github.event_name == 'schedule' || github.event.label.name == 'pullpreview' || contains(github.event.pull_request.labels.*.name, 'pullpreview') )
if: github.repository_owner == 'opf' && ( github.event_name == 'schedule' || github.event.label.name == 'pullpreview' || contains(github.event.pull_request.labels.*.name, 'pullpreview') )
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
+1 -1
View File
@@ -24,7 +24,7 @@ permissions:
jobs:
all:
name: Units + Features
if: github.repository == 'opf/openproject'
if: github.repository_owner == 'opf'
runs-on:
labels: "runs-on=${{ github.run_id }}/image=ubuntu24-full-x64/family=m7+c7+r7+i7+r8/ram=128+256/cpu=32"
timeout-minutes: 40
@@ -59,3 +59,11 @@
@media screen and (max-width: $breakpoint-md)
grid-template-columns: 170px 1fr
@media screen and (max-width: $breakpoint-sm)
grid-template-areas: "main"
grid-template-columns: 1fr
&--sidebar,
&--help
display: none
+2
View File
@@ -91,6 +91,7 @@ projects:
- board_view
- team_planner_view
- meetings
- documents
news:
- t_title: Welcome to your demo project
t_summary: |
@@ -389,6 +390,7 @@ projects:
- work_package_tracking
- gantt
- board_view
- documents
news:
- t_title: Welcome to your Scrum demo project
t_summary: |
+12 -2
View File
@@ -346,7 +346,14 @@ module Settings
allowed: -> { Redmine::I18n.all_languages }
},
default_projects_modules: {
default: %w[calendar board_view work_package_tracking gantt news costs wiki],
default: -> {
base_modules = %w[calendar board_view work_package_tracking gantt news costs wiki]
if Setting.real_time_text_collaboration_enabled?
base_modules + %w[documents]
else
base_modules
end
},
allowed: -> { OpenProject::AccessControl.available_project_modules.map(&:to_s) }
},
default_projects_public: {
@@ -575,7 +582,10 @@ module Settings
},
real_time_text_collaboration_enabled: {
description: "Enable real-time collaborative editing of text fields using BlockNoteJS and Hocuspocus server.",
default: true
default: -> {
Setting.collaborative_editing_hocuspocus_url.present? &&
Setting.collaborative_editing_hocuspocus_secret.present?
}
},
collaborative_editing_hocuspocus_url: {
format: :string,
@@ -145,14 +145,13 @@ Rails.application.config.action_controller.raise_on_open_redirects = true
# https://guides.rubyonrails.org/configuring.html#config-action-dispatch-default-headers
# Change the default headers to disable browsers' flawed legacy XSS protection.
# Rails.application.config.action_dispatch.default_headers = {
# "X-Frame-Options" => "SAMEORIGIN",
# "X-XSS-Protection" => "0",
# "X-Content-Type-Options" => "nosniff",
# "X-Download-Options" => "noopen",
# "X-Permitted-Cross-Domain-Policies" => "none",
# "Referrer-Policy" => "strict-origin-when-cross-origin"
# }
Rails.application.config.action_dispatch.default_headers = {
"X-Frame-Options" => "SAMEORIGIN",
"X-Content-Type-Options" => "nosniff",
"X-Download-Options" => "noopen",
"X-Permitted-Cross-Domain-Policies" => "none",
"Referrer-Policy" => "strict-origin-when-cross-origin"
}
# https://guides.rubyonrails.org/configuring.html#config-active-support-cache-format-version
# ** Please read carefully, this must be configured in config/application.rb **
@@ -0,0 +1,49 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
class AddDocumentsToDefaultProjectsModules < ActiveRecord::Migration[8.0]
def up
return unless Setting.exists?(:real_time_text_collaboration_enabled)
return unless Setting.real_time_text_collaboration_enabled?
# Only update if setting exists in DB (avoid updating on new installations - seeder handles that)
setting = Setting.find_by(name: "default_projects_modules")
return unless setting
current_modules = setting.value || []
return if current_modules.include?("documents")
Setting.default_projects_modules = current_modules + ["documents"]
end
def down
# No-op
end
end
+35 -1
View File
@@ -11,9 +11,43 @@ release_date: 2025-12-02
Release date: 2025-12-02
We released OpenProject [OpenProject 16.6.2](https://community.openproject.org/versions/2243).
The release contains several bug fixes and we recommend updating to the newest version.
The release contains security relevant bug fixes and we strongly urge updating to the newest version.
Below you will find a complete list of all changes and bug fixes.
The reported vulnerabilities have been reported as part of a Pentest by [Mantodea Security GmbH](https://mantodeasecurity.de/).
Thank you for your cooperation and responsible disclosure of the vulnerabilities
### CVE-2026-22601 - Code Execution in E-Mail function
For OpenProject version 16.6.1 and below, a registered administrator can execute arbitrary command by configuring sendmail binary path and sending a test email.
This vulnerability was assigned to the CVE CVE-2026-22601.
For more information, please see the [GitHub Advisory GHSA-9vrv-7h26-c7jc)](https://github.com/opf/openproject/security/advisories/GHSA-9vrv-7h26-c7jc).
### CVE-2026-22602 - User Enumeration via User ID
A lowprivileged logged-in user can view the full names of other users. The full name corresponding to any arbitrary user ID can be retrieved via the following URL, even if the requesting account has only minimal permissions:
This vulnerability was assigned to the CVE CVE-2026-22602.
For more information, please see the [GitHub Advisory GHSA-7fvx-9h6h-g82j](https://github.com/opf/openproject/security/advisories/GHSA-7fvx-9h6h-g82j).
### CVE-2026-22603 - No protection against brute-force attacks in the Change Password function
OpenProjects unauthenticated password-change endpoint (/account/change_password) was not protected by the same brute-force safeguards that apply to the normal login form.
In affected versions, an attacker who can guess or enumerate user IDs can send unlimited password-change requests for a given account without triggering lockout or other rate-limiting controls.
This vulnerability was assigned to the CVE CVE-2026-22603.
For more information, please see the [GitHub Advisory GHSA-93x5-prx9-x239](https://github.com/opf/openproject/security/advisories/GHSA-93x5-prx9-x239).
### CVE-2026-22604 - User enumeration via the change password function
When sending a POST request to the /account/change_password endpoint with an arbitrary User ID as the password_change_user_id parameter, the resulting error page would show the username for the requested user. Since this endpoint is intended to be called without being authenticated, this allows to enumerate the user names of all accounts registered in an OpenProject instance.
This vulnerability was assigned to the CVE CVE-2026-22604.
For more information, please see the [GitHub Advisory GHSA-q7qp-p3vw-j2fh](https://github.com/opf/openproject/security/advisories/GHSA-q7qp-p3vw-j2fh).
<!--more-->
## Bug fixes and changes
+10 -1
View File
@@ -11,9 +11,18 @@ release_date: 2025-12-11
Release date: 2025-12-11
We released OpenProject [OpenProject 16.6.3](https://community.openproject.org/versions/2247).
The release contains several bug fixes and we recommend updating to the newest version.
The release contains security relevant bug fixes and we strongly urge updating to the newest version.
Below you will find a complete list of all changes and bug fixes.
### CVE-2026-22605 - Insecure Direct Object Reference in Meetings
OpenProject versions <= 16.6.2 allows users with the View Meetings permission on any project, to access meeting agenda and section titles, notes, and text outcomes of meetings that belonged to projects, the user does not have access to. Linked work packages to projects the user is not allowed to see, are not affected.
This vulnerability was assigned to the CVE CVE-2026-22605.
For more information, please see the [GitHub Advisory GHSA-fq4m-pxvm-8x2j](https://github.com/opf/openproject/security/advisories/GHSA-fq4m-pxvm-8x2j).
This vulnerability was reported as part of the [YesWeHack.com OpenProject Bug Bounty program](https://yeswehack.com/programs/openproject), sponsored by the European Commission.
<!--more-->
## Bug fixes and changes
+37
View File
@@ -0,0 +1,37 @@
---
title: OpenProject 16.6.4
sidebar_navigation:
title: 16.6.4
release_version: 16.6.4
release_date: 2026-01-08
---
# OpenProject 16.6.4
Release date: 2026-01-08
We released OpenProject [OpenProject 16.6.4](https://community.openproject.org/versions/2248).
The release contains security relevant bug fixes and we strongly urge updating to the newest version.
Below you will find a complete list of all changes and bug fixes.
### CVE-2026-22600 - Arbitrary File Read via ImageMagick SVG Coder
A Local File Read (LFR) vulnerability exists in the work package PDF export functionality of OpenProject < 16.6.4 . By uploading a specially crafted SVG file (disguised as a PNG) as a work package attachment, an attacker can exploit the backend image processing engine (ImageMagick). When the work package is exported to PDF, the backend attempts to resize the image, triggering the ImageMagick text: coder. This allows an attacker to read arbitrary local files that the application user has permissions to access (e.g., /etc/passwd, all project configuration files, private project data, etc.)
This vulnerability was assigned to the CVE CVE-2026-22605.
For more information, please see the [GitHub Advisory GHSA-m8f2-cwpq-vvhh)](https://github.com/opf/openproject/security/advisories/GHSA-m8f2-cwpq-vvhh).
The vulnerability has been responsibly disclosed through the [YesWeHack bounty program for OpenProject](https://yeswehack.com/programs/openproject) by user [syndrome_imposter](https://yeswehack.com/hunters/syndrome-impostor). This bug bounty program is being sponsored by the European Commission.
<!--more-->
## Bug fixes and changes
<!-- Warning: Anything within the below lines will be automatically removed by the release script -->
<!-- BEGIN AUTOMATED SECTION -->
- Bugfix: SVG attachments are interpreted as PNG \[[#70349](https://community.openproject.org/wp/70349)\]
<!-- END AUTOMATED SECTION -->
<!-- Warning: Anything above this line will be automatically removed by the release script -->
+7
View File
@@ -13,6 +13,13 @@ Stay up to date and get an overview of the new features included in the releases
<!--- New release notes are generated below. Do not remove comment. -->
<!--- RELEASE MARKER -->
## 16.6.4
Release date: 2026-01-08
[Release Notes](16-6-4/)
## 16.6.3
Release date: 2025-12-11
@@ -39,6 +39,7 @@ $widget-box--enumeration-width: 20px
.widget-box
flex: 1
flex-basis: 32%
max-height: 750px // Avoid that individual widgets blow the whole grid
display: flex
flex-direction: column
+3
View File
@@ -92,6 +92,9 @@ module API
content_type attachment_content_type(attachment)
header["Content-Disposition"] = attachment.content_disposition
# Ensure we set nosniff on attachments served from our app
# so that browsers do not reinterpret the content
header["X-Content-Type-Options"] = "nosniff"
env["api.format"] = :binary
sendfile attachment.diskfile.path
end
+2
View File
@@ -247,6 +247,7 @@ projects:
- wiki
- board_view
- team_planner_view
- documents
news:
- t_title: Welcome to your demo project
t_summary: |
@@ -395,6 +396,7 @@ projects:
- wiki
- board_view
- team_planner_view
- documents
news:
- t_title: Welcome to your demo project
t_summary: |
@@ -53,7 +53,7 @@ RSpec.describe RootSeeder,
it "creates the BIM demo data" do
expect(Project.count).to eq 4
expect(EnabledModule.count).to eq 27
expect(EnabledModule.count).to eq 29
expect(WorkPackage.count).to eq 76
expect(Wiki.count).to eq 3
expect(Query.count).to eq 29
@@ -42,11 +42,11 @@
concat(
flex_layout do |flex_container|
flex_container.with_column do
render Primer::Beta::AvatarStack.new(
render Primer::OpenProject::AvatarStack.new(
data: { "documents--live-events-target": "users" }
) do |component|
users.map do |user|
component.with_avatar(src: avatar_url(user), alt: user.name)
component.with_avatar_with_fallback(**avatar_options_for(user))
end
end
end
@@ -67,14 +67,12 @@
component.with_body(caret: :top_left) do
flex_layout do |container|
users.each do |user|
container.with_row do
render Users::AvatarComponent.new(
user: user,
show_name: true,
link: false,
hover_card: { active: false },
size: :mini
)
container.with_column do
render Primer::OpenProject::AvatarWithFallback.new(**avatar_options_for(user))
end
container.with_column(ml: 2) do
render(Primer::Beta::Text.new(color: :subtle)) { user.name }
end
end
end
@@ -49,6 +49,14 @@ module Documents
def active_editors
I18n.t("documents.active_editors_count", count: users.count).html_safe
end
def avatar_options_for(user)
{
src: avatar_url(user),
alt: user.name,
unique_id: user.id
}
end
end
end
end
@@ -187,7 +187,10 @@ RSpec.describe DocumentsController do
end
describe "generate_oauth_token",
with_config: { collaborative_editing_hocuspocus_secret: "secret1234" } do
with_config: {
collaborative_editing_hocuspocus_url: "wss://hocuspocus.local",
collaborative_editing_hocuspocus_secret: "secret1234"
} do
let(:manage_role) { create(:project_role, permissions: %i[view_documents manage_documents]) }
let(:view_only_role) { create(:project_role, permissions: [:view_documents]) }
let(:user_with_manage) { create(:user) }
@@ -214,7 +214,7 @@ RSpec.describe "Upload attachment to documents",
end
end
context "for collaborative documents" do
context "for collaborative documents", with_settings: { real_time_text_collaboration_enabled: true } do
let(:document) { create(:document, project:) }
let(:editor) { FormFields::Primerized::BlockNoteEditorInput.new }
let(:attachments_list) { Components::AttachmentsList.new }
@@ -30,7 +30,7 @@
require "rails_helper"
RSpec.describe "BlockNote editor rendering", :js do
RSpec.describe "BlockNote editor rendering", :js, with_settings: { real_time_text_collaboration_enabled: true } do
let(:admin) { create(:admin) }
let(:type) { create(:document_type, :experimental) }
let(:document) { create(:document, type:) }
@@ -30,7 +30,9 @@
require "spec_helper"
RSpec.describe "Document collaboration settings admin", :js, :settings_reset do
RSpec.describe "Document collaboration settings admin",
:js,
:settings_reset do
include Flash::Expectations
current_user { create(:admin) }
@@ -39,6 +41,16 @@ RSpec.describe "Document collaboration settings admin", :js, :settings_reset do
it "can configure hocuspocus url and secret" do
visit admin_settings_document_collaboration_settings_path
within_test_selector("collaboration-settings-disabled-notice") do
expect(page).to have_heading("Real-time collaboration is not enabled")
expect(page).to have_content("Once enabled, multiple users will be able to work together on a " \
"document at the same time. All new documents will be based on a new " \
"editor (BlockNote) and will require a working connection to a Hocuspocus server.")
click_on "Enable real-time collaboration"
end
expect_and_dismiss_flash(message: "Real-time collaboration has been enabled.")
expect(page).to have_field("Hocuspocus server URL", with: "")
expect(page).to have_field("Client secret", with: "")
@@ -72,22 +84,12 @@ RSpec.describe "Document collaboration settings admin", :js, :settings_reset do
end
expect_and_dismiss_flash(message: "Real-time collaboration has been disabled.")
within_test_selector("collaboration-settings-disabled-notice") do
expect(page).to have_heading("Real-time collaboration is not enabled")
expect(page).to have_content("Once enabled, multiple users will be able to work together on a " \
"document at the same time. All new documents will be based on a new " \
"editor (BlockNote) and will require a working connection to a Hocuspocus server.")
click_on "Enable real-time collaboration"
end
expect_and_dismiss_flash(message: "Real-time collaboration has been enabled.")
expect(Setting.real_time_text_collaboration_enabled?).to be(true)
end
end
context "with hocuspocus url set via environment variable",
with_env: { "OPENPROJECT_COLLABORATIVE_EDITING_HOCUSPOCUS_URL" => "wss://env-hocuspocus.example.com" } do
with_env: { "OPENPROJECT_COLLABORATIVE_EDITING_HOCUSPOCUS_URL" => "wss://env-hocuspocus.example.com" },
with_settings: { collaborative_editing_hocuspocus_secret: "secret1234" } do
before do
reset(:collaborative_editing_hocuspocus_url)
visit admin_settings_document_collaboration_settings_path
@@ -99,11 +101,16 @@ RSpec.describe "Document collaboration settings admin", :js, :settings_reset do
expect(page).to have_field("Hocuspocus server URL",
with: "wss://env-hocuspocus.example.com",
disabled: true)
expect(page).to have_field("Client secret",
with: "",
disabled: false)
end
end
context "with secret set via environment variable",
with_env: { "OPENPROJECT_COLLABORATIVE_EDITING_HOCUSPOCUS_SECRET" => "envsupersecret" } do
with_env: { "OPENPROJECT_COLLABORATIVE_EDITING_HOCUSPOCUS_SECRET" => "envsupersecret" },
with_settings: { collaborative_editing_hocuspocus_url: "wss://env-hocuspocus.example.com" } do
before do
reset(:collaborative_editing_hocuspocus_secret)
visit admin_settings_document_collaboration_settings_path
@@ -112,6 +119,9 @@ RSpec.describe "Document collaboration settings admin", :js, :settings_reset do
it "shows the secret as read-only" do
expect(page).to have_content("Some values are configured via environment variables and cannot be edited here.")
expect(page).to have_field("Hocuspocus server URL",
with: "wss://env-hocuspocus.example.com",
disabled: false)
expect(page).to have_field("Client secret",
with: "",
disabled: true)
@@ -47,7 +47,7 @@ RSpec.describe "Create Document",
current_user { manager }
context "for collaborative documents" do
context "for collaborative documents", with_settings: { real_time_text_collaboration_enabled: true } do
it "creates a new document via +Document buttons" do
index_page.visit!
@@ -52,7 +52,8 @@ RSpec.describe "Show/Edit Document View",
# rubocop:enable RSpec/AnyInstance
end
it "renders a collaborative document" do
it "renders a collaborative document",
with_settings: { real_time_text_collaboration_enabled: true } do
visit document_path(document)
expect(page).to have_content("Collaborative document")
@@ -460,7 +460,8 @@ class MeetingsController < ApplicationController
@meeting = scope
.visible
.includes([:project, :author, { participants: :user }, :sections, { agenda_items: :outcomes }])
.includes([:project, :author, { participants: :user }, { agenda_items: :outcomes }])
.preload(:sections)
.find(params[:id])
end
@@ -513,5 +513,28 @@ RSpec.describe "Meetings CRUD",
show_page.expect_blankslate
end
end
it "maintains section order when rendering" do
section1 = create(:meeting_section, meeting:, title: "Section A")
section2 = create(:meeting_section, meeting:, title: "Section B")
section3 = create(:meeting_section, meeting:, title: "Section C")
show_page.visit!
expect(show_page.section_headers)
.to eq(["Section A", "Section B", "Section C"])
show_page.select_section_action(section3, "Move up")
wait_for { [section1, section2, section3].map { |s| s.reload.position } }
.to eq([1, 3, 2])
wait_for { show_page.section_headers }
.to eq(["Section A", "Section C", "Section B"])
show_page.reload!
expect(show_page.section_headers)
.to eq(["Section A", "Section C", "Section B"])
end
end
end
@@ -780,5 +780,10 @@ module Pages::Meetings
element["data-reference-value"] != old_reference_value
end
end
def section_headers
page.all(".op-meeting-section-container[data-test-selector^='meeting-section-header-container-']")
.map(&:text)
end
end
end
+2 -2
View File
@@ -7,8 +7,8 @@ name: OpenProject
applicationSuite: openDesk
url: 'https://github.com/opf/openproject'
roadmap: 'https://www.openproject.org/roadmap'
releaseDate: '2025-12-11'
softwareVersion: '16.6.3'
releaseDate: '2026-01-08'
softwareVersion: '16.6.4'
developmentStatus: stable
softwareType: standalone/web
logo: 'publiccode_logo.svg'
+1 -1
View File
@@ -38,7 +38,7 @@ RSpec.describe Settings::Definition, :settings_reset do
described_class.add_all
expect(described_class.all.keys).to eq(described_class::DEFINITIONS.keys)
expect(described_class.all.keys).to match_array(described_class::DEFINITIONS.keys)
end
it "does not add any plugin/feature settings if they were removed for some reason" do
@@ -0,0 +1,120 @@
# 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 Rails.root.join("db/migrate/20260106151226_add_documents_to_default_projects_modules")
RSpec.describe AddDocumentsToDefaultProjectsModules, type: :model do
let(:base_modules) { %w[calendar board_view work_package_tracking gantt news costs wiki] }
before do
# Ensure a clean state
Setting.find_by(name: "default_projects_modules")&.destroy
Setting.clear_cache
end
context "when real_time_text_collaboration is enabled",
with_settings: { real_time_text_collaboration_enabled: true } do
context "when default_projects_modules setting exists in DB" do
before do
Setting.default_projects_modules = base_modules
end
it "adds documents to the default modules" do
ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) }
Setting.clear_cache
expect(Setting.default_projects_modules).to include("documents")
end
it "preserves existing modules" do
ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) }
Setting.clear_cache
expect(Setting.default_projects_modules).to include(*base_modules)
end
context "when documents is already in the default modules" do
before do
Setting.default_projects_modules = base_modules + ["documents"]
end
it "does not duplicate documents" do
ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) }
Setting.clear_cache
expect(Setting.default_projects_modules.count("documents")).to eq(1)
end
end
end
context "when default_projects_modules setting does not exist in DB" do
it "does not create the setting (seeder handles new installations)" do
expect(Setting.find_by(name: "default_projects_modules")).to be_nil
ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) }
# Setting should still not exist - seeder will handle it
expect(Setting.find_by(name: "default_projects_modules")).to be_nil
end
end
end
context "when real_time_text_collaboration is disabled",
with_settings: { real_time_text_collaboration_enabled: false } do
before do
Setting.default_projects_modules = base_modules
end
it "does not modify the default modules" do
ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) }
Setting.clear_cache
expect(Setting.default_projects_modules).not_to include("documents")
expect(Setting.default_projects_modules).to match_array(base_modules)
end
end
context "when real_time_text_collaboration_enabled setting does not exist" do
before do
Setting.default_projects_modules = base_modules
allow(Setting).to receive(:exists?).and_call_original
allow(Setting).to receive(:exists?).with(:real_time_text_collaboration_enabled).and_return(false)
end
it "does not modify the default modules" do
ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) }
Setting.clear_cache
expect(Setting.default_projects_modules).not_to include("documents")
expect(Setting.default_projects_modules).to match_array(base_modules)
end
end
end
+27
View File
@@ -603,4 +603,31 @@ RSpec.describe Setting do
end
end
end
describe "default_projects_modules conditional default" do
shared_examples "base modules unchanged" do
it "includes the base modules" do
base_modules = %w[calendar board_view work_package_tracking gantt news costs wiki]
expect(Settings::Definition[:default_projects_modules].default).to include(*base_modules)
end
end
context "when real_time_text_collaboration is enabled",
with_settings: { real_time_text_collaboration_enabled: true } do
it "includes documents in the default modules" do
expect(Settings::Definition[:default_projects_modules].default).to include("documents")
end
it_behaves_like "base modules unchanged"
end
context "when real_time_text_collaboration is disabled",
with_settings: { real_time_text_collaboration_enabled: false } do
it "does not include documents in the default modules" do
expect(Settings::Definition[:default_projects_modules].default).not_to include("documents")
end
it_behaves_like "base modules unchanged"
end
end
end
@@ -469,6 +469,10 @@ RSpec.shared_examples "an APIv3 attachment resource", content_type: :json, type:
expect(expires_time > Time.now.utc + max_age - 60).to be_truthy
end
it "includes X-Content-Type-Options nosniff header to prevent content type sniffing" do
expect(subject.headers["X-Content-Type-Options"]).to eq "nosniff"
end
it "sends the file in binary" do
expect(subject.body)
.to match(mock_file.read)
@@ -60,5 +60,12 @@ RSpec.describe "" do
end
end
end
it "includes X-Content-Type-Options nosniff header to prevent content type sniffing" do
get "/"
expect(last_response).to have_http_status(200)
expect(last_response.headers["X-Content-Type-Options"]).to eq "nosniff"
end
end
end
@@ -53,7 +53,7 @@ RSpec.describe RootSeeder,
it "creates the demo data" do # rubocop:disable RSpec/MultipleExpectations
expect(Project.count).to eq 2
expect(EnabledModule.count).to eq 13
expect(EnabledModule.count).to eq 15
expect(WorkPackage.count).to eq 36
expect(Wiki.count).to eq 2
expect(Query.having_views.count).to eq 8