diff --git a/.github/workflows/brakeman-scan-core.yml b/.github/workflows/brakeman-scan-core.yml index 1b585f33991b..3160d431daf2 100644 --- a/.github/workflows/brakeman-scan-core.yml +++ b/.github/workflows/brakeman-scan-core.yml @@ -29,11 +29,6 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 - with: - # FIXME: remove the ruby version once '3.2.2' is released. - # This is set to head to fix ruby segfaulting when brakeman is - # used. See https://bugs.ruby-lang.org/issues/19433 - ruby-version: 'head' - name: Setup Brakeman run: | diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index c28326a5fb47..b8554bbbf4bc 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -19,9 +19,8 @@ jobs: TOKEN: ${{ secrets.OPENPROJECT_CI_TOKEN }} REPOSITORY: opf/openproject-flavours WORKFLOW_ID: ci.yml - CORE_REF: ${{ github.ref_name }} run: | curl -i --fail-with-body -H"authorization: Bearer $TOKEN" \ -XPOST -H"Accept: application/vnd.github.v3+json" \ https://api.github.com/repos/$REPOSITORY/actions/workflows/$WORKFLOW_ID/dispatches \ - -d '{"ref": "dev", "inputs": { "ref" : "'$CORE_REF'" }}' + -d '{"ref": "dev", "inputs": { "ref" : "${{ github.ref_name }}" }}' diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml new file mode 100644 index 000000000000..8b30f3873e49 --- /dev/null +++ b/.github/workflows/danger.yml @@ -0,0 +1,26 @@ +name: migration-warning-on-release-branches + +on: + pull_request: + branches: + - release/* + paths: + - 'db/migrate/**.rb' + - 'modules/**/db/migrate/*.rb' + +jobs: + danger: + if: github.repository == 'opf/openproject' + runs-on: [ubuntu-latest] + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2.3' + - uses: MeilCli/danger-action@v5 + with: + danger_file: 'Dangerfile' + danger_id: 'danger-pr' + env: + DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Dangerfile b/Dangerfile new file mode 100644 index 000000000000..3e464b81f625 --- /dev/null +++ b/Dangerfile @@ -0,0 +1,9 @@ +CORE_OR_MODULE_MIGRATIONS_REGEX = %r{(modules/.*)?db/migrate/.*\.rb} + +def added_or_modified_migrations? + (git.modified_files + git.added_files).grep(CORE_OR_MODULE_MIGRATIONS_REGEX) +end + +if added_or_modified_migrations? + warn "This PR has migration-related changes on a release branch. Ping @opf/operations" +end diff --git a/Gemfile b/Gemfile index d1f0ac5216e1..bf82336a46b5 100644 --- a/Gemfile +++ b/Gemfile @@ -155,7 +155,7 @@ gem 'structured_warnings', '~> 0.4.0' # don't require by default, instead load on-demand when actually configured gem 'airbrake', '~> 13.0.0', require: false -gem 'md_to_pdf', git: 'https://github.com/opf/md-to-pdf', ref: '04d22bfa73fbeb549fb1f215a6b9a81cfe820814' +gem 'md_to_pdf', git: 'https://github.com/opf/md-to-pdf', ref: '82c2b5cc25a28fbd62cb05b17d9ba0f68d701109' gem 'prawn', '~> 2.4' # prawn implicitly depends on matrix gem no longer in ruby core with 3.1 gem 'matrix', '~> 0.4.2' @@ -164,6 +164,8 @@ gem 'meta-tags', '~> 2.20.0' gem 'paper_trail', '~> 15.1.0' +gem 'clamav-client', github: 'honestica/clamav-client', ref: '29e78ae94307cb34e79ddd29c5da79752239d8b7' + group :production do # we use dalli as standard memcache client # requires memcached 1.4+ @@ -212,7 +214,7 @@ gem 'appsignal', '~> 3.0', require: false gem 'view_component' # Lookbook -gem 'lookbook', github: 'ViewComponent/lookbook', ref: '473f86d7e343cd78b74cc293a4de06b9b5e7a3e2' +gem 'lookbook', '~> 2.2.1' # Require factory_bot for usage with openproject plugins testing gem 'factory_bot', '~> 6.4.0', require: false @@ -262,7 +264,7 @@ group :test do gem 'capybara-screenshot', '~> 1.0.17' gem 'cuprite', '~> 0.15.0' gem 'selenium-devtools' - gem 'selenium-webdriver', '~> 4.17.0' + gem 'selenium-webdriver', '~> 4.18.0' gem 'fuubar', '~> 2.5.0' gem 'timecop', '~> 0.9.0' @@ -315,9 +317,9 @@ group :development, :test do 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-doc' # ruby linting gem 'rubocop', require: false @@ -381,4 +383,4 @@ end gem 'openproject-octicons', '~>19.8.0' gem 'openproject-octicons_helper', '~>19.8.0' -gem 'openproject-primer_view_components', '~>0.20.0' +gem 'openproject-primer_view_components', '~>0.22.2' diff --git a/Gemfile.lock b/Gemfile.lock index 29279b35a89a..59c6c834fe52 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,24 +1,6 @@ -GIT - remote: https://github.com/ViewComponent/lookbook.git - revision: 473f86d7e343cd78b74cc293a4de06b9b5e7a3e2 - ref: 473f86d7e343cd78b74cc293a4de06b9b5e7a3e2 - specs: - lookbook (2.2.0) - activemodel - css_parser - htmlbeautifier (~> 1.3) - htmlentities (~> 4.3.4) - marcel (~> 1.0) - railties (>= 5.0) - redcarpet (~> 3.5) - rouge (>= 3.26, < 5.0) - view_component (>= 2.0) - yard (~> 0.9.25) - zeitwerk (~> 2.5) - GIT remote: https://github.com/citizensadvice/capybara_accessible_selectors - revision: 621e394ff9bafb420fb15792c3a3e3334a618687 + revision: 67bbac702fd039a81ce858f1157701b5a5b70cd1 branch: main specs: capybara_accessible_selectors (0.11.0) @@ -34,18 +16,25 @@ GIT parallel_tests (>= 3.3.0, < 5) rspec (>= 3.10) +GIT + remote: https://github.com/honestica/clamav-client.git + revision: 29e78ae94307cb34e79ddd29c5da79752239d8b7 + ref: 29e78ae94307cb34e79ddd29c5da79752239d8b7 + specs: + clamav-client (3.4.2) + GIT remote: https://github.com/opf/md-to-pdf - revision: 04d22bfa73fbeb549fb1f215a6b9a81cfe820814 - ref: 04d22bfa73fbeb549fb1f215a6b9a81cfe820814 + revision: 82c2b5cc25a28fbd62cb05b17d9ba0f68d701109 + ref: 82c2b5cc25a28fbd62cb05b17d9ba0f68d701109 specs: - md_to_pdf (0.0.24) + md_to_pdf (0.0.25) color_conversion (~> 0.1) front_matter_parser (~> 1.0) json-schema (~> 4.1) - markly (~> 0.7) + markly (~> 0.10) matrix (~> 0.4) - nokogiri (~> 1.1) + nokogiri (~> 1.16) prawn (~> 2.4) prawn-table (~> 0.2) text-hyphen (~> 1.5) @@ -219,7 +208,7 @@ PATH remote: modules/two_factor_authentication specs: openproject-two_factor_authentication (1.0.0) - aws-sdk-sns (~> 1.71.0) + aws-sdk-sns (~> 1.72.0) messagebird-rest (~> 1.4.2) rotp (~> 6.1) @@ -238,35 +227,35 @@ GEM remote: https://rubygems.org/ specs: Ascii85 (1.1.0) - actioncable (7.1.3) - actionpack (= 7.1.3) - activesupport (= 7.1.3) + actioncable (7.1.3.2) + actionpack (= 7.1.3.2) + activesupport (= 7.1.3.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.3) - actionpack (= 7.1.3) - activejob (= 7.1.3) - activerecord (= 7.1.3) - activestorage (= 7.1.3) - activesupport (= 7.1.3) + actionmailbox (7.1.3.2) + actionpack (= 7.1.3.2) + activejob (= 7.1.3.2) + activerecord (= 7.1.3.2) + activestorage (= 7.1.3.2) + activesupport (= 7.1.3.2) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.1.3) - actionpack (= 7.1.3) - actionview (= 7.1.3) - activejob (= 7.1.3) - activesupport (= 7.1.3) + actionmailer (7.1.3.2) + actionpack (= 7.1.3.2) + actionview (= 7.1.3.2) + activejob (= 7.1.3.2) + activesupport (= 7.1.3.2) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.2) - actionpack (7.1.3) - actionview (= 7.1.3) - activesupport (= 7.1.3) + actionpack (7.1.3.2) + actionview (= 7.1.3.2) + activesupport (= 7.1.3.2) nokogiri (>= 1.8.5) racc rack (>= 2.2.4) @@ -277,31 +266,31 @@ GEM actionpack-xml_parser (2.0.1) actionpack (>= 5.0) railties (>= 5.0) - actiontext (7.1.3) - actionpack (= 7.1.3) - activerecord (= 7.1.3) - activestorage (= 7.1.3) - activesupport (= 7.1.3) + actiontext (7.1.3.2) + actionpack (= 7.1.3.2) + activerecord (= 7.1.3.2) + activestorage (= 7.1.3.2) + activesupport (= 7.1.3.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.3) - activesupport (= 7.1.3) + actionview (7.1.3.2) + activesupport (= 7.1.3.2) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.1.3) - activesupport (= 7.1.3) + activejob (7.1.3.2) + activesupport (= 7.1.3.2) globalid (>= 0.3.6) - activemodel (7.1.3) - activesupport (= 7.1.3) + activemodel (7.1.3.2) + activesupport (= 7.1.3.2) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (7.1.3) - activemodel (= 7.1.3) - activesupport (= 7.1.3) + activerecord (7.1.3.2) + activemodel (= 7.1.3.2) + activesupport (= 7.1.3.2) timeout (>= 0.4.0) activerecord-import (1.5.1) activerecord (>= 4.2) @@ -314,13 +303,13 @@ GEM multi_json (~> 1.11, >= 1.11.2) rack (>= 2.0.8, < 4) railties (>= 6.1) - activestorage (7.1.3) - actionpack (= 7.1.3) - activejob (= 7.1.3) - activerecord (= 7.1.3) - activesupport (= 7.1.3) + activestorage (7.1.3.2) + actionpack (= 7.1.3.2) + activejob (= 7.1.3.2) + activerecord (= 7.1.3.2) + activesupport (= 7.1.3.2) marcel (~> 1.0) - activesupport (7.1.3) + activesupport (7.1.3.2) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -351,11 +340,12 @@ GEM awesome_nested_set (3.6.0) activerecord (>= 4.0.0, < 7.2) aws-eventstream (1.3.0) - aws-partitions (1.888.0) - aws-sdk-core (3.191.1) + aws-partitions (1.894.0) + aws-sdk-core (3.191.2) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) + base64 jmespath (~> 1, >= 1.6.1) aws-sdk-kms (1.77.0) aws-sdk-core (~> 3, >= 3.191.0) @@ -364,7 +354,7 @@ GEM aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) - aws-sdk-sns (1.71.0) + aws-sdk-sns (1.72.0) aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) aws-sigv4 (1.8.0) @@ -390,7 +380,7 @@ GEM parser (>= 2.4) smart_properties bigdecimal (3.1.6) - bindata (2.4.15) + bindata (2.5.0) bootsnap (1.18.3) msgpack (~> 1.2) brakeman (6.1.2) @@ -435,7 +425,7 @@ GEM concurrent-ruby (1.2.3) connection_pool (2.4.1) cookiejar (0.3.4) - crack (0.4.6) + crack (1.0.0) bigdecimal rexml crass (1.0.6) @@ -445,8 +435,7 @@ GEM capybara (~> 3.0) ferrum (~> 0.14.0) daemons (1.4.1) - dalli (3.2.7) - base64 + dalli (3.2.8) date (3.3.4) date_validator (0.12.0) activemodel (>= 3) @@ -463,12 +452,12 @@ GEM disposable (0.6.3) declarative (>= 0.0.9, < 1.0.0) representable (>= 3.1.1, < 4) - doorkeeper (5.6.8) + doorkeeper (5.6.9) railties (>= 5) - dotenv (2.8.1) - dotenv-rails (2.8.1) - dotenv (= 2.8.1) - railties (>= 3.2) + dotenv (3.0.2) + dotenv-rails (3.0.2) + dotenv (= 3.0.2) + railties (>= 6.1) drb (2.2.0) ruby2_keywords dry-container (0.11.0) @@ -589,7 +578,7 @@ GEM google-apis-core (>= 0.12.0, < 2.a) google-cloud-env (2.1.1) faraday (>= 1.0, < 3.a) - googleauth (1.10.0) + googleauth (1.11.0) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) @@ -615,7 +604,7 @@ GEM html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) - htmlbeautifier (1.4.2) + htmlbeautifier (1.4.3) htmldiff (0.0.1) htmlentities (4.3.4) http-2-next (1.0.3) @@ -667,14 +656,15 @@ GEM json_spec (1.1.5) multi_json (~> 1.0) rspec (>= 2.0, < 4.0) - jwt (2.7.1) + jwt (2.8.0) + base64 ladle (1.0.1) open4 (~> 1.0) language_server-protocol (3.17.0.3) launchy (2.5.2) addressable (~> 2.8) lefthook (1.6.1) - letter_opener (1.8.1) + letter_opener (1.9.0) launchy (>= 2.2, < 3) listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) @@ -691,6 +681,18 @@ GEM loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) + lookbook (2.2.1) + activemodel + css_parser + htmlbeautifier (~> 1.3) + htmlentities (~> 4.3.4) + marcel (~> 1.0) + railties (>= 5.0) + redcarpet (~> 3.5) + rouge (>= 3.26, < 5.0) + view_component (>= 2.0) + yard (~> 0.9.25) + zeitwerk (~> 2.5) mail (2.8.1) mini_mime (>= 0.1.1) net-imap @@ -757,7 +759,7 @@ GEM actionview openproject-octicons (= 19.8.0) railties - openproject-primer_view_components (0.20.0) + openproject-primer_view_components (0.22.2) actionview (>= 5.0.0) activesupport (>= 5.0.0) openproject-octicons (>= 19.8.0) @@ -770,7 +772,7 @@ GEM activerecord (>= 6.1) request_store (~> 1.4) parallel (1.24.0) - parallel_tests (4.5.0) + parallel_tests (4.5.1) parallel parser (3.3.0.5) ast (~> 2.4.1) @@ -784,7 +786,7 @@ GEM hashery (~> 2.0) ruby-rc4 ttfunk - pg (1.5.4) + pg (1.5.5) plaintext (0.3.4) activesupport (> 2.2.1) nokogiri (~> 1.10, >= 1.10.4) @@ -825,14 +827,14 @@ GEM puma (>= 5.0, < 7) raabro (1.4.0) racc (1.7.3) - rack (2.2.8) + rack (2.2.8.1) rack-accept (0.4.5) rack (>= 0.4) rack-attack (6.7.0) rack (>= 1.0, < 4) rack-cors (2.0.1) rack (>= 2.0.0) - rack-mini-profiler (3.3.0) + rack-mini-profiler (3.3.1) rack (>= 1.2.0) rack-oauth2 (2.2.1) activesupport @@ -855,20 +857,20 @@ GEM rackup (1.0.0) rack (< 3) webrick - rails (7.1.3) - actioncable (= 7.1.3) - actionmailbox (= 7.1.3) - actionmailer (= 7.1.3) - actionpack (= 7.1.3) - actiontext (= 7.1.3) - actionview (= 7.1.3) - activejob (= 7.1.3) - activemodel (= 7.1.3) - activerecord (= 7.1.3) - activestorage (= 7.1.3) - activesupport (= 7.1.3) + rails (7.1.3.2) + actioncable (= 7.1.3.2) + actionmailbox (= 7.1.3.2) + actionmailer (= 7.1.3.2) + actionpack (= 7.1.3.2) + actiontext (= 7.1.3.2) + actionview (= 7.1.3.2) + activejob (= 7.1.3.2) + activemodel (= 7.1.3.2) + activerecord (= 7.1.3.2) + activestorage (= 7.1.3.2) + activesupport (= 7.1.3.2) bundler (>= 1.15.0) - railties (= 7.1.3) + railties (= 7.1.3.2) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -883,9 +885,9 @@ GEM rails-i18n (7.0.8) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) - railties (7.1.3) - actionpack (= 7.1.3) - activesupport (= 7.1.3) + railties (7.1.3.2) + actionpack (= 7.1.3.2) + activesupport (= 7.1.3.2) irb rackup (>= 1.0.0) rake (>= 12.2) @@ -896,7 +898,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rb_sys (0.9.87) + rb_sys (0.9.88) rbtree3 (0.7.1) rdoc (6.6.2) psych (>= 4.0.0) @@ -904,7 +906,7 @@ GEM redcarpet (3.6.0) redis (5.1.0) redis-client (>= 0.17.0) - redis-client (0.19.1) + redis-client (0.20.0) connection_pool regexp_parser (2.9.0) reline (0.4.2) @@ -1000,9 +1002,9 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.12.0) secure_headers (6.5.0) - selenium-devtools (0.121.0) + selenium-devtools (0.122.0) selenium-webdriver (~> 4.2) - selenium-webdriver (4.17.0) + selenium-webdriver (4.18.1) base64 (~> 0.2) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) @@ -1011,7 +1013,7 @@ GEM shoulda-context (2.0.0) shoulda-matchers (6.1.0) activesupport (>= 5.2.0) - signet (0.18.0) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -1060,7 +1062,7 @@ GEM timeout (0.4.1) trailblazer-option (0.1.2) ttfunk (1.7.0) - turbo-rails (2.0.2) + turbo-rails (2.0.4) actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) @@ -1083,7 +1085,7 @@ GEM activemodel (>= 3.0.0) public_suffix vcr (6.2.0) - view_component (3.10.0) + view_component (3.11.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) method_source (~> 1.0) @@ -1099,7 +1101,7 @@ GEM activesupport faraday (~> 2.0) faraday-follow_redirects - webmock (3.20.0) + webmock (3.22.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -1146,6 +1148,7 @@ DEPENDENCIES capybara_accessible_selectors! carrierwave (~> 1.3.1) carrierwave_direct (~> 2.1.0) + clamav-client! climate_control closure_tree (~> 7.4.0) colored2 @@ -1195,7 +1198,7 @@ DEPENDENCIES letter_opener listen (~> 3.8.0) lograge (~> 0.14.0) - lookbook! + lookbook (~> 2.2.1) mail (= 2.8.1) matrix (~> 0.4.2) md_to_pdf! @@ -1227,7 +1230,7 @@ DEPENDENCIES openproject-octicons (~> 19.8.0) openproject-octicons_helper (~> 19.8.0) openproject-openid_connect! - openproject-primer_view_components (~> 0.20.0) + openproject-primer_view_components (~> 0.22.2) openproject-recaptcha! openproject-reporting! openproject-storages! @@ -1284,7 +1287,7 @@ DEPENDENCIES sanitize (~> 6.1.0) secure_headers (~> 6.5.0) selenium-devtools - selenium-webdriver (~> 4.17.0) + selenium-webdriver (~> 4.18.0) semantic (~> 1.6.1) shoulda-context (~> 2.0) shoulda-matchers (~> 6.0) diff --git a/app/components/admin/quarantined_attachments/row_component.rb b/app/components/admin/quarantined_attachments/row_component.rb new file mode 100644 index 000000000000..392929a4db1a --- /dev/null +++ b/app/components/admin/quarantined_attachments/row_component.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Admin + module QuarantinedAttachments + class RowComponent < ::RowComponent + delegate :project, to: :attachment, allow_nil: true + delegate :container, to: :attachment, allow_nil: true + + def attachment + model + end + + def row_css_id + "quarantined_attachment_#{attachment.id}" + end + + delegate :filename, to: :attachment + + def attached_to + description = attachment.description.present? ? "(#{attachment.description})" : '' + text = "#{container_name} #{attachment.container_id} #{description}" + case container + when Message + helpers.link_to_message(container) + when WorkPackage + helpers.link_to_work_package(container) + when WikiPage + helpers.link_to(text, project_wiki_path(project, container)) + when User + helpers.link_to_user(container) + when MeetingContent + helpers.link_to(text, meeting_path(container.meeting_id)) + when Grids::Overview + helpers.link_to(text, project_overview_path(container.project_id)) + else + text + end + end + + def container_name + container ? container.model_name.human : (attachment.container_type || I18n.t(:label_none)) + end + + def author + render Users::AvatarComponent.new(user: attachment.author, size: :mini, link: true, show_name: true) + end + + def created_at + helpers.format_time attachment.created_at + end + + def button_links + [delete_link] + end + + def delete_link + helpers.link_to( + helpers.op_icon('icon icon-delete'), + { controller: '/admin/attachments/quarantined_attachments', action: :destroy, id: model }, + title: I18n.t('antivirus_scan.quarantined_attachments.delete'), + method: :delete, + data: { confirm: I18n.t(:text_are_you_sure), disable_with: I18n.t(:label_loading) } + ) + end + end + end +end diff --git a/app/components/admin/quarantined_attachments/table_component.rb b/app/components/admin/quarantined_attachments/table_component.rb new file mode 100644 index 000000000000..f2f2d429b814 --- /dev/null +++ b/app/components/admin/quarantined_attachments/table_component.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Admin + module QuarantinedAttachments + class TableComponent < ::TableComponent + columns :filename, :attached_to, :author, :created_at + options :current_user + + def initial_sort + %i[id desc] + end + + def sortable? + false + end + + def headers + [ + ['filename', { caption: Attachment.human_attribute_name(:filename) }], + ['attached_to', { caption: I18n.t('antivirus_scan.quarantined_attachments.container') }], + ['author', { caption: Attachment.human_attribute_name(:author) }], + ['created_at', { caption: Attachment.human_attribute_name(:created_at) }] + ] + end + end + end +end diff --git a/app/components/members/row_component.rb b/app/components/members/row_component.rb index b388a71794f4..a1bb6fc7857e 100644 --- a/app/components/members/row_component.rb +++ b/app/components/members/row_component.rb @@ -77,9 +77,9 @@ def roles def shared count = member.shared_work_packages_count if count > 0 - link_to I18n.t(:'label_x_work_packages', count:), + link_to I18n.t(:label_x_work_packages, count:), helpers.project_work_packages_shared_with_path(principal, member.project), - target: "_blank" + target: "_blank", rel: "noopener" end end diff --git a/app/components/projects/projects_filters_component.rb b/app/components/projects/projects_filters_component.rb index db33c2ccb9a9..6ffc4af774a3 100644 --- a/app/components/projects/projects_filters_component.rb +++ b/app/components/projects/projects_filters_component.rb @@ -29,7 +29,6 @@ # ++ class Projects::ProjectsFiltersComponent < FiltersComponent - def allowed_filters super .select { |f| allowed_filter?(f) } diff --git a/app/controllers/admin/attachments/quarantined_attachments_controller.rb b/app/controllers/admin/attachments/quarantined_attachments_controller.rb new file mode 100644 index 000000000000..a931d5576ad8 --- /dev/null +++ b/app/controllers/admin/attachments/quarantined_attachments_controller.rb @@ -0,0 +1,83 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Admin + module Attachments + class QuarantinedAttachmentsController < ApplicationController + layout 'admin' + before_action :require_admin + before_action :find_quarantined_attachments + + before_action :find_attachment, only: %i[destroy] + + menu_item :attachment_quarantine + + def index; end + + def destroy + container = @attachment.container + @attachment.destroy! + + create_journal(container, + User.system, + I18n.t('antivirus_scan.deleted_by_admin', filename: @attachment.filename)) + + flash[:notice] = t(:notice_successful_delete) + redirect_to action: :index + end + + def default_breadcrumb + t('antivirus_scan.quarantined_attachments.title') + end + + def show_local_breadcrumb + true + end + + private + + def create_journal(container, user, notes) + ::Journals::CreateService + .new(container, user) + .call(notes:) + end + + def find_quarantined_attachments + @attachments = Attachment + .status_quarantined + .includes(:author, :container) + end + + def find_attachment + @attachment = @attachments.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end + end + end +end diff --git a/app/controllers/admin/settings/attachments_settings_controller.rb b/app/controllers/admin/settings/attachments_settings_controller.rb index 98633cee7f9b..929520884250 100644 --- a/app/controllers/admin/settings/attachments_settings_controller.rb +++ b/app/controllers/admin/settings/attachments_settings_controller.rb @@ -28,14 +28,12 @@ module Admin::Settings class AttachmentsSettingsController < ::Admin::SettingsController - menu_item :settings_attachments + menu_item :attachments_settings def default_breadcrumb t(:'attributes.attachments') end - private - def settings_params super.tap do |settings| settings["attachment_whitelist"] = settings["attachment_whitelist"].split(/\r?\n/) diff --git a/app/controllers/admin/settings/projects_settings_controller.rb b/app/controllers/admin/settings/projects_settings_controller.rb index dd971a29bde2..5e879fb9abfd 100644 --- a/app/controllers/admin/settings/projects_settings_controller.rb +++ b/app/controllers/admin/settings/projects_settings_controller.rb @@ -50,7 +50,7 @@ def validate_enabled_modules I18n.t( 'settings.projects.missing_dependencies', module: I18n.t("project_module_#{m[:name]}"), - dependencies: m[:dependencies].map { |dep|I18n.t("project_module_#{dep}") }.join(', ') + dependencies: m[:dependencies].map { |dep| I18n.t("project_module_#{dep}") }.join(', ') ) end diff --git a/app/controllers/admin/settings/virus_scanning_settings_controller.rb b/app/controllers/admin/settings/virus_scanning_settings_controller.rb new file mode 100644 index 000000000000..01c4ecc124bb --- /dev/null +++ b/app/controllers/admin/settings/virus_scanning_settings_controller.rb @@ -0,0 +1,106 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Admin::Settings + class VirusScanningSettingsController < ::Admin::SettingsController + menu_item :virus_scanning_settings + + before_action :require_ee + before_action :check_clamav, only: %i[update], if: -> { scan_enabled? } + + def default_breadcrumb + t('settings.antivirus.title') + end + + def av_form + selected = params.dig(:settings, :antivirus_scan_mode)&.to_sym || :disabled + + respond_to do |format| + format.turbo_stream do + render turbo_stream: turbo_stream.replace(:attachments_av_subform, + partial: "admin/settings/virus_scanning_settings/av_form", + locals: { selected: }) + end + end + end + + private + + def require_ee + render('upsale') unless EnterpriseToken.allows_to?(:virus_scanning) + end + + def mark_unscanned_attachments + @unscanned_attachments = Attachment.status_uploaded + end + + def check_clamav + return if params.dig(:settings, :antivirus_scan_mode) == 'disabled' + + service = ::Attachments::ClamAVService.new(params[:settings][:antivirus_scan_mode].to_sym, + params[:settings][:antivirus_scan_target]) + + service.ping + rescue StandardError => e + Rails.logger.error { "Failed to check availability of ClamAV: #{e.message}" } + flash[:error] = t(:'settings.antivirus.clamav_ping_failed') + redirect_to action: :show + end + + def scan_enabled? + Setting.antivirus_scan_mode != :disabled || params.dig(:settings, :antivirus_scan_mode) != 'disabled' + end + + def success_callback(_call) + if Setting.antivirus_scan_mode == :disabled && Attachment.status_quarantined.any? + remaining_quarantine_warning + elsif scan_enabled? + rescan_files + else + super + end + end + + def rescan_files + flash[:info] = t('settings.antivirus.remaining_rescanned_files', + file_count: t(:label_x_files, count: Attachment.status_uploaded.count)) + Attachment.status_uploaded.update_all(status: :rescan) + + job = Attachments::VirusRescanJob.perform_later + redirect_to job_status_path(job.job_id) + end + + def remaining_quarantine_warning + flash[:info] = t('settings.antivirus.remaining_quarantined_files_html', + link: helpers.link_to(t('antivirus_scan.quarantined_attachments.title'), + admin_quarantined_attachments_path), + file_count: t(:label_x_files, count: Attachment.status_quarantined.count)) + redirect_to action: :show + end + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1f77a494fd07..564973057f70 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -220,7 +220,15 @@ def reset_i18n_fallbacks end def set_localization - SetLocalizationService.new(User.current, request.env['HTTP_ACCEPT_LANGUAGE']).call + # 1. Use completely autheticated user + # 2. Use user with some authenticated stages not compelted. + # In this case user is not considered logged in, but identified. + # It covers localization for extra authentication stages(like :consent, for example) + # 3. Use anonymous instance. + user = RequestStore[:current_user] || + (session[:authenticated_user_id].present? && User.find_by(id: session[:authenticated_user_id])) || + User.anonymous + SetLocalizationService.new(user, request.env['HTTP_ACCEPT_LANGUAGE']).call end def deny_access(not_found: false) diff --git a/app/controllers/concerns/accounts/authentication_stages.rb b/app/controllers/concerns/accounts/authentication_stages.rb index 1ddb7bf5fbd4..c2524f58e16d 100644 --- a/app/controllers/concerns/accounts/authentication_stages.rb +++ b/app/controllers/concerns/accounts/authentication_stages.rb @@ -1,3 +1,31 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + module Accounts::AuthenticationStages def successful_authentication(user, reset_stages: true, just_registered: false) stages = authentication_stages after_activation: just_registered, reset: reset_stages diff --git a/app/controllers/concerns/accounts/user_consent.rb b/app/controllers/concerns/accounts/user_consent.rb index 74cf7f9d00ca..a2603dfae3ef 100644 --- a/app/controllers/concerns/accounts/user_consent.rb +++ b/app/controllers/concerns/accounts/user_consent.rb @@ -33,8 +33,8 @@ module Accounts::UserConsent include ::UserConsentHelper def consent - if consent_required? - render 'account/consent', locals: { consenting_user: } + if user_consent_required? && consenting_user&.consent_expired? + render 'account/consent' else consent_finished end @@ -50,14 +50,6 @@ def confirm_consent end end - def consent_required? - # Ensure consent is enabled and a text is provided - return false unless user_consent_required? - - # Require the user to consent if he hasn't already - consent_expired? - end - def decline_consent message = I18n.t('consent.decline_warning_message') + "\n" message << @@ -71,19 +63,6 @@ def decline_consent redirect_to authentication_stage_failure_path :consent end - def consent_expired? - consented_at = consenting_user.try(:consented_at) - - # Always if the user has not consented - return true if consented_at.blank? - - # Did not expire if no consent_time set, but user has consented at some point - return false if Setting.consent_time.blank? - - # Otherwise, expires when consent_time is newer than last consented_at - consented_at < Setting.consent_time - end - def consenting_user User.find_by id: session[:authenticated_user_id] end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index e8c169e20b38..fca3e2517d7c 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -49,11 +49,6 @@ def system_settings_tabs controller: '/admin/settings/projects_settings', label: :label_project_plural }, - { - name: 'attachments', - controller: '/admin/settings/attachments_settings', - label: :'attributes.attachments' - }, { name: 'repositories', controller: '/admin/settings/repositories_settings', diff --git a/app/helpers/user_consent_helper.rb b/app/helpers/user_consent_helper.rb index dbf945c51c1d..66d5a6dafbe8 100644 --- a/app/helpers/user_consent_helper.rb +++ b/app/helpers/user_consent_helper.rb @@ -37,16 +37,14 @@ def user_consent_required? end ## - # Gets consent instructions for the given user. + # Gets consent instructions. # - # @param user [User] The user to get instructions for. # @param locale [String] ISO-639-1 code for the desired locale (e.g. de, en, fr). # `I18n.locale` is set for each request individually depending # among other things on the user's Accept-Language headers. # @return [String] Instructions in the respective language. - def user_consent_instructions(_user, locale: I18n.locale) + def user_consent_instructions(locale) all = Setting.consent_info - all.fetch(locale.to_s) { all.values.first } end @@ -54,6 +52,8 @@ def consent_checkbox_label(locale: I18n.locale) I18n.t('consent.checkbox_label', locale:) end + private + def consent_configured? if Setting.consent_info.count == 0 Rails.logger.error 'Instance is configured to require consent, but no consent_info has been set.' diff --git a/app/models/attachment.rb b/app/models/attachment.rb index de87d4fd9f74..2dbce28730ef 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -29,10 +29,18 @@ require 'digest/md5' class Attachment < ApplicationRecord + enum status: { + uploaded: 0, + prepared: 1, + scanned: 2, + quarantined: 3, + rescan: 4 + }.freeze, _prefix: true + belongs_to :container, polymorphic: true belongs_to :author, class_name: 'User' - validates :author, :content_type, :filesize, presence: true + validates :author, :content_type, :filesize, :status, presence: true validates :description, length: { maximum: 255 } validate :filesize_below_allowed_maximum, @@ -62,10 +70,10 @@ class Attachment < ApplicationRecord mount_uploader :file, OpenProject::Configuration.file_uploader - after_commit :extract_fulltext, on: :create + after_commit :enqueue_jobs, on: :create, if: -> { !internal_container? } - scope :pending_direct_upload, -> { where(digest: "", downloads: -1) } - scope :not_pending_direct_upload, -> { where.not(digest: "", downloads: -1) } + scope :pending_direct_upload, -> { status_prepared } + scope :not_pending_direct_upload, -> { not_status_prepared } ## # Returns an URL if the attachment is stored in an external (fog) attachment storage @@ -122,7 +130,11 @@ def deletable?(user = User.current) end def prepared? - downloads == -1 + status_prepared? + end + + def pending_virus_scan? + status_uploaded? && Setting::VirusScanning.enabled? end # images are sent inline @@ -235,10 +247,18 @@ def copy!(&) attachment.save! end - def extract_fulltext - return unless OpenProject::Database.allows_tsv? && (!container || container.class.attachment_tsv_extracted?) + def enqueue_jobs + extract_fulltext - ExtractFulltextJob.perform_later(id) + if pending_virus_scan? + Attachments::VirusScanJob.perform_later(self) + end + end + + def extract_fulltext + if OpenProject::Database.allows_tsv? && (!container || container.class.attachment_tsv_extracted?) + Attachments::ExtractFulltextJob.perform_later(id) + end end # Extract the fulltext of any attachments where fulltext is still nil. @@ -252,9 +272,9 @@ def self.extract_fulltext_where_missing(run_now: true) .pluck(:id) .each do |id| if run_now - ExtractFulltextJob.perform_now(id) + Attachments::ExtractFulltextJob.perform_now(id) else - ExtractFulltextJob.perform_later(id) + Attachments::ExtractFulltextJob.perform_later(id) end end end @@ -263,7 +283,7 @@ def self.force_extract_fulltext return unless OpenProject::Database.allows_tsv? Attachment.pluck(:id).each do |id| - ExtractFulltextJob.perform_now(id) + Attachments::ExtractFulltextJob.perform_now(id) end end @@ -299,6 +319,10 @@ def pending_direct_upload? digest == "" && downloads == -1 end + def internal_container? + container&.is_a?(Export) + end + private def filesize_below_allowed_maximum @@ -307,10 +331,6 @@ def filesize_below_allowed_maximum end end - def internal_container? - container&.is_a?(Export) - end - def container_changed_more_than_once if container_id_changed_more_than_once? || container_type_changed_more_than_once? errors.add(:container, :unchangeable) diff --git a/app/models/members/scopes/visible.rb b/app/models/members/scopes/visible.rb index b4028f180d43..b9857d740a9b 100644 --- a/app/models/members/scopes/visible.rb +++ b/app/models/members/scopes/visible.rb @@ -45,10 +45,10 @@ def visible(user) def visible_for_non_admins(user) view_members = Project.allowed_to(user, :view_members) manage_members = Project.allowed_to(user, :manage_members) + view_work_packages = Project.allowed_to(user, :view_shared_work_packages) - project_scope = view_members.or(manage_members) - - where(project_id: project_scope.select(:id)) + where(project_id: view_members.or(manage_members).select(:id), entity_type: nil) + .or(where(project_id: view_work_packages.select(:id), entity_type: WorkPackage.name)) end def visible_for_admins diff --git a/app/models/role.rb b/app/models/role.rb index f492b43d96a6..6fa1006486da 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -77,7 +77,16 @@ def copy_from_role(source_role) inclusion: { in: ->(*) { Role.subclasses.map(&:to_s) } } def self.givable - where.not(builtin: [BUILTIN_NON_MEMBER, BUILTIN_ANONYMOUS]) + where + .not( + builtin: [ + Role::BUILTIN_NON_MEMBER, + Role::BUILTIN_ANONYMOUS, + Role::BUILTIN_WORK_PACKAGE_VIEWER, + Role::BUILTIN_WORK_PACKAGE_COMMENTER, + Role::BUILTIN_WORK_PACKAGE_EDITOR + ] + ) .order(Arel.sql('position')) end diff --git a/app/models/setting/virus_scanning.rb b/app/models/setting/virus_scanning.rb new file mode 100644 index 000000000000..91161f54fbf0 --- /dev/null +++ b/app/models/setting/virus_scanning.rb @@ -0,0 +1,35 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +class Setting + module VirusScanning + def self.enabled? + Setting.antivirus_scan_mode != :disabled && EnterpriseToken.allows_to?(:virus_scanning) + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 22e7c63f525a..bef894a13795 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -90,7 +90,6 @@ class User < Principal inverse_of: :user, dependent: :destroy - # Users blocked via brute force prevention # use lambda here, so time is evaluated on each query scope :blocked, -> { create_blocked_scope(self, true) } @@ -470,6 +469,17 @@ def anonymous? !logged? end + def consent_expired? + # Always if the user has not consented + return true if consented_at.blank? + + # Did not expire if no consent_time set, but user has consented at some point + return false if Setting.consent_time.blank? + + # Otherwise, expires when consent_time is newer than last consented_at + consented_at < Setting.consent_time + end + # Cheap version of Project.visible.count def number_of_known_projects if admin? diff --git a/app/seeders/demo_data/global_query_seeder.rb b/app/seeders/demo_data/global_query_seeder.rb index b6e535ab5942..4c918b2387f9 100644 --- a/app/seeders/demo_data/global_query_seeder.rb +++ b/app/seeders/demo_data/global_query_seeder.rb @@ -33,6 +33,10 @@ def seed_data! end end + def applicable? + Query.global.none? + end + private def seed_global_queries diff --git a/app/seeders/development_data/shared_work_packages_seeder.rb b/app/seeders/development_data/shared_work_packages_seeder.rb index 6abe5062e5a7..5bfccbcc581d 100644 --- a/app/seeders/development_data/shared_work_packages_seeder.rb +++ b/app/seeders/development_data/shared_work_packages_seeder.rb @@ -81,8 +81,8 @@ def work_package_attributes reference: :save_gotham, description: "Gotham is in trouble. It's your job to save it!", status: seed_data.find_reference(:default_status_new), - type: seed_data.find_reference(:default_type_epic), - priority: seed_data.find_reference(:default_priority_immediate) + type: seed_data.find_reference(:default_type_epic, :default_type_phase), + priority: seed_data.find_reference(:default_priority_immediate, :default_priority_high) }, { project:, @@ -92,7 +92,7 @@ def work_package_attributes description: 'Must be stopped before Gotham is doomed.', status: seed_data.find_reference(:default_status_new), type: seed_data.find_reference(:default_type_task), - priority: seed_data.find_reference(:default_priority_immediate) + priority: seed_data.find_reference(:default_priority_immediate, :default_priority_high) }, { project:, diff --git a/app/seeders/root_seeder.rb b/app/seeders/root_seeder.rb index 0537e57076e4..586f493b6671 100644 --- a/app/seeders/root_seeder.rb +++ b/app/seeders/root_seeder.rb @@ -29,10 +29,13 @@ # Seeds the minimum data required to run OpenProject (BasicDataSeeder, AdminUserSeeder) # as well as optional demo data (DemoDataSeeder) to give a user some orientation. class RootSeeder < Seeder - def initialize(seed_development_data: Rails.env.development?) + attr_reader :raise_on_unknown_language + + def initialize(seed_development_data: Rails.env.development?, raise_on_unknown_language: false) super() @seed_development_data = seed_development_data + @raise_on_unknown_language = raise_on_unknown_language load_available_seeders end @@ -174,7 +177,14 @@ def seed_plugins_data def desired_lang desired_lang = ENV.fetch('OPENPROJECT_SEED_LOCALE', Setting.default_language) - raise "Locale #{desired_lang} is not supported" if Redmine::I18n.all_languages.exclude?(desired_lang) + + if Redmine::I18n.all_languages.exclude?(desired_lang) + if raise_on_unknown_language + raise "Locale #{desired_lang} is not supported" + else + desired_lang = :en + end + end desired_lang end diff --git a/app/seeders/source/seed_data.rb b/app/seeders/source/seed_data.rb index 61da39acbcd2..82540ce6a9fe 100644 --- a/app/seeders/source/seed_data.rb +++ b/app/seeders/source/seed_data.rb @@ -43,15 +43,25 @@ def store_reference(reference, record) registry[reference] = record end - def find_reference(reference, default: :__unset__) + # Finds and returns the value associated with the given reference. + # + # @param reference [Symbol] The reference to search for. + # @param fallbacks [Array] Optional fallback references to search for if the primary reference is not found. + # @param default [Object] The default value to return if no reference or fallbacks are found. + # @return [Object, nil] The value associated with the reference, or nil if the reference is nil. + # @raise [ArgumentError] If no reference or fallbacks are found and no default value is provided. + def find_reference(reference, *fallbacks, default: :__unset__) return if reference.nil? - registry.fetch(reference) do - if default == :__unset__ - raise ArgumentError, "Nothing registered with reference #{reference.inspect}" - end - + existing_ref = [reference, *fallbacks].find { |ref| registry.key?(ref) } + if existing_ref + registry[existing_ref] + elsif default != :__unset__ default + else + references = [reference, *fallbacks].map(&:inspect) + message = "Nothing registered with #{'reference'.pluralize(references.count)} #{references.to_sentence(locale: false)}" + raise ArgumentError, message end end diff --git a/app/seeders/standard.yml b/app/seeders/standard.yml index 8eee51f18036..e64223af7f27 100644 --- a/app/seeders/standard.yml +++ b/app/seeders/standard.yml @@ -27,14 +27,17 @@ #++ priorities: - - t_name: Low + - reference: :default_priority_low + t_name: Low color_name: cyan-1 position: 1 - - t_name: Normal + - reference: :default_priority_normal + t_name: Normal color_name: blue-3 is_default: true position: 2 - - t_name: High + - reference: :default_priority_high + t_name: High color_name: yellow-7 position: 3 - reference: :default_priority_immediate diff --git a/app/services/attachments/clamav_service.rb b/app/services/attachments/clamav_service.rb new file mode 100644 index 000000000000..d069d0d8a5db --- /dev/null +++ b/app/services/attachments/clamav_service.rb @@ -0,0 +1,61 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Attachments + class ClamAVService + attr_reader :clamav_client + + def initialize(scan_mode = Setting.antivirus_scan_mode, connection_target = Setting.antivirus_scan_target) + options = clamav_client_options(scan_mode, connection_target) + @clamav_client = ClamAV::Client.new(**options) + end + + def scan(attachment) + file = attachment.diskfile + clamav_client.execute(ClamAV::Commands::InstreamCommand.new(file)) + end + + def ping + clamav_client.execute(ClamAV::Commands::PingCommand.new) + end + + private + + def clamav_client_options(scan_mode, connection_target) + case scan_mode + when :clamav_socket + { unix_socket: connection_target } + when :clamav_host + tcp_host, tcp_port = connection_target.split(':') + { tcp_host:, tcp_port: } + else + raise ArgumentError.new("Unknown clamav scan mode #{scan_mode}") + end + end + end +end diff --git a/app/services/attachments/set_attributes_service.rb b/app/services/attachments/set_attributes_service.rb index 99c7360cbaba..38fb7d536380 100644 --- a/app/services/attachments/set_attributes_service.rb +++ b/app/services/attachments/set_attributes_service.rb @@ -36,6 +36,10 @@ def set_attributes(params) def set_default_attributes(_params) model.author = user if model.author.nil? + + model.change_by_system do + model.status = model.internal_container? ? :scanned : :uploaded + end end end end diff --git a/app/services/attachments/set_prepared_attributes_service.rb b/app/services/attachments/set_prepared_attributes_service.rb index 73ab99f3157a..4da64362694a 100644 --- a/app/services/attachments/set_prepared_attributes_service.rb +++ b/app/services/attachments/set_prepared_attributes_service.rb @@ -47,7 +47,7 @@ def set_prepared_attributes(params) model.extend(OpenProject::ChangedBySystem) model.change_by_system do - model.downloads = -1 + model.status = :prepared # Set a preliminary content type as the file is not present # The content type will be updated by the FinishDirectUploadJob if necessary. model.content_type = params[:content_type].presence || OpenProject::ContentTypeDetector::SENSIBLE_DEFAULT diff --git a/app/services/authorization/enterprise_service.rb b/app/services/authorization/enterprise_service.rb index 45706430c01a..b8c92cd9957d 100644 --- a/app/services/authorization/enterprise_service.rb +++ b/app/services/authorization/enterprise_service.rb @@ -48,6 +48,7 @@ class Authorization::EnterpriseService work_package_query_relation_columns work_package_sharing one_drive_sharepoint_file_storage + virus_scanning ).freeze def initialize(token) diff --git a/app/services/projects/archive_service.rb b/app/services/projects/archive_service.rb index 28b20f7bc9e4..db0e8aaf1535 100644 --- a/app/services/projects/archive_service.rb +++ b/app/services/projects/archive_service.rb @@ -29,7 +29,7 @@ module Projects class ArchiveService < ::BaseServices::BaseContracted include Contracted - include Projects::Concerns::UpdateDemoData + prepend Projects::Concerns::UpdateDemoData def initialize(user:, model:, contract_class: Projects::ArchiveContract) super(user:, contract_class:) diff --git a/app/services/projects/concerns/update_demo_data.rb b/app/services/projects/concerns/update_demo_data.rb index 2fbb91ce679b..e923cf9fae9f 100644 --- a/app/services/projects/concerns/update_demo_data.rb +++ b/app/services/projects/concerns/update_demo_data.rb @@ -34,8 +34,8 @@ def after_perform(call) project = call.result # e.g. when one of the demo projects gets deleted or archived - if %w[your-scrum-project demo-project].include?(project.identifier) - Setting.demo_projects_available = !project.destroyed? && !project.archived? + if %w[demo-project].include?(project.identifier) + Setting.demo_projects_available = !project.destroyed? && !project.archived? && project.public? end super diff --git a/app/services/projects/delete_service.rb b/app/services/projects/delete_service.rb index 2acfb54e552e..110998d456b5 100644 --- a/app/services/projects/delete_service.rb +++ b/app/services/projects/delete_service.rb @@ -28,7 +28,7 @@ module Projects class DeleteService < ::BaseServices::Delete - include Projects::Concerns::UpdateDemoData + prepend Projects::Concerns::UpdateDemoData ## # Reference to the dependent projects that we're deleting diff --git a/app/services/projects/unarchive_service.rb b/app/services/projects/unarchive_service.rb index f0df6035a9f0..203b49944537 100644 --- a/app/services/projects/unarchive_service.rb +++ b/app/services/projects/unarchive_service.rb @@ -29,7 +29,7 @@ module Projects class UnarchiveService < ::BaseServices::BaseContracted include Contracted - include Projects::Concerns::UpdateDemoData + prepend Projects::Concerns::UpdateDemoData def initialize(user:, model:, contract_class: Projects::UnarchiveContract) super(user:, contract_class:) diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index b6da8ef729dd..1d04e3f0b4cb 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -28,6 +28,8 @@ module Projects class UpdateService < ::BaseServices::Update + prepend Projects::Concerns::UpdateDemoData + private attr_accessor :memoized_changes diff --git a/app/services/set_localization_service.rb b/app/services/set_localization_service.rb index 9249a5342640..013aeab942b2 100644 --- a/app/services/set_localization_service.rb +++ b/app/services/set_localization_service.rb @@ -1,3 +1,31 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + class SetLocalizationService attr_reader :user, :http_accept_header diff --git a/app/views/account/_register.html.erb b/app/views/account/_register.html.erb index 8331c2c9447d..8ea86ce868b0 100644 --- a/app/views/account/_register.html.erb +++ b/app/views/account/_register.html.erb @@ -90,7 +90,7 @@ See COPYRIGHT and LICENSE files for more details. <% if user_consent_required? %>
- <%= render partial: 'account/user_consent_check', locals: { consenting_user: @user } %> + <%= render partial: 'account/user_consent_check' %> <% end %> <%= render partial: 'account/auth_providers', locals: { omniauth_title: I18n.t('account.signup_with_auth_provider'), wide: true } %> diff --git a/app/views/account/_user_consent_check.html.erb b/app/views/account/_user_consent_check.html.erb index c59978007828..26020ae4bd9c 100644 --- a/app/views/account/_user_consent_check.html.erb +++ b/app/views/account/_user_consent_check.html.erb @@ -1,6 +1,6 @@