mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Merge branch 'release/17.0' into dev
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
@@ -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 low‑privileged 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
|
||||
|
||||
OpenProject’s 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 -->
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+8
-10
@@ -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
|
||||
|
||||
+8
@@ -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:) }
|
||||
|
||||
+24
-14
@@ -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
@@ -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'
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user