diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index b2eac6ca41e7..364045d10d63 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -5,3 +5,5 @@
21a696ef9b170e14ad2daf53364a4c2113822c2f
# Update copyright information for 2023
c795874f7f281297bbd3bad2fdb58b24cb4ce624
+# switch to double quotes in most changed files with a lot of quotes
+5c72ea0046a6b5230bf456f55a296ed6fd579535
diff --git a/Gemfile b/Gemfile
index 45910ccfdb70..f7059356a44d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -26,361 +26,361 @@
# See COPYRIGHT and LICENSE files for more details.
#++
-source 'https://rubygems.org'
+source "https://rubygems.org"
# TODO: Once packager.io and heroku buildpacks support bundler 2.4.22,
# then we can use the new bundler syntax `ruby file: '.ruby-version'`.
# https://github.com/heroku/heroku-buildpack-ruby/issues/1408#issuecomment-1841596215
-ruby File.read('.ruby-version').strip
+ruby File.read(".ruby-version").strip
-gem 'actionpack-xml_parser', '~> 2.0.0'
-gem 'activemodel-serializers-xml', '~> 1.0.1'
-gem 'activerecord-import', '~> 1.5.0'
-gem 'activerecord-session_store', '~> 2.1.0'
-gem 'ox'
-gem 'rails', '~> 7.1.3'
-gem 'responders', '~> 3.0'
+gem "actionpack-xml_parser", "~> 2.0.0"
+gem "activemodel-serializers-xml", "~> 1.0.1"
+gem "activerecord-import", "~> 1.5.0"
+gem "activerecord-session_store", "~> 2.1.0"
+gem "ox"
+gem "rails", "~> 7.1.3"
+gem "responders", "~> 3.0"
-gem 'ffi', '~> 1.15'
+gem "ffi", "~> 1.15"
-gem 'rdoc', '>= 2.4.2'
+gem "rdoc", ">= 2.4.2"
-gem 'doorkeeper', '~> 5.6.6'
+gem "doorkeeper", "~> 5.6.6"
# Maintain our own omniauth due to relative URL root issues
# see upstream PR: https://github.com/omniauth/omniauth/pull/903
-gem 'omniauth', git: 'https://github.com/opf/omniauth', ref: 'fe862f986b2e846e291784d2caa3d90a658c67f0'
-gem 'request_store', '~> 1.6.0'
+gem "omniauth", git: "https://github.com/opf/omniauth", ref: "fe862f986b2e846e291784d2caa3d90a658c67f0"
+gem "request_store", "~> 1.6.0"
-gem 'warden', '~> 1.2'
-gem 'warden-basic_auth', '~> 0.2.1'
+gem "warden", "~> 1.2"
+gem "warden-basic_auth", "~> 0.2.1"
-gem 'will_paginate', '~> 4.0.0'
+gem "will_paginate", "~> 4.0.0"
-gem 'friendly_id', '~> 5.5.0'
+gem "friendly_id", "~> 5.5.0"
-gem 'acts_as_list', '~> 1.1.0'
-gem 'acts_as_tree', '~> 2.9.0'
-gem 'awesome_nested_set', '~> 3.6.0'
-gem 'closure_tree', '~> 7.4.0'
-gem 'rubytree', '~> 2.0.0'
+gem "acts_as_list", "~> 1.1.0"
+gem "acts_as_tree", "~> 2.9.0"
+gem "awesome_nested_set", "~> 3.6.0"
+gem "closure_tree", "~> 7.4.0"
+gem "rubytree", "~> 2.0.0"
# Only used in down migrations now.
# Is to be removed once the referencing migrations have been squashed.
-gem 'typed_dag', '~> 2.0.2', require: false
+gem "typed_dag", "~> 2.0.2", require: false
-gem 'addressable', '~> 2.8.0'
+gem "addressable", "~> 2.8.0"
# Remove whitespace from model input
-gem 'auto_strip_attributes', '~> 2.5'
+gem "auto_strip_attributes", "~> 2.5"
# Provide timezone info for TZInfo used by AR
-gem 'tzinfo-data', '~> 1.2024.1'
+gem "tzinfo-data", "~> 1.2024.1"
# to generate html-diffs (e.g. for wiki comparison)
-gem 'htmldiff'
+gem "htmldiff"
# Generate url slugs with #to_url and other string niceties
-gem 'stringex', '~> 2.8.5'
+gem "stringex", "~> 2.8.5"
# CommonMark markdown parser with GFM extension
-gem 'commonmarker', '~> 1.0.3'
+gem "commonmarker", "~> 1.0.3"
# HTML pipeline for transformations on text formatter output
# such as sanitization or additional features
-gem 'html-pipeline', '~> 2.14.0'
+gem "html-pipeline", "~> 2.14.0"
# Tasklist parsing and renderer
-gem 'deckar01-task_list', '~> 2.3.1'
+gem "deckar01-task_list", "~> 2.3.1"
# Requires escape-utils for faster escaping
-gem 'escape_utils', '~> 1.3'
+gem "escape_utils", "~> 1.3"
# Syntax highlighting used in html-pipeline with rouge
-gem 'rouge', '~> 4.2.0'
+gem "rouge", "~> 4.2.0"
# HTML sanitization used for html-pipeline
-gem 'sanitize', '~> 6.1.0'
+gem "sanitize", "~> 6.1.0"
# HTML autolinking for mails and urls (replaces autolink)
-gem 'rinku', '~> 2.0.4', require: %w[rinku rails_rinku]
+gem "rinku", "~> 2.0.4", require: %w[rinku rails_rinku]
# Version parsing with semver
-gem 'semantic', '~> 1.6.1'
+gem "semantic", "~> 1.6.1"
# generates SVG Graphs
# used for statistics on svn repositories
-gem 'svg-graph', '~> 2.2.0'
+gem "svg-graph", "~> 2.2.0"
-gem 'date_validator', '~> 0.12.0'
-gem 'email_validator', '~> 2.2.3'
-gem 'json_schemer', '~> 2.2.0'
-gem 'ruby-duration', '~> 3.2.0'
+gem "date_validator", "~> 0.12.0"
+gem "email_validator", "~> 2.2.3"
+gem "json_schemer", "~> 2.2.0"
+gem "ruby-duration", "~> 3.2.0"
# `config/initializers/mail_starttls_patch.rb` has also been patched to
# fix STARTTLS handling until https://github.com/mikel/mail/pull/1536 is
# released.
-gem 'mail', '= 2.8.1'
+gem "mail", "= 2.8.1"
# provide compatible filesystem information for available storage
-gem 'sys-filesystem', '~> 1.4.0', require: false
+gem "sys-filesystem", "~> 1.4.0", require: false
-gem 'bcrypt', '~> 3.1.6'
+gem "bcrypt", "~> 3.1.6"
-gem 'multi_json', '~> 1.15.0'
-gem 'oj', '~> 3.16.0'
+gem "multi_json", "~> 1.15.0"
+gem "oj", "~> 3.16.0"
-gem 'daemons'
-gem 'good_job', '~> 3.26.1' # update should be done manually in sync with saas-openproject version.
+gem "daemons"
+gem "good_job", "~> 3.26.1" # update should be done manually in sync with saas-openproject version.
-gem 'rack-protection', '~> 3.2.0'
+gem "rack-protection", "~> 3.2.0"
# Rack::Attack is a rack middleware to protect your web app from bad clients.
# It allows whitelisting, blacklisting, throttling, and tracking based
# on arbitrary properties of the request.
# https://github.com/kickstarter/rack-attack
-gem 'rack-attack', '~> 6.7.0'
+gem "rack-attack", "~> 6.7.0"
# CSP headers
-gem 'secure_headers', '~> 6.5.0'
+gem "secure_headers", "~> 6.5.0"
# Browser detection for incompatibility checks
-gem 'browser', '~> 5.3.0'
+gem "browser", "~> 5.3.0"
# Providing health checks
-gem 'okcomputer', '~> 1.18.1'
+gem "okcomputer", "~> 1.18.1"
-gem 'gon', '~> 6.4.0'
+gem "gon", "~> 6.4.0"
# Lograge to provide sane and non-verbose logging
-gem 'lograge', '~> 0.14.0'
+gem "lograge", "~> 0.14.0"
# Structured warnings to selectively disable them in production
-gem 'structured_warnings', '~> 0.4.0'
+gem "structured_warnings", "~> 0.4.0"
# catch exceptions and send them to any airbrake compatible backend
# don't require by default, instead load on-demand when actually configured
-gem 'airbrake', '~> 13.0.0', require: false
+gem "airbrake", "~> 13.0.0", require: false
-gem 'md_to_pdf', git: 'https://github.com/opf/md-to-pdf', ref: '8f14736a88ad0064d2a97be108fe7061ffbcee91'
-gem 'prawn', '~> 2.4'
+gem "md_to_pdf", git: "https://github.com/opf/md-to-pdf", ref: "8f14736a88ad0064d2a97be108fe7061ffbcee91"
+gem "prawn", "~> 2.4"
# prawn implicitly depends on matrix gem no longer in ruby core with 3.1
-gem 'matrix', '~> 0.4.2'
+gem "matrix", "~> 0.4.2"
-gem 'meta-tags', '~> 2.20.0'
+gem "meta-tags", "~> 2.20.0"
-gem 'paper_trail', '~> 15.1.0'
+gem "paper_trail", "~> 15.1.0"
-gem 'clamav-client', github: 'honestica/clamav-client', ref: '29e78ae94307cb34e79ddd29c5da79752239d8b7'
+gem "clamav-client", github: "honestica/clamav-client", ref: "29e78ae94307cb34e79ddd29c5da79752239d8b7"
group :production do
# we use dalli as standard memcache client
# requires memcached 1.4+
- gem 'dalli', '~> 3.2.0'
- gem 'redis', '~> 5.1.0'
+ gem "dalli", "~> 3.2.0"
+ gem "redis", "~> 5.1.0"
end
-gem 'i18n-js', '~> 4.2.3'
-gem 'rails-i18n', '~> 7.0.0'
+gem "i18n-js", "~> 4.2.3"
+gem "rails-i18n", "~> 7.0.0"
-gem 'sprockets', '~> 3.7.2' # lock sprockets below 4.0
-gem 'sprockets-rails', '~> 3.4.2'
+gem "sprockets", "~> 3.7.2" # lock sprockets below 4.0
+gem "sprockets-rails", "~> 3.4.2"
-gem 'puma', '~> 6.4'
-gem 'puma-plugin-statsd', '~> 2.0'
-gem 'rack-timeout', '~> 0.6.3', require: 'rack/timeout/base'
+gem "puma", "~> 6.4"
+gem "puma-plugin-statsd", "~> 2.0"
+gem "rack-timeout", "~> 0.6.3", require: "rack/timeout/base"
-gem 'nokogiri', '~> 1.16.0'
+gem "nokogiri", "~> 1.16.0"
-gem 'carrierwave', '~> 1.3.1'
-gem 'carrierwave_direct', '~> 2.1.0'
-gem 'fog-aws'
+gem "carrierwave", "~> 1.3.1"
+gem "carrierwave_direct", "~> 2.1.0"
+gem "fog-aws"
-gem 'aws-sdk-core', '~> 3.107'
+gem "aws-sdk-core", "~> 3.107"
# File upload via fog + screenshots on travis
-gem 'aws-sdk-s3', '~> 1.91'
+gem "aws-sdk-s3", "~> 1.91"
-gem 'openproject-token', '~> 4.0'
+gem "openproject-token", "~> 4.0"
-gem 'plaintext', '~> 0.3.2'
+gem "plaintext", "~> 0.3.2"
-gem 'ruby-progressbar', '~> 1.13.0', require: false
+gem "ruby-progressbar", "~> 1.13.0", require: false
-gem 'mini_magick', '~> 4.12.0', require: false
+gem "mini_magick", "~> 4.12.0", require: false
-gem 'validate_url'
+gem "validate_url"
# Storages support code
-gem 'dry-container'
+gem "dry-container"
# ActiveRecord extension which adds typecasting to store accessors
-gem 'store_attribute', '~> 1.0'
+gem "store_attribute", "~> 1.0"
# Appsignal integration
-gem 'appsignal', '~> 3.0', require: false
+gem "appsignal", "~> 3.0", require: false
-gem 'view_component'
+gem "view_component"
# Lookbook
-gem 'lookbook', '~> 2.2.1'
+gem "lookbook", "~> 2.2.1"
# Require factory_bot for usage with openproject plugins testing
-gem 'factory_bot', '~> 6.4.0', require: false
+gem "factory_bot", "~> 6.4.0", require: false
# require factory_bot_rails for convenience in core development
-gem 'factory_bot_rails', '~> 6.4.0', require: false
+gem "factory_bot_rails", "~> 6.4.0", require: false
-gem 'turbo-rails', '~> 2.0.0'
+gem "turbo-rails", "~> 2.0.0"
-gem 'httpx'
+gem "httpx"
group :test do
- gem 'launchy', '~> 3.0.0'
- gem 'rack-test', '~> 2.1.0'
- gem 'shoulda-context', '~> 2.0'
+ gem "launchy", "~> 3.0.0"
+ gem "rack-test", "~> 2.1.0"
+ gem "shoulda-context", "~> 2.0"
# Test prof provides factories from code
# and other niceties
- gem 'test-prof', '~> 1.3.0'
- gem 'turbo_tests', github: 'crohr/turbo_tests', ref: 'fix/runtime-info'
+ gem "test-prof", "~> 1.3.0"
+ gem "turbo_tests", github: "crohr/turbo_tests", ref: "fix/runtime-info"
- gem 'rack_session_access'
- gem 'rspec', '~> 3.13.0'
+ gem "rack_session_access"
+ gem "rspec", "~> 3.13.0"
# also add to development group, so 'spec' rake task gets loaded
- gem 'rspec-rails', '~> 6.1.0', group: :development
+ gem "rspec-rails", "~> 6.1.0", group: :development
# Retry failures within the same environment
- gem 'retriable', '~> 3.1.1'
- gem 'rspec-retry', '~> 0.6.1'
+ gem "retriable", "~> 3.1.1"
+ gem "rspec-retry", "~> 0.6.1"
# Accessibility tests
- gem 'axe-core-rspec'
+ gem "axe-core-rspec"
# Modify ENV
- gem 'climate_control'
+ gem "climate_control"
# XML comparison tests
- gem 'compare-xml', '~> 0.66', require: false
+ gem "compare-xml", "~> 0.66", require: false
# PDF Export tests
- gem 'pdf-inspector', '~> 1.2'
+ gem "pdf-inspector", "~> 1.2"
# brings back testing for 'assigns' and 'assert_template' extracted in rails 5
- gem 'rails-controller-testing', '~> 1.0.2'
+ gem "rails-controller-testing", "~> 1.0.2"
- gem 'capybara', '~> 3.40.0'
- gem 'capybara_accessible_selectors', git: 'https://github.com/citizensadvice/capybara_accessible_selectors', branch: 'main'
- gem 'capybara-screenshot', '~> 1.0.17'
- gem 'cuprite', '~> 0.15.0'
- gem 'selenium-devtools'
- gem 'selenium-webdriver', '~> 4.18.0'
+ gem "capybara", "~> 3.40.0"
+ gem "capybara_accessible_selectors", git: "https://github.com/citizensadvice/capybara_accessible_selectors", branch: "main"
+ gem "capybara-screenshot", "~> 1.0.17"
+ gem "cuprite", "~> 0.15.0"
+ gem "selenium-devtools"
+ gem "selenium-webdriver", "~> 4.18.0"
- gem 'fuubar', '~> 2.5.0'
- gem 'timecop', '~> 0.9.0'
+ gem "fuubar", "~> 2.5.0"
+ gem "timecop", "~> 0.9.0"
# Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests.
- gem 'vcr'
+ gem "vcr"
# Mock backend requests (for ruby tests)
- gem 'webmock', '~> 3.12', require: false
+ gem "webmock", "~> 3.12", require: false
# Mock selenium requests through proxy (for feature tests)
- gem 'puffing-billy', '~> 4.0.0'
- gem 'table_print', '~> 1.5.6'
+ gem "puffing-billy", "~> 4.0.0"
+ gem "table_print", "~> 1.5.6"
- gem 'equivalent-xml', '~> 0.6'
- gem 'json_spec', '~> 1.1.4'
- gem 'shoulda-matchers', '~> 6.0', require: nil
+ gem "equivalent-xml", "~> 0.6"
+ gem "json_spec", "~> 1.1.4"
+ gem "shoulda-matchers", "~> 6.0", require: nil
- gem 'parallel_tests', '~> 4.0'
+ gem "parallel_tests", "~> 4.0"
end
group :ldap do
- gem 'net-ldap', '~> 0.19.0'
+ gem "net-ldap", "~> 0.19.0"
end
group :development do
- gem 'listen', '~> 3.9.0' # Use for event-based reloaders
+ gem "listen", "~> 3.9.0" # Use for event-based reloaders
- gem 'letter_opener'
+ gem "letter_opener"
- gem 'spring'
- gem 'spring-commands-rspec'
- gem 'spring-commands-rubocop'
+ gem "spring"
+ gem "spring-commands-rspec"
+ gem "spring-commands-rubocop"
- gem 'colored2'
+ gem "colored2"
# git hooks manager
- gem 'lefthook', require: false
+ gem "lefthook", require: false
end
group :development, :test do
- gem 'dotenv-rails'
+ gem "dotenv-rails"
# Tracing and profiling gems
- gem 'flamegraph', require: false
- gem 'rack-mini-profiler', require: false
- gem 'ruby-prof', require: false
- gem 'stackprof', require: false
+ gem "flamegraph", require: false
+ gem "rack-mini-profiler", require: false
+ gem "ruby-prof", require: false
+ gem "stackprof", require: false
# REPL with debug commands
- gem 'debug'
+ gem "debug"
- gem 'pry-byebug', '~> 3.10.0', platforms: [:mri]
- gem 'pry-doc'
- gem 'pry-rails', '~> 0.3.6'
- gem 'pry-rescue', '~> 1.6.0'
+ gem "pry-byebug", "~> 3.10.0", platforms: [:mri]
+ gem "pry-doc"
+ gem "pry-rails", "~> 0.3.6"
+ gem "pry-rescue", "~> 1.6.0"
# ruby linting
- gem 'rubocop', require: false
- gem 'rubocop-inflector', require: false
- gem 'rubocop-performance', require: false
- gem 'rubocop-rails', require: false
- gem 'rubocop-rspec', require: false
+ gem "rubocop", require: false
+ gem "rubocop-inflector", require: false
+ gem "rubocop-performance", require: false
+ gem "rubocop-rails", require: false
+ gem "rubocop-rspec", require: false
# erb linting
- gem 'erb_lint', require: false
- gem 'erblint-github', require: false
+ gem "erb_lint", require: false
+ gem "erblint-github", require: false
# Brakeman scanner
- gem 'brakeman', '~> 6.1.0'
+ gem "brakeman", "~> 6.1.0"
# i18n-tasks helps find and manage missing and unused translations.
- gem 'i18n-tasks', '~> 1.0.13'
+ gem "i18n-tasks", "~> 1.0.13"
end
-gem 'bootsnap', '~> 1.18.0', require: false
+gem "bootsnap", "~> 1.18.0", require: false
# API gems
-gem 'grape', '~> 2.0.0'
-gem 'grape_logging', '~> 1.8.4'
-gem 'roar', '~> 1.2.0'
+gem "grape", "~> 2.0.0"
+gem "grape_logging", "~> 1.8.4"
+gem "roar", "~> 1.2.0"
# CORS for API
-gem 'rack-cors', '~> 2.0.2'
+gem "rack-cors", "~> 2.0.2"
# Gmail API
-gem 'google-apis-gmail_v1', require: false
-gem 'googleauth', require: false
+gem "google-apis-gmail_v1", require: false
+gem "googleauth", require: false
# Required for contracts
-gem 'disposable', '~> 0.6.2'
+gem "disposable", "~> 0.6.2"
platforms :mri, :mingw, :x64_mingw do
group :postgres do
- gem 'pg', '~> 1.5.0'
+ gem "pg", "~> 1.5.0"
end
# Support application loading when no database exists yet.
- gem 'activerecord-nulldb-adapter', '~> 1.0.0'
+ gem "activerecord-nulldb-adapter", "~> 1.0.0"
# Have application level locks on the database to have a mutex shared between workers/hosts.
# We e.g. employ this to safeguard the creation of journals.
- gem 'with_advisory_lock', '~> 5.1.0'
+ gem "with_advisory_lock", "~> 5.1.0"
end
# Load Gemfile.modules explicitly to allow dependabot to work
-eval_gemfile './Gemfile.modules'
+eval_gemfile "./Gemfile.modules"
# Load Gemfile.local, Gemfile.plugins and custom Gemfiles
-gemfiles = Dir.glob File.expand_path('{Gemfile.plugins,Gemfile.local}', __dir__)
-gemfiles << ENV['CUSTOM_PLUGIN_GEMFILE'] unless ENV['CUSTOM_PLUGIN_GEMFILE'].nil?
+gemfiles = Dir.glob File.expand_path("{Gemfile.plugins,Gemfile.local}", __dir__)
+gemfiles << ENV["CUSTOM_PLUGIN_GEMFILE"] unless ENV["CUSTOM_PLUGIN_GEMFILE"].nil?
gemfiles.each do |file|
# We use send to allow dependabot to function
# don't use eval_gemfile(file) here as it will break dependabot!
send(:eval_gemfile, file) if File.readable?(file)
end
-gem 'openproject-octicons', '~>19.8.0'
-gem 'openproject-octicons_helper', '~>19.8.0'
-gem 'openproject-primer_view_components', '~>0.23.0'
+gem "openproject-octicons", "~>19.8.0"
+gem "openproject-octicons_helper", "~>19.8.0"
+gem "openproject-primer_view_components", "~>0.23.0"
diff --git a/app/components/open_project/common/attribute_component.rb b/app/components/open_project/common/attribute_component.rb
index 12d1584a7b6e..e77a2f9892b5 100644
--- a/app/components/open_project/common/attribute_component.rb
+++ b/app/components/open_project/common/attribute_component.rb
@@ -57,7 +57,7 @@ def full_text
end
def display_expand_button_value
- multi_type? || text_ast.xpath('html/body').children.length > 1 ? :block : :none
+ multi_type? || body_children.length > 1 ? :block : :none
end
def text_color
@@ -67,18 +67,26 @@ def text_color
private
def first_paragraph
- @first_paragraph ||= text_ast
- .xpath('html/body')
- .children
- .first
- .inner_html
- .html_safe # rubocop:disable Rails/OutputSafety
+ @first_paragraph ||= if body_children.any?
+ body_children
+ .first
+ .inner_html
+ .html_safe # rubocop:disable Rails/OutputSafety
+ else
+ ''
+ end
end
def text_ast
@text_ast ||= Nokogiri::HTML(full_text)
end
+ def body_children
+ text_ast
+ .xpath('html/body')
+ .children
+ end
+
def multi_type?
first_paragraph.include?('figure') || first_paragraph.include?('macro')
end
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb
index 91dd292afc99..d670f34df98c 100644
--- a/app/controllers/admin_controller.rb
+++ b/app/controllers/admin_controller.rb
@@ -57,7 +57,7 @@ def projects
end
def plugins
- @plugins = Redmine::Plugin.all.sort
+ @plugins = Redmine::Plugin.not_bundled.sort
end
def test_email
diff --git a/app/views/admin/plugins.html.erb b/app/views/admin/plugins.html.erb
index 2343204c7d44..5ad7fe0851f0 100644
--- a/app/views/admin/plugins.html.erb
+++ b/app/views/admin/plugins.html.erb
@@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<% html_title t(:label_administration), t(:label_plugins) %>
-<%= toolbar title: t(:label_modules_and_plugins) %>
+<%= toolbar title: t(:label_plugins) %>
<% if @plugins.any? %>
@@ -88,5 +88,6 @@ See COPYRIGHT and LICENSE files for more details.
<% else %>
- <%= no_results_box %>
+ <%= no_results_box display_action: true,
+ action_url: OpenProject::Static::Links.url_for(:integrations) %>
<% end %>
diff --git a/config/constants/settings/definition.rb b/config/constants/settings/definition.rb
index b71ba78e7a4b..c3ec3526f229 100644
--- a/config/constants/settings/definition.rb
+++ b/config/constants/settings/definition.rb
@@ -28,7 +28,7 @@
module Settings
class Definition
- ENV_PREFIX = 'OPENPROJECT_'.freeze
+ ENV_PREFIX = "OPENPROJECT_".freeze
AR_BOOLEAN_TYPE = ActiveRecord::Type::Boolean.new
DEFINITIONS = {
activity_days_default: {
@@ -36,34 +36,34 @@ class Definition
},
after_first_login_redirect_url: {
format: :string,
- description: 'URL users logging in for the first time will be redirected to (e.g., a help screen)',
+ description: "URL users logging in for the first time will be redirected to (e.g., a help screen)",
default: nil
},
after_login_default_redirect_url: {
- description: 'Override URL to which logged in users are redirected instead of the My page',
+ description: "Override URL to which logged in users are redirected instead of the My page",
format: :string,
default: nil
},
apiv3_cors_enabled: {
- description: 'Enable CORS headers for APIv3 server responses',
+ description: "Enable CORS headers for APIv3 server responses",
default: false
},
apiv3_cors_origins: {
default: []
},
apiv3_docs_enabled: {
- description: 'Enable interactive APIv3 documentation as part of the application',
+ description: "Enable interactive APIv3 documentation as part of the application",
default: true
},
apiv3_enable_basic_auth: {
- description: 'Enable API token or global basic authentication for APIv3 requests',
+ description: "Enable API token or global basic authentication for APIv3 requests",
default: true
},
apiv3_max_page_size: {
default: 1000
},
app_title: {
- default: 'OpenProject'
+ default: "OpenProject"
},
attachment_max_size: {
default: 5120
@@ -76,41 +76,41 @@ class Definition
# Carrierwave storage type. Possible values are, among others, :file and :fog.
# The latter requires further configuration.
attachments_storage: {
- description: 'File storage configuration',
+ description: "File storage configuration",
default: :file,
format: :symbol,
allowed: %i[file fog],
writable: false
},
attachments_storage_path: {
- description: 'File storage disk location (only applicable for local file storage)',
+ description: "File storage disk location (only applicable for local file storage)",
format: :string,
default: nil,
writable: false
},
attachments_grace_period: {
- description: 'Time in minutes to wait before uploaded files not attached to any container are removed',
+ description: "Time in minutes to wait before uploaded files not attached to any container are removed",
default: 180
},
antivirus_scan_mode: {
- description: 'Virus scanning option for files uploaded to OpenProject',
+ description: "Virus scanning option for files uploaded to OpenProject",
format: :symbol,
default: :disabled,
allowed: %i[disabled clamav_socket clamav_host]
},
antivirus_scan_target: {
- description: 'The socket or hostname to connect to ClamAV',
+ description: "The socket or hostname to connect to ClamAV",
format: :string,
default: nil
},
antivirus_scan_action: {
- description: 'Virus scanning action for found infected files',
+ description: "Virus scanning action for found infected files",
format: :symbol,
default: :quarantine,
allowed: %i[quarantine delete]
},
auth_source_sso: {
- description: 'Configuration for Header-based Single Sign-On',
+ description: "Configuration for Header-based Single Sign-On",
format: :hash,
default: nil,
writable: false # config is cached globally so let's make it not writable
@@ -123,7 +123,7 @@ class Definition
# user: admin
# password: 123456
authentication: {
- description: 'Configuration options for global basic auth',
+ description: "Configuration options for global basic auth",
format: :hash,
default: nil
},
@@ -138,12 +138,12 @@ class Definition
allowed: [1, 7, 14, 30, 60, 90, 365]
},
autologin_cookie_name: {
- description: 'Cookie name for autologin cookie',
- default: 'autologin'
+ description: "Cookie name for autologin cookie",
+ default: "autologin"
},
autologin_cookie_path: {
- description: 'Cookie path for autologin cookie',
- default: '/'
+ description: "Cookie path for autologin cookie",
+ default: "/"
},
available_languages: {
format: :array,
@@ -153,33 +153,33 @@ class Definition
allowed: -> { Redmine::I18n.all_languages }
},
avatar_link_expiry_seconds: {
- description: 'Cache duration for avatar image API responses',
+ description: "Cache duration for avatar image API responses",
default: 24.hours.to_i
},
# Allow users with the required permissions to create backups via the web interface or API.
backup_enabled: {
- description: 'Enable application backups through the UI',
+ description: "Enable application backups through the UI",
default: true
},
backup_daily_limit: {
- description: 'Maximum number of application backups allowed per day',
+ description: "Maximum number of application backups allowed per day",
default: 3
},
backup_initial_waiting_period: {
- description: 'Wait time before newly created backup tokens are usable',
+ description: "Wait time before newly created backup tokens are usable",
default: 24.hours,
format: :integer
},
backup_include_attachments: {
- description: 'Allow inclusion of attachments in application backups',
+ description: "Allow inclusion of attachments in application backups",
default: true
},
backup_attachment_size_max_sum_mb: {
- description: 'Maximum limit of attachment size to include into application backups',
+ description: "Maximum limit of attachment size to include into application backups",
default: 1024
},
blacklisted_routes: {
- description: 'Blocked routes to prevent access to certain modules or pages',
+ description: "Blocked routes to prevent access to certain modules or pages",
default: [],
writable: false # used in initializer
},
@@ -187,19 +187,19 @@ class Definition
default: true
},
boards_demo_data_available: {
- description: 'Internal setting determining availability of demo seed data',
+ description: "Internal setting determining availability of demo seed data",
default: false
},
brute_force_block_minutes: {
- description: 'Number of minutes to block users after presumed brute force attack',
+ description: "Number of minutes to block users after presumed brute force attack",
default: 30
},
brute_force_block_after_failed_logins: {
- description: 'Number of login attempts per user before assuming brute force attack',
+ description: "Number of login attempts per user before assuming brute force attack",
default: 20
},
cache_expires_in_seconds: {
- description: 'Expiration time for memcache entries, empty for no expiry be default',
+ description: "Expiration time for memcache entries, empty for no expiry be default",
format: :integer,
default: nil,
writable: false
@@ -209,40 +209,40 @@ class Definition
},
# use dalli defaults for memcache
cache_memcache_server: {
- description: 'The memcache server host and IP',
+ description: "The memcache server host and IP",
format: :string,
default: nil,
writable: false
},
cache_redis_url: {
- description: 'URL to the redis cache server',
+ description: "URL to the redis cache server",
format: :string,
default: nil,
writable: false
},
cache_namespace: {
format: :string,
- description: 'Namespace for cache keys, useful when multiple applications use a single memcache server',
+ description: "Namespace for cache keys, useful when multiple applications use a single memcache server",
default: nil,
writable: false
},
commit_fix_done_ratio: {
- description: 'Progress to apply when commit fixes work package',
+ description: "Progress to apply when commit fixes work package",
default: 100
},
commit_fix_keywords: {
- description: 'Keywords to look for in commit for fixing work packages',
- default: 'fixes,closes'
+ description: "Keywords to look for in commit for fixing work packages",
+ default: "fixes,closes"
},
commit_fix_status_id: {
- description: 'Assigned status when fixing keyword is found',
+ description: "Assigned status when fixing keyword is found",
format: :integer,
default: nil,
allowed: -> { Status.pluck(:id) + [nil] }
},
commit_logs_encoding: {
description: "Encoding used to convert commit logs to UTF-8",
- default: 'UTF-8'
+ default: "UTF-8"
},
commit_logtime_activity_id: {
description: :setting_commit_logtime_activity_id,
@@ -256,7 +256,7 @@ class Definition
},
commit_ref_keywords: {
description: "Keywords used in commits for referencing work packages",
- default: 'refs,references,IssueID'
+ default: "refs,references,IssueID"
},
consent_decline_mail: {
format: :string,
@@ -283,7 +283,7 @@ class Definition
default: true
},
database_cipher_key: {
- description: 'Encryption key for repository credentials',
+ description: "Encryption key for repository credentials",
format: :string,
default: nil,
writable: false
@@ -292,28 +292,28 @@ class Definition
format: :string,
default: nil,
allowed: [
- '%Y-%m-%d',
- '%d/%m/%Y',
- '%d.%m.%Y',
- '%d-%m-%Y',
- '%m/%d/%Y',
- '%d %b %Y',
- '%d %B %Y',
- '%b %d, %Y',
- '%B %d, %Y'
+ "%Y-%m-%d",
+ "%d/%m/%Y",
+ "%d.%m.%Y",
+ "%d-%m-%Y",
+ "%m/%d/%Y",
+ "%d %b %Y",
+ "%d %B %Y",
+ "%b %d, %Y",
+ "%B %d, %Y"
].freeze
},
default_auto_hide_popups: {
- description: 'Whether to automatically hide success notifications by default',
+ description: "Whether to automatically hide success notifications by default",
default: true
},
# user configuration
default_comment_sort_order: {
- description: 'Default sort order for activities',
- default: 'asc'
+ description: "Default sort order for activities",
+ default: "asc"
},
default_language: {
- default: 'en',
+ default: "en",
allowed: -> { Redmine::I18n.all_languages }
},
default_projects_modules: {
@@ -333,7 +333,7 @@ class Definition
default: false
},
development_highlight_enabled: {
- description: 'Enable highlighting of development environment',
+ description: "Enable highlighting of development environment",
default: -> { Rails.env.development? },
format: :boolean
},
@@ -341,18 +341,18 @@ class Definition
default: 1500
},
direct_uploads: {
- description: 'Enable direct uploads to AWS S3. Only applicable with enabled Fog / AWS S3 configuration',
+ description: "Enable direct uploads to AWS S3. Only applicable with enabled Fog / AWS S3 configuration",
default: true,
writable: false
},
disable_browser_cache: {
- description: 'Prevent browser from caching any logged-in responses for security reasons',
+ description: "Prevent browser from caching any logged-in responses for security reasons",
default: true,
writable: false
},
# allow to disable default modules
disabled_modules: {
- description: 'A list of module names to prevent access to in the application',
+ description: "A list of module names to prevent access to in the application",
default: [],
allowed: -> { OpenProject::AccessControl.available_project_modules.map(&:to_s) },
writable: false # setting stored in global variable
@@ -362,48 +362,48 @@ class Definition
default: false
},
disable_password_login: {
- description: 'Disable internal logins and instead only allow SSO through OmniAuth.',
+ description: "Disable internal logins and instead only allow SSO through OmniAuth.",
default: false
},
display_subprojects_work_packages: {
default: true
},
drop_old_sessions_on_logout: {
- description: 'Destroy all sessions for current_user on logout',
+ description: "Destroy all sessions for current_user on logout",
default: true
},
drop_old_sessions_on_login: {
- description: 'Destroy all sessions for current_user on login',
+ description: "Destroy all sessions for current_user on login",
default: false
},
edition: {
format: :string,
- default: 'standard',
- description: 'OpenProject edition mode',
+ default: "standard",
+ description: "OpenProject edition mode",
writable: false,
allowed: %w[standard bim]
},
ee_manager_visible: {
- description: 'Show or hide the Enterprise configuration page and enterprise banners',
+ description: "Show or hide the Enterprise configuration page and enterprise banners",
default: true,
writable: false
},
enable_internal_assets_server: {
- description: 'Serve assets through the Rails internal asset server',
+ description: "Serve assets through the Rails internal asset server",
default: false,
writable: false
},
# email configuration
email_delivery_configuration: {
- default: 'inapp',
+ default: "inapp",
allowed: %w[inapp legacy],
writable: false,
- env_alias: 'EMAIL_DELIVERY_CONFIGURATION'
+ env_alias: "EMAIL_DELIVERY_CONFIGURATION"
},
email_delivery_method: {
format: :symbol,
default: nil,
- env_alias: 'EMAIL_DELIVERY_METHOD'
+ env_alias: "EMAIL_DELIVERY_METHOD"
},
emails_salutation: {
allowed: %w[firstname name],
@@ -411,12 +411,12 @@ class Definition
},
emails_footer: {
default: {
- 'en' => ''
+ "en" => ""
}
},
emails_header: {
default: {
- 'en' => ''
+ "en" => ""
}
},
# use email address as login, hide login in registration form
@@ -432,18 +432,18 @@ class Definition
},
# Allow connections for trial creation and booking
enterprise_trial_creation_host: {
- description: 'Host for EE trial service',
- default: 'https://start.openproject.com',
+ description: "Host for EE trial service",
+ default: "https://start.openproject.com",
writable: false
},
enterprise_chargebee_site: {
- description: 'Site name for EE trial service',
- default: 'openproject-enterprise',
+ description: "Site name for EE trial service",
+ default: "openproject-enterprise",
writable: false
},
enterprise_plan: {
- description: 'Default EE selected plan',
- default: 'enterprise-on-premises---euro---1-year',
+ description: "Default EE selected plan",
+ default: "enterprise-on-premises---euro---1-year",
writable: false
},
feeds_enabled: {
@@ -463,54 +463,54 @@ class Definition
allowed: [1, 4]
},
fog: {
- description: 'Configure fog, e.g. when using an S3 uploader',
+ description: "Configure fog, e.g. when using an S3 uploader",
default: {}
},
fog_download_url_expires_in: {
- description: 'Expiration time in seconds of created shared presigned URLs',
+ description: "Expiration time in seconds of created shared presigned URLs",
default: 21600 # 6h by default as 6 hours is max in S3 when using IAM roles
},
# Additional / overridden help links
force_help_link: {
- description: 'You can set a custom URL for the help button in application header menu.',
+ description: "You can set a custom URL for the help button in application header menu.",
format: :string,
default: nil
},
force_formatting_help_link: {
- description: 'You can set a custom URL for the help button in the WYSIWYG editor.',
+ description: "You can set a custom URL for the help button in the WYSIWYG editor.",
format: :string,
default: nil
},
forced_single_page_size: {
- description: 'Forced page size for manually sorted work package views',
+ description: "Forced page size for manually sorted work package views",
default: 250
},
good_job_queues: {
- description: '',
+ description: "",
format: :string,
writable: false,
- default: '*'
+ default: "*"
},
good_job_max_threads: {
- description: '',
+ description: "",
format: :integer,
writable: false,
default: 20
},
good_job_max_cache: {
- description: '',
+ description: "",
format: :integer,
writable: false,
default: 10_000
},
good_job_enable_cron: {
- description: '',
+ description: "",
format: :boolean,
writable: false,
default: true
},
good_job_cleanup_preserved_jobs_before_seconds_ago: {
- description: '',
+ description: "",
format: :integer,
writable: false,
default: 7.days
@@ -520,40 +520,40 @@ class Definition
},
# Health check configuration
health_checks_authentication_password: {
- description: 'Add an authentication challenge for the /health_check endpoint',
+ description: "Add an authentication challenge for the /health_check endpoint",
format: :string,
default: nil
},
## Maximum number of minutes that jobs have not yet run after their designated 'run_at' time
health_checks_jobs_never_ran_minutes_ago: {
- description: 'Set threshold of outstanding background jobs to fail health check',
+ description: "Set threshold of outstanding background jobs to fail health check",
format: :integer,
default: 5
},
## Maximum number of unprocessed requests in puma's backlog.
health_checks_backlog_threshold: {
- description: 'Set threshold of outstanding HTTP requests to fail health check',
+ description: "Set threshold of outstanding HTTP requests to fail health check",
format: :integer,
default: 20
},
# Default gravatar image, set to something other than 404
# to ensure a default is returned
gravatar_fallback_image: {
- description: 'Set default gravatar image fallback',
- default: '404'
+ description: "Set default gravatar image fallback",
+ default: "404"
},
hidden_menu_items: {
- description: 'Hide menu items in the menu sidebar for each main menu (such as Administration and Projects).',
+ description: "Hide menu items in the menu sidebar for each main menu (such as Administration and Projects).",
default: {},
writable: false # cached in global variable
},
impressum_link: {
- description: 'Impressum link to be set, hidden by default',
+ description: "Impressum link to be set, hidden by default",
format: :string,
default: nil
},
installation_type: {
- default: 'manual',
+ default: "manual",
writable: false
},
installation_uuid: {
@@ -561,7 +561,7 @@ class Definition
default: nil
},
internal_password_confirmation: {
- description: 'Require password confirmations for certain administrative actions',
+ description: "Require password confirmations for certain administrative actions",
default: true
},
invitation_expiration_days: {
@@ -571,20 +571,20 @@ class Definition
default: 5
},
ldap_force_no_page: {
- description: 'Force LDAP to respond as a single page, in case paged responses do not work with your server.',
+ description: "Force LDAP to respond as a single page, in case paged responses do not work with your server.",
format: :string,
default: nil
},
ldap_groups_disable_sync_job: {
- description: 'Deactivate regular synchronization job for groups in case scheduled as a separate cronjob',
+ description: "Deactivate regular synchronization job for groups in case scheduled as a separate cronjob",
default: false
},
ldap_users_disable_sync_job: {
- description: 'Deactivate user attributes synchronization from LDAP',
+ description: "Deactivate user attributes synchronization from LDAP",
default: false
},
ldap_users_sync_status: {
- description: 'Enable user status (locked/unlocked) synchronization from LDAP',
+ description: "Enable user status (locked/unlocked) synchronization from LDAP",
format: :boolean,
default: false
},
@@ -594,8 +594,8 @@ class Definition
writable: true
},
log_level: {
- description: 'Set the OpenProject logger level',
- default: Rails.env.development? ? 'debug' : 'info',
+ description: "Set the OpenProject logger level",
+ default: Rails.env.development? ? "debug" : "info",
allowed: %w[debug info warn error fatal],
writable: false
},
@@ -603,14 +603,14 @@ class Definition
default: false
},
lograge_enabled: {
- description: 'Use lograge formatter for outputting logs',
+ description: "Use lograge formatter for outputting logs",
default: true,
format: :boolean,
writable: false
},
lograge_formatter: {
- description: 'Lograge formatter to use for outputting logs',
- default: 'key_value',
+ description: "Lograge formatter to use for outputting logs",
+ default: "key_value",
format: :string,
writable: false
},
@@ -618,42 +618,42 @@ class Definition
default: true
},
lookbook_enabled: {
- description: 'Enable the Lookbook component documentation tool. Discouraged for production environments.',
+ description: "Enable the Lookbook component documentation tool. Discouraged for production environments.",
default: -> { Rails.env.development? },
format: :boolean
},
lost_password: {
- description: 'Activate or deactivate lost password form',
+ description: "Activate or deactivate lost password form",
default: true
},
mail_from: {
- default: 'openproject@example.net'
+ default: "openproject@example.net"
},
mail_handler_api_key: {
format: :string,
default: nil
},
mail_handler_body_delimiters: {
- default: ''
+ default: ""
},
mail_handler_body_delimiter_regex: {
- default: ''
+ default: ""
},
mail_handler_ignore_filenames: {
- default: 'signature.asc'
+ default: "signature.asc"
},
mail_suffix_separators: {
- default: '+'
+ default: "+"
},
main_content_language: {
- default: 'english',
- description: 'Main content language for PostgreSQL full text features',
+ default: "english",
+ description: "Main content language for PostgreSQL full text features",
writable: false,
allowed: %w[danish dutch english finnish french german hungarian
italian norwegian portuguese romanian russian simple spanish swedish turkish]
},
migration_check_on_exceptions: {
- description: 'Check for missing migrations in internal errors',
+ description: "Check for missing migrations in internal errors",
default: true,
writable: false
},
@@ -671,12 +671,12 @@ class Definition
default: 60000
},
oauth_allow_remapping_of_existing_users: {
- description: 'When set to false, prevent users from other identity providers to take over accounts connected ' \
- 'to another identity provider.',
+ description: "When set to false, prevent users from other identity providers to take over accounts connected " \
+ "to another identity provider.",
default: true
},
omniauth_direct_login_provider: {
- description: 'Clicking on login sends a login request to the specified OmniAuth provider.',
+ description: "Clicking on login sends a login request to the specified OmniAuth provider.",
format: :string,
default: nil
},
@@ -687,11 +687,11 @@ class Definition
writable: false # this changes a global variable and must therefore not be writable at runtime
},
onboarding_video_url: {
- description: 'Onboarding guide instructional video URL',
- default: 'https://player.vimeo.com/video/163426858?autoplay=1'
+ description: "Onboarding guide instructional video URL",
+ default: "https://player.vimeo.com/video/163426858?autoplay=1"
},
onboarding_enabled: {
- description: 'Enable or disable onboarding guided tour for new users',
+ description: "Enable or disable onboarding guided tour for new users",
default: true
},
password_active_rules: {
@@ -714,7 +714,7 @@ class Definition
# Requires a migration to be written
# replace Setting#per_page_options_array
per_page_options: {
- default: '20, 100'
+ default: "20, 100"
},
plain_text_mail: {
default: false
@@ -724,63 +724,63 @@ class Definition
format: :string
},
rails_asset_host: {
- description: 'Custom asset hostname for serving assets (e.g., Cloudfront)',
+ description: "Custom asset hostname for serving assets (e.g., Cloudfront)",
format: :string,
default: nil,
writable: false
},
rails_cache_store: {
- description: 'Set cache store implemenation to use with OpenProject',
+ description: "Set cache store implemenation to use with OpenProject",
format: :symbol,
default: :file_store,
writable: false,
allowed: %i[file_store memcache redis]
},
rails_relative_url_root: {
- description: 'Set a URL prefix / base path to run OpenProject under, e.g., host.tld/openproject',
- default: '',
+ description: "Set a URL prefix / base path to run OpenProject under, e.g., host.tld/openproject",
+ default: "",
writable: false
},
https: {
- description: 'Set assumed connection security for the Rails processes',
+ description: "Set assumed connection security for the Rails processes",
format: :boolean,
default: -> { Rails.env.production? },
writable: false
},
hsts: {
- description: 'Allow disabling of HSTS headers and http -> https redirects',
+ description: "Allow disabling of HSTS headers and http -> https redirects",
format: :boolean,
default: true,
writable: false
},
home_url: {
- description: 'Override default link when clicking on the top menu logo (Homescreen by default).',
+ description: "Override default link when clicking on the top menu logo (Homescreen by default).",
format: :string,
default: nil
},
httpx_connect_timeout: {
- description: '',
+ description: "",
format: :float,
writable: false,
allowed: (0..),
default: 3
},
httpx_read_timeout: {
- description: '',
+ description: "",
format: :float,
writable: false,
allowed: (0..),
default: 3
},
httpx_write_timeout: {
- description: '',
+ description: "",
format: :float,
writable: false,
allowed: (0..),
default: 3
},
httpx_keep_alive_timeout: {
- description: '',
+ description: "",
format: :float,
writable: false,
allowed: (0..),
@@ -788,27 +788,27 @@ class Definition
},
rate_limiting: {
default: {},
- description: 'Configure rate limiting for various endpoint rules. See configuration documentation for details.'
+ description: "Configure rate limiting for various endpoint rules. See configuration documentation for details."
},
registration_footer: {
default: {
- 'en' => ''
+ "en" => ""
}
},
remote_storage_upload_host: {
format: :string,
default: nil,
writable: false,
- description: 'Host the frontend uses to upload files to, which has to be added to the CSP.'
+ description: "Host the frontend uses to upload files to, which has to be added to the CSP."
},
remote_storage_download_host: {
format: :string,
default: nil,
writable: false,
- description: 'Host the frontend uses to download files, which has to be added to the CSP.'
+ description: "Host the frontend uses to download files, which has to be added to the CSP."
},
report_incoming_email_errors: {
- description: 'Respond to incoming mails with error details',
+ description: "Respond to incoming mails with error details",
default: true
},
repositories_automatic_managed_vendor: {
@@ -854,7 +854,7 @@ class Definition
writable: false
},
scm_local_checkout_path: {
- default: 'repositories', # relative to OpenProject directory
+ default: "repositories", # relative to OpenProject directory
writable: false
},
scm_subversion_command: {
@@ -867,32 +867,32 @@ class Definition
default: true
},
security_badge_url: {
- description: 'URL of the update check badge',
+ description: "URL of the update check badge",
default: "https://releases.openproject.com/v1/check.svg",
writable: false
},
seed_admin_user_password: {
description: 'Password to set for the initially created admin user (Login remains "admin").',
- default: 'admin',
+ default: "admin",
writable: false
},
seed_admin_user_mail: {
- description: 'E-mail to set for the initially created admin user.',
- default: 'admin@example.net',
+ description: "E-mail to set for the initially created admin user.",
+ default: "admin@example.net",
writable: false
},
seed_admin_user_name: {
- description: 'Name to set for the initially created admin user.',
- default: 'OpenProject Admin',
+ description: "Name to set for the initially created admin user.",
+ default: "OpenProject Admin",
writable: false
},
seed_admin_user_password_reset: {
- description: 'Whether to force a password reset for the initially created admin user.',
+ description: "Whether to force a password reset for the initially created admin user.",
default: true,
writable: false
},
seed_ldap: {
- description: 'Provide an LDAP connection and sync settings through ENV',
+ description: "Provide an LDAP connection and sync settings through ENV",
writable: false,
default: nil,
format: :hash
@@ -901,12 +901,12 @@ class Definition
default: 2
},
sendmail_arguments: {
- description: 'Arguments to call sendmail with in case it is configured as outgoing email setup',
+ description: "Arguments to call sendmail with in case it is configured as outgoing email setup",
format: :string,
default: "-i"
},
sendmail_location: {
- description: 'Location of sendmail to call if it is configured as outgoing email setup',
+ description: "Location of sendmail to call if it is configured as outgoing email setup",
format: :string,
default: "/usr/sbin/sendmail"
},
@@ -914,11 +914,11 @@ class Definition
appsignal_frontend_key: {
format: :string,
default: nil,
- description: 'Appsignal API key for JavaScript error reporting'
+ description: "Appsignal API key for JavaScript error reporting"
},
session_cookie_name: {
- description: 'Set session cookie name',
- default: '_open_project_session'
+ description: "Set session cookie name",
+ default: "_open_project_session"
},
session_ttl_enabled: {
default: false
@@ -927,44 +927,44 @@ class Definition
default: 120
},
show_community_links: {
- description: 'Enable or disable links to OpenProject community instances',
+ description: "Enable or disable links to OpenProject community instances",
default: true
},
show_product_version: {
- description: 'Show product version information in the administration section',
+ description: "Show product version information in the administration section",
default: true
},
show_pending_migrations_warning: {
- description: 'Enable or disable warning bar in case of pending migrations',
+ description: "Enable or disable warning bar in case of pending migrations",
default: true,
writable: false
},
show_setting_mismatch_warning: {
- description: 'Show mismatched protocol/hostname warning. In cases where they must differ this can be disabled',
+ description: "Show mismatched protocol/hostname warning. In cases where they must differ this can be disabled",
default: true
},
# Render storage information
show_storage_information: {
- description: 'Show available and taken storage information under administration / info',
+ description: "Show available and taken storage information under administration / info",
default: true
},
show_warning_bars: {
- description: 'Render warning bars (pending migrations, deprecation, unsupported browsers)',
+ description: "Render warning bars (pending migrations, deprecation, unsupported browsers)",
# Hide warning bars by default in tests as they might overlay other elements
default: -> { !Rails.env.test? }
},
smtp_authentication: {
format: :string,
- default: 'plain',
- env_alias: 'SMTP_AUTHENTICATION'
+ default: "plain",
+ env_alias: "SMTP_AUTHENTICATION"
},
smtp_enable_starttls_auto: {
format: :boolean,
default: false,
- env_alias: 'SMTP_ENABLE_STARTTLS_AUTO'
+ env_alias: "SMTP_ENABLE_STARTTLS_AUTO"
},
smtp_openssl_verify_mode: {
- description: 'Globally set verify mode for OpenSSL. Careful: Setting to none will disable any SSL verification!',
+ description: "Globally set verify mode for OpenSSL. Careful: Setting to none will disable any SSL verification!",
format: :string,
default: "peer",
allowed: %w[none peer client_once fail_if_no_peer_cert],
@@ -973,43 +973,43 @@ class Definition
smtp_ssl: {
format: :boolean,
default: false,
- env_alias: 'SMTP_SSL'
+ env_alias: "SMTP_SSL"
},
smtp_address: {
format: :string,
- default: '',
- env_alias: 'SMTP_ADDRESS'
+ default: "",
+ env_alias: "SMTP_ADDRESS"
},
smtp_domain: {
format: :string,
- default: 'your.domain.com',
- env_alias: 'SMTP_DOMAIN'
+ default: "your.domain.com",
+ env_alias: "SMTP_DOMAIN"
},
smtp_user_name: {
format: :string,
- default: '',
- env_alias: 'SMTP_USER_NAME'
+ default: "",
+ env_alias: "SMTP_USER_NAME"
},
smtp_port: {
format: :integer,
default: 587,
- env_alias: 'SMTP_PORT'
+ env_alias: "SMTP_PORT"
},
smtp_password: {
format: :string,
- default: '',
- env_alias: 'SMTP_PASSWORD'
+ default: "",
+ env_alias: "SMTP_PASSWORD"
},
software_name: {
- description: 'Override software application name',
- default: 'OpenProject'
+ description: "Override software application name",
+ default: "OpenProject"
},
software_url: {
- description: 'Override software application URL',
- default: 'https://www.openproject.org/'
+ description: "Override software application URL",
+ default: "https://www.openproject.org/"
},
sql_slow_query_threshold: {
- description: 'Time limit in ms after which queries will be logged as slow queries',
+ description: "Time limit in ms after which queries will be logged as slow queries",
default: 2000,
writable: false
},
@@ -1019,19 +1019,19 @@ class Definition
allowed: [1, 6, 7]
},
statsd: {
- description: 'enable statsd metrics (currently puma only) by configuring host',
+ description: "enable statsd metrics (currently puma only) by configuring host",
default: {
- 'host' => nil,
- 'port' => 8125
+ "host" => nil,
+ "port" => 8125
},
writable: false
},
sys_api_enabled: {
- description: 'Enable internal system API for setting up managed repositories',
+ description: "Enable internal system API for setting up managed repositories",
default: false
},
sys_api_key: {
- description: 'Internal system API key for setting up managed repositories',
+ description: "Internal system API key for setting up managed repositories",
default: nil,
format: :string
},
@@ -1039,8 +1039,8 @@ class Definition
format: :string,
default: nil,
allowed: [
- '%H:%M',
- '%I:%M %p'
+ "%H:%M",
+ "%I:%M %p"
].freeze
},
user_default_timezone: {
@@ -1052,10 +1052,10 @@ class Definition
default: false
},
user_default_theme: {
- default: 'light',
+ default: "light",
format: :string,
allowed: -> do
- UserPreferences::Schema.schema.dig('definitions', 'UserPreferences', 'properties', 'theme', 'enum')
+ UserPreferences::Schema.schema.dig("definitions", "UserPreferences", "properties", "theme", "enum")
end
},
users_deletable_by_self: {
@@ -1066,13 +1066,13 @@ class Definition
allowed: -> { User::USER_FORMATS_STRUCTURE.keys }
},
web: {
- description: 'Web worker count and threads configuration',
+ description: "Web worker count and threads configuration",
default: {
- 'workers' => 2,
- 'timeout' => 120,
- 'wait_timeout' => 10,
- 'min_threads' => 4,
- 'max_threads' => 16
+ "workers" => 2,
+ "timeout" => 120,
+ "wait_timeout" => 10,
+ "min_threads" => 4,
+ "max_threads" => 16
},
writable: false
},
@@ -1088,7 +1088,7 @@ class Definition
default: false
},
work_package_done_ratio: {
- default: 'field',
+ default: "field",
allowed: %w[field status disabled]
},
work_packages_projects_export_limit: {
@@ -1105,7 +1105,7 @@ class Definition
},
work_package_list_default_highlighting_mode: {
format: :string,
- default: -> { EnterpriseToken.allows_to?(:conditional_highlighting) ? 'inline' : 'none' },
+ default: -> { EnterpriseToken.allows_to?(:conditional_highlighting) ? "inline" : "none" },
allowed: -> { Query::QUERY_HIGHLIGHTING_MODES.map(&:to_s) },
writable: -> { EnterpriseToken.allows_to?(:conditional_highlighting) }
},
@@ -1117,14 +1117,14 @@ class Definition
default: false
},
working_days: {
- description: 'Set working days of the week (Array of 1 to 7, where 1=Monday, 7=Sunday)',
+ description: "Set working days of the week (Array of 1 to 7, where 1=Monday, 7=Sunday)",
format: :array,
allowed: Array(1..7),
default: Array(1..5) # Sat, Sun being non-working days,
},
youtube_channel: {
- description: 'Link to YouTube channel in help menu',
- default: 'https://www.youtube.com/c/OpenProjectCommunity'
+ description: "Link to YouTube channel in help menu",
+ default: "https://www.youtube.com/c/OpenProjectCommunity"
}
}.freeze
@@ -1289,7 +1289,7 @@ def all
def file_config
@file_config ||= begin
- filename = Rails.root.join('config/configuration.yml')
+ filename = Rails.root.join("config/configuration.yml")
file_config = {}
@@ -1314,8 +1314,8 @@ def override_value(definition)
end
def override_value_from_file(definition)
- envs = ['default', Rails.env]
- envs.delete('default') if Rails.env.test? # The test setup should govern the configuration
+ envs = ["default", Rails.env]
+ envs.delete("default") if Rails.env.test? # The test setup should govern the configuration
envs.each do |env|
next unless (env_config = file_config[env])
next unless env_config.has_key?(definition.name)
@@ -1379,7 +1379,7 @@ def path_to_hash(*path)
end
def unescape_underscores(path_segment)
- path_segment.gsub '__', '_'
+ path_segment.gsub "__", "_"
end
def find_env_var_override(definition)
@@ -1449,7 +1449,7 @@ def env_name_alias(definition)
def extract_value_from_env(env_var_name, env_var_value)
# YAML parses '' as false, but empty ENV variables will be passed as that.
# To specify specific values, one can use !!str (-> '') or !!null (-> nil)
- return env_var_value if env_var_value == ''
+ return env_var_value if env_var_value == ""
parsed = load_yaml(env_var_value)
diff --git a/config/initializers/menus.rb b/config/initializers/menus.rb
index a5ce41ce3da2..ec7662bd4824 100644
--- a/config/initializers/menus.rb
+++ b/config/initializers/menus.rb
@@ -26,39 +26,39 @@
# See COPYRIGHT and LICENSE files for more details.
#++
-require 'redmine/menu_manager'
+require "redmine/menu_manager"
Redmine::MenuManager.map :top_menu do |menu|
# projects menu will be added by
# Redmine::MenuManager::TopMenuHelper#render_projects_top_menu_node
menu.push :projects,
- { controller: '/projects', project_id: nil, action: 'index' },
+ { controller: "/projects", project_id: nil, action: "index" },
context: :modules,
- caption: I18n.t('label_projects_menu'),
- icon: 'projects',
+ caption: I18n.t("label_projects_menu"),
+ icon: "projects",
if: Proc.new {
User.current.logged? || !Setting.login_required?
}
menu.push :activity,
- { controller: '/activities', action: 'index' },
+ { controller: "/activities", action: "index" },
context: :modules,
- icon: 'checkmark'
+ icon: "checkmark"
menu.push :work_packages,
- { controller: '/work_packages', project_id: nil, state: nil, action: 'index' },
+ { controller: "/work_packages", project_id: nil, state: nil, action: "index" },
context: :modules,
- caption: I18n.t('label_work_package_plural'),
- icon: 'work-packages',
+ caption: I18n.t("label_work_package_plural"),
+ icon: "work-packages",
if: Proc.new {
(User.current.logged? || !Setting.login_required?) &&
User.current.allowed_in_any_work_package?(:view_work_packages)
}
menu.push :news,
- { controller: '/news', project_id: nil, action: 'index' },
+ { controller: "/news", project_id: nil, action: "index" },
context: :modules,
- caption: I18n.t('label_news_plural'),
- icon: 'news',
+ caption: I18n.t("label_news_plural"),
+ icon: "news",
if: Proc.new {
(User.current.logged? || !Setting.login_required?) &&
User.current.allowed_in_any_project?(:view_news)
@@ -67,17 +67,17 @@
menu.push :help,
OpenProject::Static::Links.help_link,
last: true,
- caption: '',
- icon: 'help op-app-help--icon',
+ caption: "",
+ icon: "help op-app-help--icon",
html: { accesskey: OpenProject::AccessKeys.key_for(:help),
- title: I18n.t('label_help'),
- target: '_blank' }
+ title: I18n.t("label_help"),
+ target: "_blank" }
end
Redmine::MenuManager.map :quick_add_menu do |menu|
menu.push :new_project,
Proc.new { |project|
- { controller: '/projects', action: :new, project_id: nil, parent_id: project&.id }
+ { controller: "/projects", action: :new, project_id: nil, parent_id: project&.id }
},
caption: ->(*) { Project.model_name.human },
icon: "add",
@@ -93,32 +93,32 @@
menu.push :invite_user,
nil,
caption: :label_invite_user,
- icon: 'user-plus',
+ icon: "user-plus",
html: {
- 'invite-user-modal-augment': 'invite-user-modal-augment'
+ "invite-user-modal-augment": "invite-user-modal-augment"
},
if: Proc.new { User.current.allowed_in_any_project?(:manage_members) }
end
Redmine::MenuManager.map :account_menu do |menu|
menu.push :timers,
- { controller: '/my/timer', action: 'show' },
- partial: '/my/timer/menu'
+ { controller: "/my/timer", action: "show" },
+ partial: "/my/timer/menu"
menu.push :my_page,
:my_page_path,
- caption: I18n.t('js.my_page.label'),
+ caption: I18n.t("js.my_page.label"),
if: Proc.new { User.current.logged? }
menu.push :my_profile,
- { controller: '/users', action: 'show', id: 'me' },
+ { controller: "/users", action: "show", id: "me" },
caption: :label_my_activity,
if: Proc.new { User.current.logged? }
menu.push :my_account,
- { controller: '/my', action: 'account' },
+ { controller: "/my", action: "account" },
if: Proc.new { User.current.logged? }
menu.push :administration,
- { controller: '/admin', action: 'index' },
+ { controller: "/admin", action: "index" },
if: Proc.new {
- User.current.allowed_globally?({ controller: '/admin', action: 'index' })
+ User.current.allowed_globally?({ controller: "/admin", action: "index" })
}
menu.push :logout,
:signout_path,
@@ -128,53 +128,53 @@
Redmine::MenuManager.map :global_menu do |menu|
# Homescreen
menu.push :home,
- { controller: '/homescreen', action: 'index' },
- icon: 'home',
+ { controller: "/homescreen", action: "index" },
+ icon: "home",
first: true
# Projects
menu.push :projects,
- { controller: '/projects', project_id: nil, action: 'index' },
- caption: I18n.t('label_projects_menu'),
- icon: 'projects',
+ { controller: "/projects", project_id: nil, action: "index" },
+ caption: I18n.t("label_projects_menu"),
+ icon: "projects",
after: :home,
if: Proc.new {
User.current.logged? || !Setting.login_required?
}
menu.push :projects_query_select,
- { controller: '/projects', project_id: nil, action: 'index' },
+ { controller: "/projects", project_id: nil, action: "index" },
parent: :projects,
- partial: 'projects/menus/menu'
+ partial: "projects/menus/menu"
# Activity
menu.push :activity,
- { controller: '/activities', action: 'index' },
- icon: 'checkmark',
+ { controller: "/activities", action: "index" },
+ icon: "checkmark",
after: :projects
menu.push :activity_filters,
- { controller: '/activities', action: 'index' },
+ { controller: "/activities", action: "index" },
parent: :activity,
- partial: 'activities/filters_menu'
+ partial: "activities/filters_menu"
# Work packages
menu.push :work_packages,
- { controller: '/work_packages', action: 'index' },
+ { controller: "/work_packages", action: "index" },
caption: :label_work_package_plural,
- icon: 'view-list',
+ icon: "view-list",
after: :activity
menu.push :work_packages_query_select,
- { controller: '/work_packages', action: 'index' },
+ { controller: "/work_packages", action: "index" },
parent: :work_packages,
- partial: 'work_packages/menu_query_select'
+ partial: "work_packages/menu_query_select"
# News
menu.push :news,
- { controller: '/news', project_id: nil, action: 'index' },
- caption: I18n.t('label_news_plural'),
- icon: 'news',
+ { controller: "/news", project_id: nil, action: "index" },
+ caption: I18n.t("label_news_plural"),
+ icon: "news",
after: :boards,
if: Proc.new {
(User.current.logged? || !Setting.login_required?) &&
@@ -184,194 +184,194 @@
Redmine::MenuManager.map :notifications_menu do |menu|
menu.push :notification_grouping_select,
- { controller: '/my', action: 'notifications' },
- partial: 'notifications/menu_notification_center'
+ { controller: "/my", action: "notifications" },
+ partial: "notifications/menu_notification_center"
end
Redmine::MenuManager.map :my_menu do |menu|
menu.push :account,
- { controller: '/my', action: 'account' },
+ { controller: "/my", action: "account" },
caption: :label_profile,
- icon: 'user'
+ icon: "user"
menu.push :settings,
- { controller: '/my', action: 'settings' },
+ { controller: "/my", action: "settings" },
caption: :label_setting_plural,
- icon: 'settings2'
+ icon: "settings2"
menu.push :password,
- { controller: '/my', action: 'password' },
+ { controller: "/my", action: "password" },
caption: :button_change_password,
if: Proc.new { User.current.change_password_allowed? },
- icon: 'locked'
+ icon: "locked"
menu.push :access_token,
- { controller: '/my', action: 'access_token' },
- caption: I18n.t('my_account.access_tokens.access_tokens'),
- icon: 'key'
+ { controller: "/my", action: "access_token" },
+ caption: I18n.t("my_account.access_tokens.access_tokens"),
+ icon: "key"
menu.push :sessions,
- { controller: '/my/sessions', action: :index },
- caption: :'users.sessions.title',
- icon: 'installation-services'
+ { controller: "/my/sessions", action: :index },
+ caption: :"users.sessions.title",
+ icon: "installation-services"
menu.push :notifications,
- { controller: '/my', action: 'notifications' },
- caption: I18n.t('js.notifications.settings.title'),
- icon: 'bell'
+ { controller: "/my", action: "notifications" },
+ caption: I18n.t("js.notifications.settings.title"),
+ icon: "bell"
menu.push :reminders,
- { controller: '/my', action: 'reminders' },
- caption: I18n.t('js.reminders.settings.title'),
- icon: 'email-alert'
+ { controller: "/my", action: "reminders" },
+ caption: I18n.t("js.reminders.settings.title"),
+ icon: "email-alert"
menu.push :delete_account, :delete_my_account_info_path,
- caption: I18n.t('account.delete'),
+ caption: I18n.t("account.delete"),
param: :user_id,
if: Proc.new { Setting.users_deletable_by_self? },
last: :delete_account,
- icon: 'delete'
+ icon: "delete"
end
Redmine::MenuManager.map :admin_menu do |menu|
menu.push :admin_overview,
- { controller: '/admin', action: :index },
+ { controller: "/admin", action: :index },
if: Proc.new { User.current.admin? },
caption: :label_overview,
- icon: 'home',
+ icon: "home",
first: true
menu.push :users,
- { controller: '/users' },
+ { controller: "/users" },
if: Proc.new {
!User.current.admin? &&
(User.current.allowed_globally?(:manage_user) || User.current.allowed_globally?(:create_user))
},
caption: :label_user_plural,
- icon: 'group'
+ icon: "group"
menu.push :placeholder_users,
- { controller: '/placeholder_users' },
+ { controller: "/placeholder_users" },
if: Proc.new { !User.current.admin? && User.current.allowed_globally?(:manage_placeholder_user) },
caption: :label_placeholder_user_plural,
- icon: 'group'
+ icon: "group"
menu.push :users_and_permissions,
- { controller: '/users' },
+ { controller: "/users" },
if: Proc.new { User.current.admin? },
caption: :label_user_and_permission,
- icon: 'group'
+ icon: "group"
menu.push :user_settings,
- { controller: '/admin/settings/users_settings', action: :show },
+ { controller: "/admin/settings/users_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_users_settings,
parent: :users_and_permissions
menu.push :users,
- { controller: '/users' },
+ { controller: "/users" },
if: Proc.new { User.current.admin? },
caption: :label_user_plural,
parent: :users_and_permissions
menu.push :placeholder_users,
- { controller: '/placeholder_users' },
+ { controller: "/placeholder_users" },
if: Proc.new { User.current.admin? },
caption: :label_placeholder_user_plural,
parent: :users_and_permissions,
- enterprise_feature: 'placeholder_users'
+ enterprise_feature: "placeholder_users"
menu.push :groups,
- { controller: '/groups' },
+ { controller: "/groups" },
if: Proc.new { User.current.admin? },
caption: :label_group_plural,
parent: :users_and_permissions
menu.push :roles,
- { controller: '/roles' },
+ { controller: "/roles" },
if: Proc.new { User.current.admin? },
caption: :label_role_and_permissions,
parent: :users_and_permissions
menu.push :permissions_report,
- { controller: '/roles', action: 'report' },
+ { controller: "/roles", action: "report" },
if: Proc.new { User.current.admin? },
caption: :label_permissions_report,
parent: :users_and_permissions
menu.push :user_avatars,
- { controller: '/admin/settings', action: 'show_plugin', id: :openproject_avatars },
+ { controller: "/admin/settings", action: "show_plugin", id: :openproject_avatars },
if: Proc.new { User.current.admin? },
caption: :label_avatar_plural,
parent: :users_and_permissions
menu.push :admin_work_packages,
- { controller: '/admin/settings/work_packages_settings', action: :show },
+ { controller: "/admin/settings/work_packages_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_work_package_plural,
- icon: 'view-list'
+ icon: "view-list"
menu.push :work_packages_setting,
- { controller: '/admin/settings/work_packages_settings', action: :show },
+ { controller: "/admin/settings/work_packages_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_setting_plural,
parent: :admin_work_packages
menu.push :types,
- { controller: '/types' },
+ { controller: "/types" },
if: Proc.new { User.current.admin? },
caption: :label_type_plural,
parent: :admin_work_packages
menu.push :statuses,
- { controller: '/statuses' },
+ { controller: "/statuses" },
if: Proc.new { User.current.admin? },
caption: :label_status,
parent: :admin_work_packages,
- html: { class: 'statuses' }
+ html: { class: "statuses" }
menu.push :workflows,
- { controller: '/workflows', action: 'edit' },
+ { controller: "/workflows", action: "edit" },
if: Proc.new { User.current.admin? },
caption: Proc.new { Workflow.model_name.human },
parent: :admin_work_packages
menu.push :custom_fields,
- { controller: '/custom_fields' },
+ { controller: "/custom_fields" },
if: Proc.new { User.current.admin? },
caption: :label_custom_field_plural,
- icon: 'custom-fields',
- html: { class: 'custom_fields' }
+ icon: "custom-fields",
+ html: { class: "custom_fields" }
menu.push :custom_actions,
- { controller: '/custom_actions' },
+ { controller: "/custom_actions" },
if: Proc.new { User.current.admin? },
- caption: :'custom_actions.plural',
+ caption: :"custom_actions.plural",
parent: :admin_work_packages,
- enterprise_feature: 'custom_actions'
+ enterprise_feature: "custom_actions"
menu.push :attribute_help_texts,
- { controller: '/attribute_help_texts' },
- caption: :'attribute_help_texts.label_plural',
- icon: 'help2',
+ { controller: "/attribute_help_texts" },
+ caption: :"attribute_help_texts.label_plural",
+ icon: "help2",
if: Proc.new { User.current.allowed_globally?(:edit_attribute_help_texts) }
menu.push :attachments,
- { controller: '/admin/settings/attachments_settings', action: :show },
- caption: :'attributes.attachments',
- icon: 'attachment',
+ { controller: "/admin/settings/attachments_settings", action: :show },
+ caption: :"attributes.attachments",
+ icon: "attachment",
if: Proc.new { User.current.admin? }
menu.push :attachments_settings,
- { controller: '/admin/settings/attachments_settings', action: :show },
+ { controller: "/admin/settings/attachments_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_setting_plural,
parent: :attachments
menu.push :virus_scanning_settings,
- { controller: '/admin/settings/virus_scanning_settings', action: :show },
- caption: :'settings.antivirus.title',
+ { controller: "/admin/settings/virus_scanning_settings", action: :show },
+ caption: :"settings.antivirus.title",
parent: :attachments,
- enterprise_feature: 'virus_scanning',
+ enterprise_feature: "virus_scanning",
if: Proc.new { User.current.admin? }
menu.push :attachment_quarantine,
- { controller: '/admin/attachments/quarantined_attachments', action: :index },
- caption: :'antivirus_scan.quarantined_attachments.title',
+ { controller: "/admin/attachments/quarantined_attachments", action: :index },
+ caption: :"antivirus_scan.quarantined_attachments.title",
parent: :attachments,
if: Proc.new {
User.current.admin? &&
@@ -379,176 +379,176 @@
}
menu.push :enumerations,
- { controller: '/enumerations' },
+ { controller: "/enumerations" },
if: Proc.new { User.current.admin? },
- icon: 'enumerations'
+ icon: "enumerations"
menu.push :calendars_and_dates,
- { controller: '/admin/settings/working_days_settings', action: :show },
+ { controller: "/admin/settings/working_days_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_calendars_and_dates,
- icon: 'calendar'
+ icon: "calendar"
menu.push :working_days,
- { controller: '/admin/settings/working_days_settings', action: :show },
+ { controller: "/admin/settings/working_days_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_working_days,
parent: :calendars_and_dates
menu.push :date_format,
- { controller: '/admin/settings/date_format_settings', action: :show },
+ { controller: "/admin/settings/date_format_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_date_format,
parent: :calendars_and_dates
menu.push :icalendar,
- { controller: '/admin/settings/icalendar_settings', action: :show },
+ { controller: "/admin/settings/icalendar_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_calendar_subscriptions,
parent: :calendars_and_dates
menu.push :settings,
- { controller: '/admin/settings/general_settings', action: :show },
+ { controller: "/admin/settings/general_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_system_settings,
- icon: 'settings2'
+ icon: "settings2"
SettingsHelper.system_settings_tabs.each do |node|
menu.push :"settings_#{node[:name]}",
{ controller: node[:controller], action: :show },
caption: node[:label],
- if: Proc.new { User.current.admin? && node[:name] != 'experimental' },
+ if: Proc.new { User.current.admin? && node[:name] != "experimental" },
parent: :settings
end
menu.push :mail_and_notifications,
- { controller: '/admin/settings/aggregation_settings', action: :show },
+ { controller: "/admin/settings/aggregation_settings", action: :show },
if: Proc.new { User.current.admin? },
- caption: :'menus.admin.mails_and_notifications',
- icon: 'mail1'
+ caption: :"menus.admin.mails_and_notifications",
+ icon: "mail1"
menu.push :notification_settings,
- { controller: '/admin/settings/aggregation_settings', action: :show },
+ { controller: "/admin/settings/aggregation_settings", action: :show },
if: Proc.new { User.current.admin? },
- caption: :'menus.admin.aggregation',
+ caption: :"menus.admin.aggregation",
parent: :mail_and_notifications
menu.push :mail_notifications,
- { controller: '/admin/settings/mail_notifications_settings', action: :show },
+ { controller: "/admin/settings/mail_notifications_settings", action: :show },
if: Proc.new { User.current.admin? },
- caption: :'menus.admin.mail_notification',
+ caption: :"menus.admin.mail_notification",
parent: :mail_and_notifications
menu.push :incoming_mails,
- { controller: '/admin/settings/incoming_mails_settings', action: :show },
+ { controller: "/admin/settings/incoming_mails_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_incoming_emails,
parent: :mail_and_notifications
menu.push :api_and_webhooks,
- { controller: '/admin/settings/api_settings', action: :show },
+ { controller: "/admin/settings/api_settings", action: :show },
if: Proc.new { User.current.admin? },
- caption: :'menus.admin.api_and_webhooks',
- icon: 'relations'
+ caption: :"menus.admin.api_and_webhooks",
+ icon: "relations"
menu.push :api,
- { controller: '/admin/settings/api_settings', action: :show },
+ { controller: "/admin/settings/api_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_api_access_key_type,
parent: :api_and_webhooks
menu.push :authentication,
- { controller: '/admin/settings/authentication_settings', action: :show },
+ { controller: "/admin/settings/authentication_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_authentication,
- icon: 'two-factor-authentication'
+ icon: "two-factor-authentication"
menu.push :authentication_settings,
- { controller: '/admin/settings/authentication_settings', action: :show },
+ { controller: "/admin/settings/authentication_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_setting_plural,
parent: :authentication
menu.push :ldap_authentication,
- { controller: '/ldap_auth_sources', action: 'index' },
+ { controller: "/ldap_auth_sources", action: "index" },
if: Proc.new { User.current.admin? && !OpenProject::Configuration.disable_password_login? },
parent: :authentication,
caption: :label_ldap_auth_source_plural,
- html: { class: 'server_authentication' },
+ html: { class: "server_authentication" },
last: true
menu.push :oauth_applications,
- { controller: '/oauth/applications', action: 'index' },
+ { controller: "/oauth/applications", action: "index" },
if: Proc.new { User.current.admin? },
parent: :authentication,
- caption: :'oauth.application.plural',
- html: { class: 'oauth_applications' }
+ caption: :"oauth.application.plural",
+ html: { class: "oauth_applications" }
menu.push :announcements,
- { controller: '/announcements', action: 'edit' },
+ { controller: "/announcements", action: "edit" },
if: Proc.new { User.current.admin? },
caption: :label_announcement,
- icon: 'news'
+ icon: "news"
menu.push :plugins,
- { controller: '/admin', action: 'plugins' },
+ { controller: "/admin", action: "plugins" },
if: Proc.new { User.current.admin? },
last: true,
- icon: 'plugins'
+ icon: "plugins"
menu.push :backups,
- { controller: '/admin/backups', action: 'show' },
+ { controller: "/admin/backups", action: "show" },
if: Proc.new { OpenProject::Configuration.backup_enabled? && User.current.allowed_globally?(Backup.permission) },
caption: :label_backup,
last: true,
- icon: 'save'
+ icon: "save"
menu.push :info,
- { controller: '/admin', action: 'info' },
+ { controller: "/admin", action: "info" },
if: Proc.new { User.current.admin? },
caption: :label_information_plural,
last: true,
- icon: 'info1'
+ icon: "info1"
menu.push :custom_style,
- { controller: '/custom_styles', action: :show },
+ { controller: "/custom_styles", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_custom_style,
- icon: 'design',
- enterprise_feature: 'define_custom_style'
+ icon: "design",
+ enterprise_feature: "define_custom_style"
menu.push :colors,
- { controller: '/colors', action: 'index' },
+ { controller: "/colors", action: "index" },
if: Proc.new { User.current.admin? },
caption: :label_color_plural,
- icon: 'status'
+ icon: "status"
menu.push :enterprise,
- { controller: '/enterprises', action: :show },
+ { controller: "/enterprises", action: :show },
caption: :label_enterprise_edition,
- icon: 'enterprise-addons',
+ icon: "enterprise-addons",
if: proc { User.current.admin? && OpenProject::Configuration.ee_manager_visible? }
menu.push :admin_costs,
- { controller: '/admin/settings', action: 'show_plugin', id: :costs },
+ { controller: "/admin/settings", action: "show_plugin", id: :costs },
if: Proc.new { User.current.admin? },
caption: :project_module_costs,
- icon: 'budget'
+ icon: "budget"
menu.push :costs_setting,
- { controller: '/admin/settings', action: 'show_plugin', id: :costs },
+ { controller: "/admin/settings", action: "show_plugin", id: :costs },
if: Proc.new { User.current.admin? },
caption: :label_setting_plural,
parent: :admin_costs
menu.push :admin_backlogs,
- { controller: '/backlogs_settings', action: :show },
+ { controller: "/backlogs_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_backlogs,
- icon: 'backlogs'
+ icon: "backlogs"
menu.push :backlogs_settings,
- { controller: '/backlogs_settings', action: :show },
+ { controller: "/backlogs_settings", action: :show },
if: Proc.new { User.current.admin? },
caption: :label_setting_plural,
parent: :admin_backlogs
@@ -556,72 +556,72 @@
Redmine::MenuManager.map :project_menu do |menu|
menu.push :activity,
- { controller: '/activities', action: 'index' },
- if: Proc.new { |p| p.module_enabled?('activity') },
- icon: 'checkmark'
+ { controller: "/activities", action: "index" },
+ if: Proc.new { |p| p.module_enabled?("activity") },
+ icon: "checkmark"
menu.push :activity_filters,
- { controller: '/activities', action: 'index' },
- if: Proc.new { |p| p.module_enabled?('activity') },
+ { controller: "/activities", action: "index" },
+ if: Proc.new { |p| p.module_enabled?("activity") },
parent: :activity,
- partial: 'activities/filters_menu'
+ partial: "activities/filters_menu"
menu.push :roadmap,
- { controller: '/versions', action: 'index' },
+ { controller: "/versions", action: "index" },
if: Proc.new { |p| p.shared_versions.any? },
- icon: 'roadmap'
+ icon: "roadmap"
menu.push :work_packages,
- { controller: '/work_packages', action: 'index' },
+ { controller: "/work_packages", action: "index" },
caption: :label_work_package_plural,
- if: Proc.new { |p| p.module_enabled?('work_package_tracking') },
- icon: 'view-list',
+ if: Proc.new { |p| p.module_enabled?("work_package_tracking") },
+ icon: "view-list",
html: {
- id: 'main-menu-work-packages',
- 'wp-query-menu': 'wp-query-menu'
+ id: "main-menu-work-packages",
+ "wp-query-menu": "wp-query-menu"
}
menu.push :work_packages_query_select,
- { controller: '/work_packages', action: 'index' },
+ { controller: "/work_packages", action: "index" },
parent: :work_packages,
- partial: 'work_packages/menu_query_select',
+ partial: "work_packages/menu_query_select",
last: true,
caption: :label_all_open_wps
menu.push :news,
- { controller: '/news', action: 'index' },
+ { controller: "/news", action: "index" },
caption: :label_news_plural,
- icon: 'news'
+ icon: "news"
menu.push :forums,
- { controller: '/forums', action: 'index', id: nil },
+ { controller: "/forums", action: "index", id: nil },
caption: :label_forum_plural,
- icon: 'ticket-note'
+ icon: "ticket-note"
menu.push :repository,
- { controller: '/repositories', action: :show },
+ { controller: "/repositories", action: :show },
if: Proc.new { |p| p.repository && !p.repository.new_record? },
- icon: 'folder-open'
+ icon: "folder-open"
# Wiki menu items are added by WikiMenuItemHelper
menu.push :members,
- { controller: '/members', action: 'index' },
+ { controller: "/members", action: "index" },
caption: :label_member_plural,
before: :settings,
- icon: 'group'
+ icon: "group"
menu.push :members_menu,
- { controller: '/members', action: 'index' },
+ { controller: "/members", action: "index" },
parent: :members,
- partial: 'members/menus/menu',
+ partial: "members/menus/menu",
caption: :label_member_plural
menu.push :settings,
- { controller: '/projects/settings/general', action: :show },
+ { controller: "/projects/settings/general", action: :show },
caption: :label_project_settings,
last: true,
- icon: 'settings2',
+ icon: "settings2",
allow_deeplink: true
{
@@ -636,7 +636,7 @@
storage: :label_required_disk_storage
}.each do |key, caption|
menu.push :"settings_#{key}",
- { controller: "/projects/settings/#{key}", action: 'show' },
+ { controller: "/projects/settings/#{key}", action: "show" },
caption:,
parent: :settings
end
diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb
index d76e3c19f52e..4098e318acf5 100644
--- a/config/initializers/permissions.rb
+++ b/config/initializers/permissions.rb
@@ -37,7 +37,7 @@
map.permission :archive_project,
{
- 'projects/archive': %i[create]
+ "projects/archive": %i[create]
},
permissible_on: :project,
require: :member
@@ -45,7 +45,7 @@
map.permission :create_backup,
{
admin: %i[index],
- 'admin/backups': %i[delete_token perform_token_reset reset_token show]
+ "admin/backups": %i[delete_token perform_token_reset reset_token show]
},
permissible_on: :global,
require: :loggedin,
@@ -54,7 +54,7 @@
map.permission :create_user,
{
users: %i[index show new create resend_invitation],
- 'users/memberships': %i[create],
+ "users/memberships": %i[create],
admin: %i[index]
},
permissible_on: :global,
@@ -64,7 +64,7 @@
map.permission :manage_user,
{
users: %i[index show edit update change_status change_status_info],
- 'users/memberships': %i[create update destroy],
+ "users/memberships": %i[create update destroy],
admin: %i[index]
},
permissible_on: :global,
@@ -74,7 +74,7 @@
map.permission :manage_placeholder_user,
{
placeholder_users: %i[index show new create edit update deletion_info destroy],
- 'placeholder_users/memberships': %i[create update destroy],
+ "placeholder_users/memberships": %i[create update destroy],
admin: %i[index]
},
permissible_on: :global,
@@ -93,10 +93,10 @@
map.permission :edit_project,
{
- 'projects/settings/general': %i[show],
- 'projects/settings/storage': %i[show],
- 'projects/templated': %i[create destroy],
- 'projects/identifier': %i[show update]
+ "projects/settings/general": %i[show],
+ "projects/settings/storage": %i[show],
+ "projects/templated": %i[create destroy],
+ "projects/identifier": %i[show update]
},
permissible_on: :project,
require: :member,
@@ -104,7 +104,7 @@
map.permission :select_project_modules,
{
- 'projects/settings/modules': %i[show update]
+ "projects/settings/modules": %i[show update]
},
permissible_on: :project,
require: :member
@@ -112,7 +112,7 @@
map.permission :manage_members,
{
members: %i[index new create update destroy autocomplete_for_member menu],
- 'members/menus': %i[show]
+ "members/menus": %i[show]
},
permissible_on: :project,
require: :member,
@@ -122,14 +122,14 @@
map.permission :view_members,
{
members: %i[index menu],
- 'members/menus': %i[show]
+ "members/menus": %i[show]
},
permissible_on: :project,
contract_actions: { members: %i[read] }
map.permission :manage_versions,
{
- 'projects/settings/versions': [:show],
+ "projects/settings/versions": [:show],
versions: %i[new create edit update close_completed destroy]
},
permissible_on: :project,
@@ -137,14 +137,14 @@
map.permission :manage_types,
{
- 'projects/settings/types': %i[show update]
+ "projects/settings/types": %i[show update]
},
permissible_on: :project,
require: :member
map.permission :select_custom_fields,
{
- 'projects/settings/custom_fields': %i[show update]
+ "projects/settings/custom_fields": %i[show update]
},
permissible_on: :project,
require: :member
@@ -179,7 +179,7 @@
journals: %i[index],
work_packages: %i[show index],
work_packages_api: [:get],
- 'work_packages/reports': %i[report report_details]
+ "work_packages/reports": %i[report report_details]
},
permissible_on: %i[work_package project],
contract_actions: { work_packages: %i[read] }
@@ -192,7 +192,7 @@
wpt.permission :edit_work_packages,
{
- 'work_packages/bulk': %i[edit update]
+ "work_packages/bulk": %i[edit update]
},
permissible_on: %i[work_package project],
require: :member,
@@ -200,7 +200,7 @@
contract_actions: { work_packages: %i[update] }
wpt.permission :move_work_packages,
- { 'work_packages/moves': %i[new create] },
+ { "work_packages/moves": %i[new create] },
permissible_on: :project,
require: :loggedin,
dependencies: :view_work_packages,
@@ -242,7 +242,7 @@
# WorkPackage categories
wpt.permission :manage_categories,
{
- 'projects/settings/categories': [:show],
+ "projects/settings/categories": [:show],
categories: %i[new create edit update destroy]
},
permissible_on: :project,
@@ -258,7 +258,7 @@
wpt.permission :delete_work_packages,
{
work_packages: :destroy,
- 'work_packages/bulk': :destroy
+ "work_packages/bulk": :destroy
},
permissible_on: :project,
require: :member,
@@ -304,8 +304,8 @@
map.permission :share_work_packages,
{
- 'work_packages/shares': %i[index create destroy update resend_invite],
- 'work_packages/shares/bulk': %i[update destroy]
+ "work_packages/shares": %i[index create destroy update resend_invite],
+ "work_packages/shares/bulk": %i[update destroy]
},
permissible_on: :project,
dependencies: %i[edit_work_packages view_shared_work_packages],
@@ -313,7 +313,7 @@
map.permission :view_shared_work_packages,
{
- 'work_packages/shares': %i[index]
+ "work_packages/shares": %i[index]
},
permissible_on: :project,
require: :member,
@@ -350,13 +350,13 @@
news.permission :manage_news,
{
news: %i[new create edit update destroy preview],
- 'news/comments': [:destroy]
+ "news/comments": [:destroy]
},
permissible_on: :project,
require: :member
news.permission :comment_news,
- { 'news/comments': :create },
+ { "news/comments": :create },
permissible_on: :project
end
@@ -429,7 +429,7 @@
repo.permission :manage_repository,
{
repositories: %i[edit create update committers destroy_info destroy],
- 'projects/settings/repository': :show
+ "projects/settings/repository": :show
},
permissible_on: :project,
require: :member
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 91ab4632f334..7703ace3f93d 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -35,7 +35,8 @@ en:
admin:
plugins:
- no_results_title_text: There are currently no plugins available.
+ no_results_title_text: There are currently no plugins installed.
+ no_results_content_text: See our integrations and plugins page for more information.
custom_styles:
color_theme: "Color theme"
color_theme_custom: "(Custom)"
diff --git a/config/routes.rb b/config/routes.rb
index bbf68291a86a..89c4963a26d3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -27,74 +27,74 @@
#++
Rails.application.routes.draw do
- root to: 'homescreen#index', as: 'home'
- rails_relative_url_root = OpenProject::Configuration['rails_relative_url_root'] || ''
+ root to: "homescreen#index", as: "home"
+ rails_relative_url_root = OpenProject::Configuration["rails_relative_url_root"] || ""
# Route for error pages
- get '/404', to: "errors#not_found"
- get '/422', to: "errors#unacceptable"
- get '/500', to: "errors#internal_error"
+ get "/404", to: "errors#not_found"
+ get "/422", to: "errors#unacceptable"
+ get "/500", to: "errors#internal_error"
# Route for health_checks
- get '/health_check' => 'ok_computer/ok_computer#show', check: 'web'
+ get "/health_check" => "ok_computer/ok_computer#show", check: "web"
# Override the default `all` checks route to return the full check
- get '/health_checks/all' => 'ok_computer/ok_computer#show', check: 'full'
+ get "/health_checks/all" => "ok_computer/ok_computer#show", check: "full"
mount OkComputer::Engine, at: "/health_checks"
- get "/api/docs" => 'api_docs#index'
+ get "/api/docs" => "api_docs#index"
# Redirect deprecated issue links to new work packages uris
- get '/issues(/)' => redirect("#{rails_relative_url_root}/work_packages")
+ get "/issues(/)" => redirect("#{rails_relative_url_root}/work_packages")
# The URI.escape doesn't escape / unless you ask it to.
# see https://github.com/rails/rails/issues/5688
- get '/issues/*rest' => redirect { |params, _req|
+ get "/issues/*rest" => redirect { |params, _req|
"#{rails_relative_url_root}/work_packages/#{URI::RFC2396_Parser.new.escape(params[:rest])}"
}
# Respond with 410 gone for APIV2 calls
- match '/api/v2(/*unmatched_route)', to: proc { [410, {}, ['']] }, via: :all
- match '/assets/compiler.js.map', to: proc { [404, {}, ['']] }, via: :all
+ match "/api/v2(/*unmatched_route)", to: proc { [410, {}, [""]] }, via: :all
+ match "/assets/compiler.js.map", to: proc { [404, {}, [""]] }, via: :all
# Redirect wp short url for work packages to full URL
- get '/wp(/)' => redirect("#{rails_relative_url_root}/work_packages")
- get '/wp/*rest' => redirect { |params, _req|
+ get "/wp(/)" => redirect("#{rails_relative_url_root}/work_packages")
+ get "/wp/*rest" => redirect { |params, _req|
"#{rails_relative_url_root}/work_packages/#{URI::RFC2396_Parser.new.escape(params[:rest])}"
}
# Add catch method for Rack OmniAuth to allow route helpers
# Note: This renders a 404 in rails but is caught by omniauth in Rack before
- get '/auth/failure', to: 'account#omniauth_failure'
- get '/auth/:provider', to: proc { [404, {}, ['']] }, as: 'omniauth_start'
- match '/auth/:provider/callback', to: 'account#omniauth_login', as: 'omniauth_login', via: %i[get post]
+ get "/auth/failure", to: "account#omniauth_failure"
+ get "/auth/:provider", to: proc { [404, {}, [""]] }, as: "omniauth_start"
+ match "/auth/:provider/callback", to: "account#omniauth_login", as: "omniauth_login", via: %i[get post]
# In case assets are actually delivered by a node server (e.g. in test env)
# forward requests to the proxy
if FrontendAssetHelper.assets_proxied?
- match '/assets/frontend/*appendix',
+ match "/assets/frontend/*appendix",
to: redirect("#{FrontendAssetHelper.cli_proxy}/assets/frontend/%{appendix}", status: 307),
format: false,
via: :all
end
- scope controller: 'account' do
- get '/account/force_password_change', action: 'force_password_change'
- post '/account/change_password', action: 'change_password'
- match '/account/lost_password', action: 'lost_password', via: %i[get post]
- match '/account/register', action: 'register', via: %i[get post patch]
- get '/account/activate', action: 'activate'
+ scope controller: "account" do
+ get "/account/force_password_change", action: "force_password_change"
+ post "/account/change_password", action: "change_password"
+ match "/account/lost_password", action: "lost_password", via: %i[get post]
+ match "/account/register", action: "register", via: %i[get post patch]
+ get "/account/activate", action: "activate"
- match '/login', action: 'login', as: 'signin', via: %i[get post]
- get '/login/internal', action: 'internal_login', as: 'internal_signin'
- get '/logout', action: 'logout', as: 'signout'
+ match "/login", action: "login", as: "signin", via: %i[get post]
+ get "/login/internal", action: "internal_login", as: "internal_signin"
+ get "/logout", action: "logout", as: "signout"
- get '/sso', action: 'auth_source_sso_failed', as: 'sso_failure'
+ get "/sso", action: "auth_source_sso_failed", as: "sso_failure"
- get '/login/:stage/failure', action: 'stage_failure', as: 'stage_failure'
- get '/login/:stage/:secret', action: 'stage_success', as: 'stage_success'
+ get "/login/:stage/failure", action: "stage_failure", as: "stage_failure"
+ get "/login/:stage/:secret", action: "stage_success", as: "stage_success"
- get '/account/consent', action: 'consent', as: 'account_consent'
- get '/account/decline_consent', action: 'decline_consent', as: 'account_decline_consent'
- post '/account/confirm_consent', action: 'confirm_consent', as: 'account_confirm_consent'
+ get "/account/consent", action: "consent", as: "account_consent"
+ get "/account/decline_consent", action: "decline_consent", as: "account_decline_consent"
+ post "/account/confirm_consent", action: "confirm_consent", as: "account_confirm_consent"
end
# Because of https://github.com/intridea/grape/pull/853/files this has to be
@@ -102,7 +102,7 @@
# returned for all routes for which the v3 has also resources. Grape does
# remove the prefix (v3) before checking whether the method is supported. I
# don't understand why that should make sense.
- mount API::Root => '/api'
+ mount API::Root => "/api"
# OAuth authorization routes
use_doorkeeper do
@@ -110,45 +110,45 @@
skip_controllers :applications, :authorized_applications
end
- get '/roles/workflow/:id/:role_id/:type_id' => 'roles#workflow'
+ get "/roles/workflow/:id/:role_id/:type_id" => "roles#workflow"
- get '/types/:id/edit/:tab' => "types#edit",
+ get "/types/:id/edit/:tab" => "types#edit",
as: "edit_type_tab"
- match '/types/:id/update/:tab' => "types#update",
+ match "/types/:id/update/:tab" => "types#update",
as: "update_type_tab",
via: %i[post patch]
resources :types do
- post 'move/:id', action: 'move', on: :collection
+ post "move/:id", action: "move", on: :collection
end
resources :statuses, except: :show do
collection do
- post 'update_work_package_done_ratio'
+ post "update_work_package_done_ratio"
end
end
- get 'custom_style/:digest/logo/:filename' => 'custom_styles#logo_download',
- as: 'custom_style_logo',
+ get "custom_style/:digest/logo/:filename" => "custom_styles#logo_download",
+ as: "custom_style_logo",
constraints: { filename: /[^\/]*/ }
- get 'custom_style/:digest/export_logo/:filename' => 'custom_styles#export_logo_download',
- as: 'custom_style_export_logo',
+ get "custom_style/:digest/export_logo/:filename" => "custom_styles#export_logo_download",
+ as: "custom_style_export_logo",
constraints: { filename: /[^\/]*/ }
- get 'custom_style/:digest/export_cover/:filename' => 'custom_styles#export_cover_download',
- as: 'custom_style_export_cover',
+ get "custom_style/:digest/export_cover/:filename" => "custom_styles#export_cover_download",
+ as: "custom_style_export_cover",
constraints: { filename: /[^\/]*/ }
- get 'custom_style/:digest/favicon/:filename' => 'custom_styles#favicon_download',
- as: 'custom_style_favicon',
+ get "custom_style/:digest/favicon/:filename" => "custom_styles#favicon_download",
+ as: "custom_style_favicon",
constraints: { filename: /[^\/]*/ }
- get 'custom_style/:digest/touch-icon/:filename' => 'custom_styles#touch_icon_download',
- as: 'custom_style_touch_icon',
+ get "custom_style/:digest/touch-icon/:filename" => "custom_styles#touch_icon_download",
+ as: "custom_style_touch_icon",
constraints: { filename: /[^\/]*/ }
- get 'highlighting/styles(/:version_tag)' => 'highlighting#styles',
- as: 'highlighting_css_styles'
+ get "highlighting/styles(/:version_tag)" => "highlighting#styles",
+ as: "highlighting_css_styles"
resources :custom_fields, except: :show do
member do
@@ -158,25 +158,25 @@
end
end
- get '(projects/:project_id)/search' => 'search#index', as: 'search'
+ get "(projects/:project_id)/search" => "search#index", as: "search"
# only providing routes for journals when there are multiple subclasses of journals
# all subclasses will look for the journals routes
resources :journals, only: :index do
- get 'diff/:field', action: :diff, on: :member, as: 'diff'
+ get "diff/:field", action: :diff, on: :member, as: "diff"
end
# REVIEW: review those wiki routes
- scope 'projects/:project_id/wiki/:id' do
+ scope "projects/:project_id/wiki/:id" do
resource :wiki_menu_item, only: %i[edit update]
end
# generic route for adding/removing watchers.
# Models declared as acts_as_watchable will be automatically added to
# OpenProject::Acts::Watchable::Routes.watched
- scope ':object_type/:object_id', constraints: OpenProject::Acts::Watchable::Routes do
- post '/watch' => 'watchers#watch'
- delete '/unwatch' => 'watchers#unwatch'
+ scope ":object_type/:object_id", constraints: OpenProject::Acts::Watchable::Routes do
+ post "/watch" => "watchers#watch"
+ delete "/unwatch" => "watchers#unwatch"
end
namespace :projects do
@@ -185,32 +185,32 @@
end
resources :projects, except: %i[show edit create update] do
- scope module: 'projects' do
- namespace 'settings' do
- resource :general, only: %i[show], controller: 'general'
+ scope module: "projects" do
+ namespace "settings" do
+ resource :general, only: %i[show], controller: "general"
resource :modules, only: %i[show update]
resource :types, only: %i[show update]
resource :custom_fields, only: %i[show update]
- resource :repository, only: %i[show], controller: 'repository'
+ resource :repository, only: %i[show], controller: "repository"
resource :versions, only: %i[show]
resource :categories, only: %i[show update]
- resource :storage, only: %i[show], controller: 'storage'
+ resource :storage, only: %i[show], controller: "storage"
end
- resource :templated, only: %i[create destroy], controller: 'templated'
- resource :archive, only: %i[create destroy], controller: 'archive'
- resource :identifier, only: %i[show update], controller: 'identifier'
+ resource :templated, only: %i[create destroy], controller: "templated"
+ resource :archive, only: %i[create destroy], controller: "archive"
+ resource :identifier, only: %i[show update], controller: "identifier"
end
member do
- get "settings", to: redirect('projects/%{id}/settings/general/')
+ get "settings", to: redirect("projects/%{id}/settings/general/")
get :copy
patch :types
# Destroy uses a get request to prompt the user before the actual DELETE request
- get :destroy_info, as: 'confirm_destroy'
+ get :destroy_info, as: "confirm_destroy"
end
resources :versions, only: %i[new create] do
@@ -222,7 +222,7 @@
# this is only another name for versions#index
# For nice "road in the url for the index action
# this could probably be rewritten with a resource as: 'roadmap'
- get '/roadmap' => 'versions#index'
+ get "/roadmap" => "versions#index"
resources :news, only: %i[index new create]
@@ -233,25 +233,25 @@
constraints: { id: /([^\/]+(?=\.markdown)|[^\/]+)/ },
except: %i[index create] do
collection do
- post '/new' => 'wiki#create', as: 'create'
+ post "/new" => "wiki#create", as: "create"
get :export
- get '/index' => 'wiki#index'
+ get "/index" => "wiki#index"
get :menu
end
member do
- get '/new' => 'wiki#new_child', as: 'new_child'
- get '/diff/:version/vs/:version_from' => 'wiki#diff', as: 'wiki_diff_compare'
- get '/diff(/:version)' => 'wiki#diff', as: 'wiki_diff'
- get '/annotate/:version' => 'wiki#annotate', as: 'wiki_annotate'
- get '/toc' => 'wiki#index'
+ get "/new" => "wiki#new_child", as: "new_child"
+ get "/diff/:version/vs/:version_from" => "wiki#diff", as: "wiki_diff_compare"
+ get "/diff(/:version)" => "wiki#diff", as: "wiki_diff"
+ get "/annotate/:version" => "wiki#annotate", as: "wiki_annotate"
+ get "/toc" => "wiki#index"
match :rename, via: %i[get patch]
- get :parent_page, action: 'edit_parent_page'
- patch :parent_page, action: 'update_parent_page'
+ get :parent_page, action: "edit_parent_page"
+ patch :parent_page, action: "update_parent_page"
get :history
post :protect
- get :select_main_menu_item, to: 'wiki_menu_items#select_main_menu_item'
- post :replace_main_menu_item, to: 'wiki_menu_items#replace_main_menu_item'
+ get :select_main_menu_item, to: "wiki_menu_items#select_main_menu_item"
+ post :replace_main_menu_item, to: "wiki_menu_items#replace_main_menu_item"
get :menu
end
end
@@ -260,24 +260,24 @@
# it is necessary to define the show action later
# than any other route as it otherwise would
# work as a catchall for everything under /wiki
- get 'wiki' => 'wiki#show'
+ get "wiki" => "wiki#show"
resources :work_packages, only: [] do
collection do
- get '/report/:detail' => 'work_packages/reports#report_details'
- get '/report' => 'work_packages/reports#report'
+ get "/report/:detail" => "work_packages/reports#report_details"
+ get "/report" => "work_packages/reports#report"
end
# states managed by client-side routing on work_package#index
- get '(/*state)' => 'work_packages#index', on: :collection, as: ''
- get '/create_new' => 'work_packages#index', on: :collection, as: 'new_split'
- get '/new' => 'work_packages#index', on: :collection, as: 'new'
+ get "(/*state)" => "work_packages#index", on: :collection, as: ""
+ get "/create_new" => "work_packages#index", on: :collection, as: "new_split"
+ get "/new" => "work_packages#index", on: :collection, as: "new"
# state for show view in project context
- get '(/*state)' => 'work_packages#show', on: :member, as: ''
+ get "(/*state)" => "work_packages#show", on: :member, as: ""
end
- resources :activity, :activities, only: :index, controller: 'activities' do
+ resources :activity, :activities, only: :index, controller: "activities" do
collection do
get :menu
end
@@ -303,7 +303,7 @@
resource :menu, only: %[show]
end
- resource :repository, controller: 'repositories', except: [:new] do
+ resource :repository, controller: "repositories", except: [:new] do
# Destroy uses a get request to prompt the user before the actual DELETE request
get :destroy_info
get :committers
@@ -311,36 +311,36 @@
get :graph
get :revisions
- get '/statistics', action: :stats, as: 'stats'
+ get "/statistics", action: :stats, as: "stats"
- get '(/revisions/:rev)/diff.:format', action: :diff
- get '(/revisions/:rev)/diff(/*repo_path)',
+ get "(/revisions/:rev)/diff.:format", action: :diff
+ get "(/revisions/:rev)/diff(/*repo_path)",
action: :diff,
- format: 'html',
- constraints: { rev: /[\w0-9.\-_]+/, repo_path: /.*/ }
+ format: "html",
+ constraints: { rev: /[\w.\-]+/, repo_path: /.*/ }
- get '(/revisions/:rev)/:format/*repo_path',
+ get "(/revisions/:rev)/:format/*repo_path",
action: :entry,
format: /raw/,
- rev: /[\w0-9.\-_]+/
+ rev: /[\w.\-]+/
%w{diff annotate changes entry browse}.each do |action|
get "(/revisions/:rev)/#{action}(/*repo_path)",
- format: 'html',
+ format: "html",
action:,
- constraints: { rev: /[\w0-9.\-_]+/, repo_path: /.*/ },
+ constraints: { rev: /[\w.\-]+/, repo_path: /.*/ },
as: "#{action}_revision"
end
- get '/revision(/:rev)', rev: /[\w0-9.\-_]+/,
+ get "/revision(/:rev)", rev: /[\w.\-]+/,
action: :revision,
- as: 'show_revision'
+ as: "show_revision"
- get '(/revisions/:rev)(/*repo_path)',
+ get "(/revisions/:rev)(/*repo_path)",
action: :show,
- format: 'html',
- constraints: { rev: /[\w0-9.\-_]+/, repo_path: /.*/ },
- as: 'show_revisions_path'
+ format: "html",
+ constraints: { rev: /[\w.\-]+/, repo_path: /.*/ },
+ as: "show_revisions_path"
end
end
@@ -352,52 +352,52 @@
end
end
- scope 'admin' do
+ scope "admin" do
resource :announcements, only: %i[edit update]
constraints(Constraints::Enterprise) do
resource :enterprise, only: %i[show create destroy]
- scope controller: 'enterprises' do
- post 'enterprise/save_trial_key' => 'enterprises#save_trial_key'
- delete 'enterprise/delete_trial_key' => 'enterprises#delete_trial_key'
+ scope controller: "enterprises" do
+ post "enterprise/save_trial_key" => "enterprises#save_trial_key"
+ delete "enterprise/delete_trial_key" => "enterprises#delete_trial_key"
end
end
resources :enumerations do
- post 'move/:id', action: 'move', on: :collection
+ post "move/:id", action: "move", on: :collection
end
- delete 'design/logo' => 'custom_styles#logo_delete', as: 'custom_style_logo_delete'
- delete 'design/export_logo' => 'custom_styles#export_logo_delete', as: 'custom_style_export_logo_delete'
- delete 'design/export_cover' => 'custom_styles#export_cover_delete', as: 'custom_style_export_cover_delete'
- delete 'design/favicon' => 'custom_styles#favicon_delete', as: 'custom_style_favicon_delete'
- delete 'design/touch_icon' => 'custom_styles#touch_icon_delete', as: 'custom_style_touch_icon_delete'
- get 'design/upsale' => 'custom_styles#upsale', as: 'custom_style_upsale'
- post 'design/colors' => 'custom_styles#update_colors', as: 'update_design_colors'
- post 'design/themes' => 'custom_styles#update_themes', as: 'update_design_themes'
- post 'design/export_cover_text_color' => 'custom_styles#update_export_cover_text_color',
- as: 'update_custom_style_export_cover_text_color'
+ delete "design/logo" => "custom_styles#logo_delete", as: "custom_style_logo_delete"
+ delete "design/export_logo" => "custom_styles#export_logo_delete", as: "custom_style_export_logo_delete"
+ delete "design/export_cover" => "custom_styles#export_cover_delete", as: "custom_style_export_cover_delete"
+ delete "design/favicon" => "custom_styles#favicon_delete", as: "custom_style_favicon_delete"
+ delete "design/touch_icon" => "custom_styles#touch_icon_delete", as: "custom_style_touch_icon_delete"
+ get "design/upsale" => "custom_styles#upsale", as: "custom_style_upsale"
+ post "design/colors" => "custom_styles#update_colors", as: "update_design_colors"
+ post "design/themes" => "custom_styles#update_themes", as: "update_design_themes"
+ post "design/export_cover_text_color" => "custom_styles#update_export_cover_text_color",
+ as: "update_custom_style_export_cover_text_color"
- resource :custom_style, only: %i[update show create], path: 'design'
+ resource :custom_style, only: %i[update show create], path: "design"
resources :attribute_help_texts, only: %i(index new create edit update destroy) do
- get :upsale, to: 'attribute_help_texts#upsale', on: :collection, as: :upsale
+ get :upsale, to: "attribute_help_texts#upsale", on: :collection, as: :upsale
end
resources :groups, except: %i[show] do
member do
# this should be put into it's own resource
- post '/members' => 'groups#add_users', as: 'members_of'
- delete '/members/:user_id' => 'groups#remove_user', as: 'member_of'
+ post "/members" => "groups#add_users", as: "members_of"
+ delete "/members/:user_id" => "groups#remove_user", as: "member_of"
# this should be put into it's own resource
- patch '/memberships/:membership_id' => 'groups#edit_membership', as: 'membership_of'
- put '/memberships/:membership_id' => 'groups#edit_membership'
- delete '/memberships/:membership_id' => 'groups#destroy_membership'
- post '/memberships' => 'groups#create_memberships', as: 'memberships_of'
+ patch "/memberships/:membership_id" => "groups#edit_membership", as: "membership_of"
+ put "/memberships/:membership_id" => "groups#edit_membership"
+ delete "/memberships/:membership_id" => "groups#destroy_membership"
+ post "/memberships" => "groups#create_memberships", as: "memberships_of"
end
end
resources :roles, except: %i[show] do
collection do
- put '/' => 'roles#bulk_update'
+ put "/" => "roles#bulk_update"
get :report
end
end
@@ -422,37 +422,37 @@
patch tab[:name], controller: tab[:controller], action: :update, as: "update_#{tab[:name]}"
end
- resource :authentication, controller: '/admin/settings/authentication_settings', only: %i[show update]
- resource :attachments, controller: '/admin/settings/attachments_settings', only: %i[show update]
- resource :virus_scanning, controller: '/admin/settings/virus_scanning_settings', only: %i[show update] do
+ resource :authentication, controller: "/admin/settings/authentication_settings", only: %i[show update]
+ resource :attachments, controller: "/admin/settings/attachments_settings", only: %i[show update]
+ resource :virus_scanning, controller: "/admin/settings/virus_scanning_settings", only: %i[show update] do
collection do
get :av_form
end
end
- resource :incoming_mails, controller: '/admin/settings/incoming_mails_settings', only: %i[show update]
- resource :aggregation, controller: '/admin/settings/aggregation_settings', only: %i[show update]
- resource :mail_notifications, controller: '/admin/settings/mail_notifications_settings', only: %i[show update]
- resource :api, controller: '/admin/settings/api_settings', only: %i[show update]
- resource :work_packages, controller: '/admin/settings/work_packages_settings', only: %i[show update]
- resource :working_days, controller: '/admin/settings/working_days_settings', only: %i[show update]
- resource :users, controller: '/admin/settings/users_settings', only: %i[show update]
- resource :date_format, controller: '/admin/settings/date_format_settings', only: %i[show update]
- resource :icalendar, controller: '/admin/settings/icalendar_settings', only: %i[show update]
+ resource :incoming_mails, controller: "/admin/settings/incoming_mails_settings", only: %i[show update]
+ resource :aggregation, controller: "/admin/settings/aggregation_settings", only: %i[show update]
+ resource :mail_notifications, controller: "/admin/settings/mail_notifications_settings", only: %i[show update]
+ resource :api, controller: "/admin/settings/api_settings", only: %i[show update]
+ resource :work_packages, controller: "/admin/settings/work_packages_settings", only: %i[show update]
+ resource :working_days, controller: "/admin/settings/working_days_settings", only: %i[show update]
+ resource :users, controller: "/admin/settings/users_settings", only: %i[show update]
+ resource :date_format, controller: "/admin/settings/date_format_settings", only: %i[show update]
+ resource :icalendar, controller: "/admin/settings/icalendar_settings", only: %i[show update]
# Redirect /settings to general settings
- get '/', to: redirect('/admin/settings/general')
+ get "/", to: redirect("/admin/settings/general")
# Plugin settings
- get 'plugin/:id', action: :show_plugin
- post 'plugin/:id', action: :update_plugin
+ get "plugin/:id", action: :show_plugin
+ post "plugin/:id", action: :update_plugin
end
resources :quarantined_attachments,
- controller: '/admin/attachments/quarantined_attachments',
+ controller: "/admin/attachments/quarantined_attachments",
only: %i[index destroy]
- resource :backups, controller: '/admin/backups', only: %i[show] do
+ resource :backups, controller: "/admin/backups", only: %i[show] do
collection do
get :reset_token
post :reset_token, action: :perform_token_reset
@@ -465,48 +465,48 @@
resource :workflows, only: %i[edit update show] do
member do
# We should fix this crappy routing (split up and rename controller methods)
- match 'copy', action: 'copy', via: %i[get post]
+ match "copy", action: "copy", via: %i[get post]
end
end
namespace :work_packages do
- match 'auto_complete' => 'auto_completes#index', via: %i[get post]
- resource :bulk, controller: 'bulk', only: %i[edit update destroy]
+ match "auto_complete" => "auto_completes#index", via: %i[get post]
+ resource :bulk, controller: "bulk", only: %i[edit update destroy]
# FIXME: this is kind of evil!! We need to remove this soonest and
# cover the functionality. Route is being used in work-package-service.js:331
- get '/bulk' => 'bulk#destroy'
+ get "/bulk" => "bulk#destroy"
resources :shares, only: %i[destroy update]
end
resources :work_packages, only: [:index] do
# move bulk of wps
- get 'move/new' => 'work_packages/moves#new', on: :collection, as: 'new_move'
- post 'move' => 'work_packages/moves#create', on: :collection, as: 'move'
+ get "move/new" => "work_packages/moves#new", on: :collection, as: "new_move"
+ post "move" => "work_packages/moves#create", on: :collection, as: "move"
# move individual wp
- resource :move, controller: 'work_packages/moves', only: %i[new create]
+ resource :move, controller: "work_packages/moves", only: %i[new create]
# states managed by client-side routing on work_package#index
- get 'details/*state' => 'work_packages#index', on: :collection, as: :details
+ get "details/*state" => "work_packages#index", on: :collection, as: :details
# Rails managed sharing route
- resources :shares, controller: 'work_packages/shares', only: %i[index create] do
+ resources :shares, controller: "work_packages/shares", only: %i[index create] do
member do
- post 'resend_invite' => 'work_packages/shares#resend_invite'
+ post "resend_invite" => "work_packages/shares#resend_invite"
end
collection do
- resource :bulk, controller: 'work_packages/shares/bulk', only: %i[update destroy], as: :shares_bulk
+ resource :bulk, controller: "work_packages/shares/bulk", only: %i[update destroy], as: :shares_bulk
end
end
# states managed by client-side (angular) routing on work_package#show
- get '/' => 'work_packages#index', on: :collection, as: 'index'
- get '/create_new' => 'work_packages#index', on: :collection, as: 'new_split'
- get '/new' => 'work_packages#index', on: :collection, as: 'new', state: 'new'
+ get "/" => "work_packages#index", on: :collection, as: "index"
+ get "/create_new" => "work_packages#index", on: :collection, as: "new_split"
+ get "/new" => "work_packages#index", on: :collection, as: "new", state: "new"
# We do not want to match the work package export routes
- get '(/*state)' => 'work_packages#show', on: :member, as: '', constraints: { id: /\d+/ }
- get '/share_upsale' => 'work_packages#index', on: :collection, as: 'share_upsale'
- get '/edit' => 'work_packages#show', on: :member, as: 'edit'
+ get "(/*state)" => "work_packages#show", on: :member, as: "", constraints: { id: /\d+/ }
+ get "/share_upsale" => "work_packages#index", on: :collection, as: "share_upsale"
+ get "/edit" => "work_packages#show", on: :member, as: "edit"
end
resources :versions, only: %i[show edit update destroy] do
@@ -515,18 +515,18 @@
end
end
- resources :activity, :activities, only: :index, controller: 'activities' do
+ resources :activity, :activities, only: :index, controller: "activities" do
collection do
get :menu
end
end
resources :users, constraints: { id: /(\d+|me)/ }, except: :edit do
- resources :memberships, controller: 'users/memberships', only: %i[update create destroy]
+ resources :memberships, controller: "users/memberships", only: %i[update create destroy]
member do
- get '/edit(/:tab)' => 'users#edit', as: 'edit'
- get '/change_status/:change_action' => 'users#change_status_info', as: 'change_status_info'
+ get "/edit(/:tab)" => "users#edit", as: "edit"
+ get "/change_status/:change_action" => "users#change_status_info", as: "change_status_info"
post :change_status
post :resend_invitation
get :deletion_info
@@ -534,10 +534,10 @@
end
resources :placeholder_users, except: :edit do
- resources :memberships, controller: 'placeholder_users/memberships', only: %i[update create destroy]
+ resources :memberships, controller: "placeholder_users/memberships", only: %i[update create destroy]
member do
- get '/edit(/:tab)' => 'placeholder_users#edit', as: 'edit'
+ get "/edit(/:tab)" => "placeholder_users#edit", as: "edit"
get :deletion_info
end
end
@@ -546,33 +546,33 @@
resources :groups, only: %i[show], as: :show_group
resources :forums, only: [] do
- resources :topics, controller: 'messages', except: [:index], shallow: true do
+ resources :topics, controller: "messages", except: [:index], shallow: true do
member do
get :quote
- post :reply, as: 'reply_to'
+ post :reply, as: "reply_to"
end
end
end
resources :news, only: %i[index destroy update edit show] do
- resources :comments, controller: 'news/comments', only: %i[create destroy], shallow: true
+ resources :comments, controller: "news/comments", only: %i[create destroy], shallow: true
end
# redirect for backwards compatibility
- scope 'attachments',
+ scope "attachments",
constraints: { id: /\d+/, filename: /[^\/]*/ },
format: false do
- get '/download/:id/:filename',
+ get "/download/:id/:filename",
to: redirect("#{rails_relative_url_root}/attachments/%{id}/%{filename}")
- get '/download/:id',
+ get "/download/:id",
to: redirect("#{rails_relative_url_root}/attachments/%{id}")
- scope ':id' do
- get '(/:filename)',
+ scope ":id" do
+ get "(/:filename)",
to: redirect("#{rails_relative_url_root}/api/v3/attachments/%{id}/content")
- delete '',
+ delete "",
to: redirect("#{rails_relative_url_root}/api/v3/attachments/%{id}")
end
end
@@ -584,45 +584,45 @@
end
end
- scope controller: 'sys' do
- match '/sys/repo_auth', action: 'repo_auth', via: %i[get post]
- get '/sys/projects', action: 'projects'
- get '/sys/fetch_changesets', action: 'fetch_changesets'
- get '/sys/projects/:id/repository/update_storage', action: 'update_required_storage'
+ scope controller: "sys" do
+ match "/sys/repo_auth", action: "repo_auth", via: %i[get post]
+ get "/sys/projects", action: "projects"
+ get "/sys/fetch_changesets", action: "fetch_changesets"
+ get "/sys/projects/:id/repository/update_storage", action: "update_required_storage"
end
# alternate routes for the current user
- scope 'my' do
- get '/deletion_info' => 'users#deletion_info', as: 'delete_my_account_info'
- post '/oauth/revoke_application/:application_id' => 'oauth/grants#revoke_application', as: 'revoke_my_oauth_application'
- delete '/storage_token/:id' => 'my#delete_storage_token', as: 'storage_token_delete'
+ scope "my" do
+ get "/deletion_info" => "users#deletion_info", as: "delete_my_account_info"
+ post "/oauth/revoke_application/:application_id" => "oauth/grants#revoke_application", as: "revoke_my_oauth_application"
+ delete "/storage_token/:id" => "my#delete_storage_token", as: "storage_token_delete"
- resources :sessions, controller: 'my/sessions', as: 'my_sessions', only: %i[index show destroy]
- resources :auto_login_tokens, controller: 'my/auto_login_tokens', as: 'my_auto_login_tokens', only: %i[destroy]
+ resources :sessions, controller: "my/sessions", as: "my_sessions", only: %i[index show destroy]
+ resources :auto_login_tokens, controller: "my/auto_login_tokens", as: "my_auto_login_tokens", only: %i[destroy]
end
- scope controller: 'my' do
- get '/my/password', action: 'password'
- post '/my/change_password', action: 'change_password'
+ scope controller: "my" do
+ get "/my/password", action: "password"
+ post "/my/change_password", action: "change_password"
- get '/my/account', action: 'account'
- get '/my/settings', action: 'settings'
- get '/my/notifications', action: 'notifications'
- get '/my/reminders', action: 'reminders'
+ get "/my/account", action: "account"
+ get "/my/settings", action: "settings"
+ get "/my/notifications", action: "notifications"
+ get "/my/reminders", action: "reminders"
- patch '/my/account', action: 'update_account'
- patch '/my/settings', action: 'update_settings'
+ patch "/my/account", action: "update_account"
+ patch "/my/settings", action: "update_settings"
- post '/my/generate_rss_key', action: 'generate_rss_key'
- delete '/my/revoke_rss_key', action: 'revoke_rss_key'
- post '/my/generate_api_key', action: 'generate_api_key'
- delete '/my/revoke_api_key', action: 'revoke_api_key'
- delete '/my/revoke_ical_token', action: 'revoke_ical_token'
- get '/my/access_token', action: 'access_token'
+ post "/my/generate_rss_key", action: "generate_rss_key"
+ delete "/my/revoke_rss_key", action: "revoke_rss_key"
+ post "/my/generate_api_key", action: "generate_api_key"
+ delete "/my/revoke_api_key", action: "revoke_api_key"
+ delete "/my/revoke_ical_token", action: "revoke_ical_token"
+ get "/my/access_token", action: "access_token"
end
- scope controller: 'onboarding' do
- patch 'user_settings', action: 'user_settings'
+ scope controller: "onboarding" do
+ patch "user_settings", action: "user_settings"
end
resources :colors do
@@ -633,18 +633,18 @@
end
end
- get '/robots' => 'homescreen#robots', defaults: { format: :txt }
+ get "/robots" => "homescreen#robots", defaults: { format: :txt }
- root to: 'account#login'
+ root to: "account#login"
scope :notifications do
- get '(/*state)', to: 'angular#notifications_layout', as: :notifications_center
+ get "(/*state)", to: "angular#notifications_layout", as: :notifications_center
end
# OAuthClient needs a "callback" URL that Nextcloud calls with a "code" (see OAuth2 RFC)
- scope 'oauth_clients/:oauth_client_id' do
- get 'callback', controller: 'oauth_clients', action: :callback
- get 'ensure_connection', controller: 'oauth_clients', action: :ensure_connection, as: 'oauth_clients_ensure_connection'
+ scope "oauth_clients/:oauth_client_id" do
+ get "callback", controller: "oauth_clients", action: :callback
+ get "ensure_connection", controller: "oauth_clients", action: :ensure_connection, as: "oauth_clients_ensure_connection"
end
if OpenProject::Configuration.lookbook_enabled?
@@ -652,6 +652,6 @@
end
if Rails.env.development?
- mount GoodJob::Engine => 'good_job'
+ mount GoodJob::Engine => "good_job"
end
end
diff --git a/docs/getting-started/my-account/README.md b/docs/getting-started/my-account/README.md
index a47a2ae730cf..59d704e31575 100644
--- a/docs/getting-started/my-account/README.md
+++ b/docs/getting-started/my-account/README.md
@@ -116,7 +116,7 @@ In order to register a new device for two-factor authentication, click the green
- Mobile phone
- App-based authenticator
-- WebAuth
+- WebAuthn
![](openproject_my_account_authentication_options.png)
diff --git a/docs/user-guide/projects/project-settings/file-storages/README.md b/docs/user-guide/projects/project-settings/file-storages/README.md
index 189f945c6680..e5dc87166037 100644
--- a/docs/user-guide/projects/project-settings/file-storages/README.md
+++ b/docs/user-guide/projects/project-settings/file-storages/README.md
@@ -60,6 +60,8 @@ The SharePoint file storage is now available to all work packages in this projec
> **Note:** Please refer to the [OneDrive/SharePoint user guide](../../../file-management/one-drive-integration) for further instructions on using the integration at a user level.
+
+
## Add a Nextcloud storage to a project
If you have selected the Nextcloud option in the previous step of storage selection, you will now see the **Project folder** options.
@@ -80,7 +82,11 @@ Click on **Add** to add your new Nextcloud file storage to this project.
The Nextcloud file storage is now available to all work packages in this project.
-> **Note:** For information on how to use the file storage (link Nextcloud user accounts at a user level, link files to a work package, view and download linked files, unlink files), please read our [Nextcloud integration user guide](../../../file-management/nextcloud-integration/).
+> **Note:** For information on how to use the file storage (link Nextcloud user accounts at a user level, link files to a work package, view and download linked files, unlink files), please read our [Nextcloud integration user guide](../../../file-management/nextcloud-integration/).
+
+If you do not yet have an access token for the file storage in a project, you will be prompted to log into your file storage. You can choose to login immediately to establish the connection or to do it later.
+
+![OAuth for file storages in OpenProject project settings](file-storages-oauth-nudge-nextcloud.png)
### Project folder member connection status
diff --git a/docs/user-guide/projects/project-settings/file-storages/file-storages-oauth-nudge-nextcloud.png b/docs/user-guide/projects/project-settings/file-storages/file-storages-oauth-nudge-nextcloud.png
new file mode 100644
index 000000000000..80a6224cb596
Binary files /dev/null and b/docs/user-guide/projects/project-settings/file-storages/file-storages-oauth-nudge-nextcloud.png differ
diff --git a/lib/open_project/static/links.rb b/lib/open_project/static/links.rb
index 18f38ad5c337..c89d47ff0ec8 100644
--- a/lib/open_project/static/links.rb
+++ b/lib/open_project/static/links.rb
@@ -44,6 +44,10 @@ def links
@links ||= static_links.merge(dynamic_links)
end
+ def url_for(item)
+ links.dig(item, :href)
+ end
+
def has?(name)
@links.key? name
end
@@ -277,6 +281,9 @@ def static_links
},
ical_docs: {
href: 'https://www.openproject.org/docs/user-guide/calendar/#subscribe-to-a-calendar'
+ },
+ integrations: {
+ href: 'https://www.openproject.org/docs/system-admin-guide/integrations/'
}
}
end
diff --git a/lib/open_project/version.rb b/lib/open_project/version.rb
index 69c9ca44612d..b545805e3995 100644
--- a/lib/open_project/version.rb
+++ b/lib/open_project/version.rb
@@ -26,8 +26,8 @@
# See COPYRIGHT and LICENSE files for more details.
#++
-require 'rexml/document'
-require 'open3'
+require "rexml/document"
+require "open3"
module OpenProject
module VERSION # :nodoc:
@@ -48,7 +48,7 @@ class << self
#
# 2.0.0debian-2
def special
- ''
+ ""
end
def revision
@@ -57,31 +57,31 @@ def revision
def core_sha
cached_or_block(:@core_sha) do
- read_optional 'CORE_VERSION'
+ read_optional "CORE_VERSION"
end
end
def core_url
cached_or_block(:@core_url) do
- read_optional 'CORE_URL'
+ read_optional "CORE_URL"
end
end
def product_sha
cached_or_block(:@product_sha) do
- read_optional 'PRODUCT_VERSION'
+ read_optional "PRODUCT_VERSION"
end
end
def product_url
cached_or_block(:@product_url) do
- read_optional 'PRODUCT_URL'
+ read_optional "PRODUCT_URL"
end
end
def builder_sha
cached_or_block(:@builder_sha) do
- read_optional 'BUILDER_VERSION'
+ read_optional "BUILDER_VERSION"
end
end
@@ -98,14 +98,14 @@ def to_a; ARRAY end
def to_s; STRING end
def to_semver
- [MAJOR, MINOR, PATCH].join('.') + special
+ [MAJOR, MINOR, PATCH].join(".") + special
end
private
def release_date_from_file
cached_or_block(:@release_date_from_file) do
- path = Rails.root.join('RELEASE_DATE')
+ path = Rails.root.join("RELEASE_DATE")
if File.exist? path
s = File.read(path)
Date.parse(s)
@@ -115,7 +115,7 @@ def release_date_from_file
def release_date_from_git
cached_or_block(:@release_date_from_git) do
- date, = Open3.capture3('git', 'log', '-1', '--format=%cd', '--date=short')
+ date, = Open3.capture3("git", "log", "-1", "--format=%cd", "--date=short")
Date.parse(date) if date
end
end
@@ -128,7 +128,7 @@ def revision_from_core_sha
def revision_from_git
cached_or_block(:@revision) do
- revision, = Open3.capture3('git', 'rev-parse', 'HEAD')
+ revision, = Open3.capture3("git", "rev-parse", "HEAD")
if revision.present?
revision.strip[0..8]
end
@@ -157,6 +157,6 @@ def cached_or_block(variable)
REVISION = revision
ARRAY = [MAJOR, MINOR, PATCH, REVISION].compact
- STRING = ARRAY.join('.')
+ STRING = ARRAY.join(".")
end
end
diff --git a/lib/redmine/plugin.rb b/lib/redmine/plugin.rb
index 4769847a8b6b..af0cac68c5ae 100644
--- a/lib/redmine/plugin.rb
+++ b/lib/redmine/plugin.rb
@@ -96,6 +96,7 @@ def def_field(*names)
end
end
def_field :gem_name, :url, :author, :author_url, :version, :settings, :bundled
+ alias :bundled? :bundled
attr_reader :id
# Plugin constructor
@@ -165,6 +166,12 @@ def self.all
registered_plugins.values
end
+ def self.not_bundled
+ registered_plugins
+ .values
+ .reject(&:bundled)
+ end
+
# Finds a plugin by its id
# Returns a PluginNotFound exception if the plugin doesn't exist
def self.find(id)
diff --git a/modules/bim/lib/open_project/bim/engine.rb b/modules/bim/lib/open_project/bim/engine.rb
index 741aac4738b7..3a9bd2ed922b 100644
--- a/modules/bim/lib/open_project/bim/engine.rb
+++ b/modules/bim/lib/open_project/bim/engine.rb
@@ -36,6 +36,7 @@ class Engine < ::Rails::Engine
register 'openproject-bim',
author_url: 'https://www.openproject.org',
+ bundled: true,
settings: {
default: {}
} do
diff --git a/modules/storages/app/forms/storages/admin/submit_or_cancel_form.rb b/modules/storages/app/forms/storages/admin/submit_or_cancel_form.rb
index e6a8b227aca0..064961e92fc4 100644
--- a/modules/storages/app/forms/storages/admin/submit_or_cancel_form.rb
+++ b/modules/storages/app/forms/storages/admin/submit_or_cancel_form.rb
@@ -58,7 +58,7 @@ def default_cancel_button_options
name: :cancel,
scheme: :default,
tag: :a,
- href: Rails.application.routes.url_helpers.admin_settings_storages_path,
+ href: OpenProject::StaticRouting::StaticRouter.new.url_helpers.admin_settings_storages_path,
label: I18n.t('button_cancel')
}
end
diff --git a/modules/storages/app/models/storages/project_storage.rb b/modules/storages/app/models/storages/project_storage.rb
index 3ae6dcb71e4e..8e9563377f06 100644
--- a/modules/storages/app/models/storages/project_storage.rb
+++ b/modules/storages/app/models/storages/project_storage.rb
@@ -92,7 +92,7 @@ def open(user)
def open_with_connection_ensured
return unless storage.configured?
- url_helpers = Rails.application.routes.url_helpers
+ url_helpers = OpenProject::StaticRouting::StaticRouter.new.url_helpers
open_project_storage_url = url_helpers.open_project_storage_url(
host: Setting.host_name,
protocol: 'https',
diff --git a/modules/storages/spec/features/storages_menu_links_spec.rb b/modules/storages/spec/features/storages_menu_links_spec.rb
index 969b12c2ad73..63d4347af018 100644
--- a/modules/storages/spec/features/storages_menu_links_spec.rb
+++ b/modules/storages/spec/features/storages_menu_links_spec.rb
@@ -31,23 +31,25 @@
require 'spec_helper'
require_module_spec_helper
-RSpec.describe 'Storage links in project menu', :js, :with_cuprite do
+RSpec.describe 'Storage links in project menu' do
include EnsureConnectionPathHelper
- let!(:storage_configured_linked1) { create(:nextcloud_storage_configured, :as_automatically_managed, name: "Storage 1") }
- let!(:project_storage1) { create(:project_storage, :as_automatically_managed, project:, storage: storage_configured_linked1) }
- let!(:storage_configured_linked2) { create(:nextcloud_storage_configured, name: "Storage 2") }
- let!(:project_storage2) do
+ shared_let(:project) { create(:project, enabled_module_names: %i[storages]) }
+ shared_let(:storage_configured_linked1) { create(:nextcloud_storage_configured, :as_automatically_managed, name: "Storage 1") }
+ shared_let(:project_storage1) do
+ create(:project_storage, :as_automatically_managed, project:, storage: storage_configured_linked1)
+ end
+ shared_let(:storage_configured_linked2) { create(:nextcloud_storage_configured, name: "Storage 2") }
+ shared_let(:project_storage2) do
create(:project_storage, project_folder_mode: 'inactive', project:, storage: storage_configured_linked2)
end
- let!(:storage_configured_linked3) { create(:nextcloud_storage_configured, name: "Storage 3") }
- let!(:project_storage3) do
+ shared_let(:storage_configured_linked3) { create(:nextcloud_storage_configured, name: "Storage 3") }
+ shared_let(:project_storage3) do
create(:project_storage, project_folder_mode: 'manual', project:, storage: storage_configured_linked3)
end
- let!(:storage_configured_unlinked) { create(:nextcloud_storage_configured, name: "Storage 4") }
- let!(:storage_unconfigured_linked) { create(:nextcloud_storage, name: "Storage 5") }
- let!(:project_storage4) { create(:project_storage, project:, storage: storage_unconfigured_linked) }
- let!(:project) { create(:project, enabled_module_names: %i[storages]) }
+ shared_let(:storage_configured_unlinked) { create(:nextcloud_storage_configured, name: "Storage 4") }
+ shared_let(:storage_unconfigured_linked) { create(:nextcloud_storage, name: "Storage 5") }
+ shared_let(:project_storage4) { create(:project_storage, project:, storage: storage_unconfigured_linked) }
let(:user) { create(:user, member_with_permissions: { project => permissions }) }
before do
@@ -82,6 +84,22 @@
expect(page).to have_no_link(storage_configured_unlinked.name)
expect(page).to have_no_link(storage_unconfigured_linked.name)
end
+
+ context 'when OP has been installed behind prefix' do
+ let(:prefix) { '/qwerty' }
+
+ before { allow(OpenProject::Configuration).to receive(:rails_relative_url_root).and_return(prefix) }
+
+ it 'has all links prefixed' do
+ visit(project_path(id: project.id))
+
+ expect(page).to have_link(storage_configured_linked1.name, href: ensure_connection_path(project_storage1))
+ expect(page).to have_link(storage_configured_linked2.name, href: ensure_connection_path(project_storage2))
+ expect(page).to have_link(storage_configured_linked3.name, href: ensure_connection_path(project_storage3))
+ expect(page).to have_no_link(storage_configured_unlinked.name)
+ expect(page).to have_no_link(storage_unconfigured_linked.name)
+ end
+ end
end
context 'read_files' do
diff --git a/modules/storages/spec/support/ensure_connection_path_helper.rb b/modules/storages/spec/support/ensure_connection_path_helper.rb
index 82d621094f03..58d1baecda45 100644
--- a/modules/storages/spec/support/ensure_connection_path_helper.rb
+++ b/modules/storages/spec/support/ensure_connection_path_helper.rb
@@ -30,10 +30,11 @@
module EnsureConnectionPathHelper
def ensure_connection_path(project_storage)
- oauth_clients_ensure_connection_path(
+ url_helpers = OpenProject::StaticRouting::StaticRouter.new.url_helpers
+ url_helpers.oauth_clients_ensure_connection_path(
oauth_client_id: project_storage.storage.oauth_client.client_id,
storage_id: project_storage.storage.id,
- destination_url: open_project_storage_url(
+ destination_url: url_helpers.open_project_storage_url(
protocol: 'https',
project_id: project_storage.project.identifier,
id: project_storage.id
diff --git a/modules/two_factor_authentication/app/controllers/concerns/two_factor_authentication/webauthn_relying_party.rb b/modules/two_factor_authentication/app/controllers/concerns/two_factor_authentication/webauthn_relying_party.rb
new file mode 100644
index 000000000000..8fcde41b375c
--- /dev/null
+++ b/modules/two_factor_authentication/app/controllers/concerns/two_factor_authentication/webauthn_relying_party.rb
@@ -0,0 +1,19 @@
+module ::TwoFactorAuthentication
+ module WebauthnRelyingParty
+ extend ActiveSupport::Concern
+
+ protected
+
+ def webauthn_relying_party
+ @webauthn_relying_party ||= begin
+ origin = "#{Setting.protocol}://#{Setting.host_name}"
+
+ WebAuthn::RelyingParty.new(
+ origin:,
+ id: URI(origin).host,
+ name: Setting.app_title
+ )
+ end
+ end
+ end
+end
diff --git a/modules/two_factor_authentication/app/controllers/two_factor_authentication/authentication_controller.rb b/modules/two_factor_authentication/app/controllers/two_factor_authentication/authentication_controller.rb
index d5d82c6266c8..a16a2a070608 100644
--- a/modules/two_factor_authentication/app/controllers/two_factor_authentication/authentication_controller.rb
+++ b/modules/two_factor_authentication/app/controllers/two_factor_authentication/authentication_controller.rb
@@ -4,8 +4,10 @@ class AuthenticationController < ApplicationController
include ::TwoFactorAuthentication::RememberToken
# Backup tokens functionality
include ::TwoFactorAuthentication::BackupCodes
+ # Webauthn relying party based on domain
+ include ::TwoFactorAuthentication::WebauthnRelyingParty
# Include global layout helper
- layout 'no_menu'
+ layout "no_menu"
# User is not yet logged in, so skip login required check
skip_before_action :check_if_login_required
@@ -29,7 +31,7 @@ def request_otp
session[:authenticated_user_force_2fa] = service.needs_registration?
if service.needs_registration?
- flash[:info] = I18n.t('two_factor_authentication.forced_registration.required_to_add_device')
+ flash[:info] = I18n.t("two_factor_authentication.forced_registration.required_to_add_device")
redirect_to new_forced_2fa_device_path
elsif !service.requires_token?
complete_stage_redirect
@@ -82,7 +84,7 @@ def successful_2fa_transmission(service, transmit)
##
# Create a token service for the current user
# with an optional override to use a non-default channel
- def otp_service(user, use_channel: nil, use_device: nil)
+ def otp_service(user, use_channel: nil, use_device: remembered_device(user))
session[:two_factor_authentication_device_id] = use_device.try(:id)
::TwoFactorAuthentication::TokenService.new user:, use_channel:, use_device:
end
@@ -90,16 +92,18 @@ def otp_service(user, use_channel: nil, use_device: nil)
##
# Get the used device for verification
def otp_service_for_verification(user)
- use_device =
- if session[:two_factor_authentication_device_id]
- user.otp_devices.find(session[:two_factor_authentication_device_id])
- end
- otp_service(user, use_device:)
+ otp_service(user, use_device: remembered_device(user))
rescue ActiveRecord::RecordNotFound
render_404
false
end
+ def remembered_device(user)
+ if session[:two_factor_authentication_device_id]
+ user.otp_devices.find(session[:two_factor_authentication_device_id])
+ end
+ end
+
##
# Detect overridden channel or device from params when trying to resend
def service_from_resend_params
@@ -142,9 +146,9 @@ def render_login_otp(service)
@active_devices = @user.otp_devices.get_active
if params["back_url"]
- render action: 'request_otp', back_url: params["back_url"]
+ render action: "request_otp", back_url: params["back_url"]
else
- render action: 'request_otp'
+ render action: "request_otp"
end
end
@@ -206,7 +210,7 @@ def manager
# In case of mis-configuration, block all logins
def ensure_valid_configuration
if manager.invalid_configuration?
- render_500 message: I18n.t('two_factor_authentication.error_is_enforced_not_active')
+ render_500 message: I18n.t("two_factor_authentication.error_is_enforced_not_active")
false
end
end
@@ -221,12 +225,5 @@ def complete_stage_redirect
def failure_stage_redirect
redirect_to authentication_stage_failure_path :two_factor_authentication
end
-
- def webauthn_relying_party
- @webauthn_relying_party ||= WebAuthn::RelyingParty.new(
- origin: "#{Setting.protocol}://#{Setting.host_name}",
- name: Setting.app_title
- )
- end
end
end
diff --git a/modules/two_factor_authentication/app/controllers/two_factor_authentication/base_controller.rb b/modules/two_factor_authentication/app/controllers/two_factor_authentication/base_controller.rb
index 0f9bfb8bf995..bf5ca78a4af8 100644
--- a/modules/two_factor_authentication/app/controllers/two_factor_authentication/base_controller.rb
+++ b/modules/two_factor_authentication/app/controllers/two_factor_authentication/base_controller.rb
@@ -1,5 +1,7 @@
module ::TwoFactorAuthentication
class BaseController < ApplicationController
+ include ::TwoFactorAuthentication::WebauthnRelyingParty
+
# Ensure 2FA authentication is enabled
before_action :ensure_enabled_2fa
@@ -10,17 +12,17 @@ class BaseController < ApplicationController
helper_method :optional_webauthn_challenge_url
- layout 'no_menu'
+ layout "no_menu"
def new
if params[:type]
@device_type = params[:type].to_sym
@device = new_device_type! @device_type
- render 'two_factor_authentication/two_factor_devices/new'
+ render "two_factor_authentication/two_factor_devices/new"
else
@available_devices = available_devices
- render 'two_factor_authentication/two_factor_devices/new_type'
+ render "two_factor_authentication/two_factor_devices/new_type"
end
end
@@ -32,7 +34,7 @@ def make_default
if @device.make_default!
flash[:notice] = t(:notice_successful_update)
else
- flash[:error] = t('two_factor_authentication.devices.make_default_failed')
+ flash[:error] = t("two_factor_authentication.devices.make_default_failed")
end
redirect_to index_path
@@ -42,14 +44,14 @@ def make_default
# Destroy the given device if its not the default
def destroy
if @device.default && strategy_manager.enforced?
- render_400 message: t('two_factor_authentication.devices.is_default_cannot_delete')
+ render_400 message: t("two_factor_authentication.devices.is_default_cannot_delete")
return
end
if @device.destroy
flash[:notice] = t(:notice_successful_delete)
else
- flash[:error] = t('two_factor_authentication.devices.failed_to_delete')
+ flash[:error] = t("two_factor_authentication.devices.failed_to_delete")
Rails.logger.error "Failed to delete #{@device.id} of user#{target_user.id}. Errors: #{@device.errors.full_messages.join(' ')}"
end
@@ -89,8 +91,8 @@ def request_device_confirmation_token
request_token_for_device(
@device,
confirm_path: url_for(action: :confirm, device_id: @device.id),
- title: I18n.t('two_factor_authentication.devices.confirm_device'),
- message: I18n.t('two_factor_authentication.devices.text_confirm_to_complete_html', identifier: @device.identifier)
+ title: I18n.t("two_factor_authentication.devices.confirm_device"),
+ message: I18n.t("two_factor_authentication.devices.text_confirm_to_complete_html", identifier: @device.identifier)
)
end
@@ -112,15 +114,15 @@ def validate_device_token
# rubocop:disable Metrics/AbcSize
def confirm_and_save(result)
if result.success? && @device.confirm_registration_and_save
- flash[:notice] = t('two_factor_authentication.devices.registration_complete')
+ flash[:notice] = t("two_factor_authentication.devices.registration_complete")
true
elsif !result.success?
flash[:notice] = nil
- flash[:error] = t('two_factor_authentication.devices.registration_failed_token_invalid')
+ flash[:error] = t("two_factor_authentication.devices.registration_failed_token_invalid")
false
else
flash[:notice] = nil
- flash[:error] = t('two_factor_authentication.devices.registration_failed_update')
+ flash[:error] = t("two_factor_authentication.devices.registration_failed_update")
false
end
end
@@ -133,10 +135,10 @@ def request_token_for_device(device, locals)
flash[:notice] = transmit.result if transmit.result.present?
# Request confirmation from user as in the regular login flow
- render 'two_factor_authentication/two_factor_devices/confirm', layout: 'base', locals:
+ render "two_factor_authentication/two_factor_devices/confirm", layout: "base", locals:
else
error = transmit.errors.full_messages.join(". ")
- default_message = t('two_factor_authentication.devices.confirm_send_failed')
+ default_message = t("two_factor_authentication.devices.confirm_send_failed")
flash[:error] = "#{default_message} #{error}"
redirect_to registration_failure_path
@@ -195,18 +197,6 @@ def verify_webauthn_credential
false
end
- def webauthn_relying_party
- @webauthn_relying_party ||= begin
- origin = "#{Setting.protocol}://#{Setting.host_name}"
-
- WebAuthn::RelyingParty.new(
- origin:,
- id: URI(origin).host,
- name: Setting.app_title
- )
- end
- end
-
def logout_other_sessions
if current_user == target_user
Rails.logger.info { "First 2FA device registered for #{target_user}, terminating other logged in sessions." }
@@ -257,7 +247,7 @@ def show_local_breadcrumb
end
def default_breadcrumb
- t('two_factor_authentication.label_devices')
+ t("two_factor_authentication.label_devices")
end
def available_devices
diff --git a/modules/two_factor_authentication/app/models/two_factor_authentication/device/webauthn.rb b/modules/two_factor_authentication/app/models/two_factor_authentication/device/webauthn.rb
index 7379befcd3fc..d36848dbac00 100644
--- a/modules/two_factor_authentication/app/models/two_factor_authentication/device/webauthn.rb
+++ b/modules/two_factor_authentication/app/models/two_factor_authentication/device/webauthn.rb
@@ -21,15 +21,13 @@ def self.device_type
def options_for_create(relying_party)
@options_for_create ||= relying_party.options_for_registration(
user: { id: user.webauthn_id, name: user.name },
- exclude: TwoFactorAuthentication::Device::Webauthn.where(user:).pluck(:webauthn_external_id),
- authenticator_selection: { user_verification: 'discouraged' }
+ exclude: TwoFactorAuthentication::Device::Webauthn.where(user:).pluck(:webauthn_external_id)
)
end
def options_for_get(relying_party)
@options_for_get ||= relying_party.options_for_authentication(
- user_verification: 'discouraged', # we do not require user verification
- allow: webauthn_external_id # TODO: Maybe also allow all other tokens? Let's see
+ allow: [webauthn_external_id] # TODO: Maybe also allow all other tokens? Let's see
)
end