diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ebaed00..521d880b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,7 @@ jobs: cp config/secrets.yml.ci config/secrets.yml cp config/docker.yml.erb.ci config/docker.yml.erb cp config/mnemosyne.yml.ci config/mnemosyne.yml + cp config/content_security_policy.yml.ci config/content_security_policy.yml - name: Create database env: diff --git a/.gitignore b/.gitignore index 1e2bbb2b..759d2f78 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /config/mnemosyne.yml /config/secrets.yml /config/docker.yml.erb +/config/content_security_policy.yml /coverage /log/*.* /public/assets diff --git a/Gemfile b/Gemfile index 571e9bdd..df74c7f2 100644 --- a/Gemfile +++ b/Gemfile @@ -33,7 +33,7 @@ gem 'prometheus_exporter' gem 'pry-byebug' gem 'puma' gem 'pundit' -gem 'rails', '~> 6.1.6' +gem 'rails', '~> 6.1.7' gem 'rails_admin', '< 3.0.0' # Blocked by https://github.com/railsadminteam/rails_admin/issues/3490 gem 'rails-i18n' gem 'rails-timeago' @@ -42,7 +42,7 @@ gem 'rest-client' gem 'rubytree' gem 'rubyzip' gem 'sass-rails' -gem 'shakapacker', '6.5.1' +gem 'shakapacker', '6.5.2' gem 'slim-rails' gem 'sorcery' # Causes a deprecation warning in Rails 6.0+, see: https://github.com/Sorcery/sorcery/pull/255 gem 'telegraf' @@ -55,6 +55,10 @@ gem 'mnemosyne-ruby' gem 'sentry-rails' gem 'sentry-ruby' +group :development do + gem 'web-console' +end + group :development, :staging do gem 'better_errors' gem 'binding_of_caller' @@ -67,7 +71,6 @@ group :development, :staging do gem 'rubocop-performance' gem 'rubocop-rails', require: false gem 'rubocop-rspec' - gem 'web-console' end group :development, :test, :staging do diff --git a/Gemfile.lock b/Gemfile.lock index d24c77ff..f120bbbd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -13,71 +13,71 @@ GEM remote: https://rubygems.org/ specs: ZenTest (4.12.1) - actioncable (6.1.6.1) - actionpack (= 6.1.6.1) - activesupport (= 6.1.6.1) + actioncable (6.1.7) + actionpack (= 6.1.7) + activesupport (= 6.1.7) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.6.1) - actionpack (= 6.1.6.1) - activejob (= 6.1.6.1) - activerecord (= 6.1.6.1) - activestorage (= 6.1.6.1) - activesupport (= 6.1.6.1) + actionmailbox (6.1.7) + actionpack (= 6.1.7) + activejob (= 6.1.7) + activerecord (= 6.1.7) + activestorage (= 6.1.7) + activesupport (= 6.1.7) mail (>= 2.7.1) - actionmailer (6.1.6.1) - actionpack (= 6.1.6.1) - actionview (= 6.1.6.1) - activejob (= 6.1.6.1) - activesupport (= 6.1.6.1) + actionmailer (6.1.7) + actionpack (= 6.1.7) + actionview (= 6.1.7) + activejob (= 6.1.7) + activesupport (= 6.1.7) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.6.1) - actionview (= 6.1.6.1) - activesupport (= 6.1.6.1) + actionpack (6.1.7) + actionview (= 6.1.7) + activesupport (= 6.1.7) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.6.1) - actionpack (= 6.1.6.1) - activerecord (= 6.1.6.1) - activestorage (= 6.1.6.1) - activesupport (= 6.1.6.1) + actiontext (6.1.7) + actionpack (= 6.1.7) + activerecord (= 6.1.7) + activestorage (= 6.1.7) + activesupport (= 6.1.7) nokogiri (>= 1.8.5) - actionview (6.1.6.1) - activesupport (= 6.1.6.1) + actionview (6.1.7) + activesupport (= 6.1.7) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.6.1) - activesupport (= 6.1.6.1) + activejob (6.1.7) + activesupport (= 6.1.7) globalid (>= 0.3.6) - activemodel (6.1.6.1) - activesupport (= 6.1.6.1) + activemodel (6.1.7) + activesupport (= 6.1.7) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (6.1.6.1) - activemodel (= 6.1.6.1) - activesupport (= 6.1.6.1) - activestorage (6.1.6.1) - actionpack (= 6.1.6.1) - activejob (= 6.1.6.1) - activerecord (= 6.1.6.1) - activesupport (= 6.1.6.1) + activerecord (6.1.7) + activemodel (= 6.1.7) + activesupport (= 6.1.7) + activestorage (6.1.7) + actionpack (= 6.1.7) + activejob (= 6.1.7) + activerecord (= 6.1.7) + activesupport (= 6.1.7) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.6.1) + activesupport (6.1.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) amq-protocol (2.3.2) ast (2.4.2) autotest (5.0.0) @@ -164,7 +164,7 @@ GEM websocket-driver (>= 0.5.1) ffi (1.15.5) forgery (0.8.1) - glob (0.3.0) + glob (0.3.1) globalid (1.0.0) activesupport (>= 5.0) haml (5.2.2) @@ -179,7 +179,7 @@ GEM domain_name (~> 0.5) i18n (1.12.0) concurrent-ruby (~> 1.0) - i18n-js (4.0.0) + i18n-js (4.0.1) glob i18n image_processing (1.12.2) @@ -206,7 +206,7 @@ GEM hana (~> 1.3) regexp_parser (~> 2.0) uri_template (~> 0.7) - jwt (2.4.1) + jwt (2.5.0) kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -254,7 +254,7 @@ GEM mnemosyne-ruby (1.13.0) activesupport (>= 4) bunny - msgpack (1.5.4) + msgpack (1.5.6) multi_json (1.15.0) multi_xml (0.6.0) nested_form (0.3.2) @@ -281,7 +281,7 @@ GEM racc (~> 1.4) nyan-cat-formatter (0.12.0) rspec (>= 2.99, >= 2.14.2, < 4) - oauth (0.5.10) + oauth (0.5.14) oauth2 (1.4.10) faraday (>= 0.17.3, < 3.0) jwt (>= 1.0, < 3.0) @@ -305,8 +305,8 @@ GEM pry (>= 0.13, < 0.15) pry-rails (0.3.9) pry (>= 0.10.4) - public_suffix (4.0.7) - puma (5.6.4) + public_suffix (5.0.0) + puma (5.6.5) nio4r (~> 2.0) pundit (2.2.0) activesupport (>= 3.0.0) @@ -321,20 +321,20 @@ GEM rack rack-test (2.0.2) rack (>= 1.3) - rails (6.1.6.1) - actioncable (= 6.1.6.1) - actionmailbox (= 6.1.6.1) - actionmailer (= 6.1.6.1) - actionpack (= 6.1.6.1) - actiontext (= 6.1.6.1) - actionview (= 6.1.6.1) - activejob (= 6.1.6.1) - activemodel (= 6.1.6.1) - activerecord (= 6.1.6.1) - activestorage (= 6.1.6.1) - activesupport (= 6.1.6.1) + rails (6.1.7) + actioncable (= 6.1.7) + actionmailbox (= 6.1.7) + actionmailer (= 6.1.7) + actionpack (= 6.1.7) + actiontext (= 6.1.7) + actionview (= 6.1.7) + activejob (= 6.1.7) + activemodel (= 6.1.7) + activerecord (= 6.1.7) + activestorage (= 6.1.7) + activesupport (= 6.1.7) bundler (>= 1.15.0) - railties (= 6.1.6.1) + railties (= 6.1.7) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -363,9 +363,9 @@ GEM rails (>= 5.0, < 7) remotipart (~> 1.3) sassc-rails (>= 1.3, < 3) - railties (6.1.6.1) - actionpack (= 6.1.6.1) - activesupport (= 6.1.6.1) + railties (6.1.7) + actionpack (= 6.1.7) + activesupport (= 6.1.7) method_source rake (>= 12.2) thor (~> 1.0) @@ -375,7 +375,7 @@ GEM activerecord (>= 6.1.5) activesupport (>= 6.1.5) i18n - rb-fsevent (0.11.1) + rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) rbtree (0.4.5) @@ -414,7 +414,7 @@ GEM rspec-mocks (~> 3.10) rspec-support (~> 3.10) rspec-support (3.11.0) - rubocop (1.35.0) + rubocop (1.36.0) json (~> 2.3) parallel (~> 1.10) parser (>= 3.1.2.1) @@ -463,8 +463,8 @@ GEM sentry-ruby (~> 5.4.2) sentry-ruby (5.4.2) concurrent-ruby (~> 1.0, >= 1.0.2) - set (1.0.2) - shakapacker (6.5.1) + set (1.0.3) + shakapacker (6.5.2) activesupport (>= 5.2) rack-proxy (>= 0.6.1) railties (>= 5.2) @@ -499,9 +499,9 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - ssrf_filter (1.0.8) + ssrf_filter (1.1.1) strscan (3.0.4) - telegraf (2.1.0) + telegraf (2.1.1) influxdb temple (0.8.2) thor (1.2.1) @@ -590,7 +590,7 @@ DEPENDENCIES puma pundit rack-mini-profiler - rails (~> 6.1.6) + rails (~> 6.1.7) rails-controller-testing rails-i18n rails-timeago @@ -611,7 +611,7 @@ DEPENDENCIES selenium-webdriver sentry-rails sentry-ruby - shakapacker (= 6.5.1) + shakapacker (= 6.5.2) shoulda-matchers simplecov slim-rails diff --git a/app/assets/javascripts/base.js b/app/assets/javascripts/base.js index 3e39eed3..ef524c25 100644 --- a/app/assets/javascripts/base.js +++ b/app/assets/javascripts/base.js @@ -32,4 +32,7 @@ $.jstree.defaults.core.worker = false; // See https://github.com/rails/jquery-ujs/issues/456 for details $(document).on('turbolinks:load', function(){ $.rails.refreshCSRFTokens(); + $('.reloadCurrentPage').on('click', function() { + window.location.reload(); + }); }); diff --git a/app/assets/javascripts/editor/editor.js.erb b/app/assets/javascripts/editor/editor.js.erb index 3df79531..05b472cd 100644 --- a/app/assets/javascripts/editor/editor.js.erb +++ b/app/assets/javascripts/editor/editor.js.erb @@ -78,22 +78,12 @@ var CodeOceanEditor = { if ($('#output-' + index).isPresent()) { return $('#output-' + index); } else { - var element = $('
').attr('id', 'output-' + index); + var element = $('').attr('id', 'output-' + index); $('#output').append(element); return element; } }, - findOrCreateRenderElement: function (index) { - if ($('#render-' + index).isPresent()) { - return $('#render-' + index); - } else { - var element = $('').attr('id', 'render-' + index); - $('#render').append(element); - return element; - } - }, - getCardClass: function (result) { if (result.file_role === 'teacher_defined_linter') { return 'card bg-info text-white' @@ -648,7 +638,7 @@ var CodeOceanEditor = { augmentStacktraceInOutput: function () { if (this.tracepositions_regex) { - $('#output>pre').each($.proxy(function(index, element) { + $('#output > .output-element').each($.proxy(function(index, element) { element = $(element) const text = _.escape(element.text()); @@ -656,16 +646,11 @@ var CodeOceanEditor = { let matches; - // Switch both lines below to enable the output of images and rendertags. - // Also consider `printOutput` in evaluation.js - - // let augmented_text = element.text(); let augmented_text = element.html(); while (matches = this.tracepositions_regex.exec(text)) { const frame = $('div.frame[data-filename="' + matches[1] + '"]') if (frame.length > 0) { - // augmented_text = augmented_text.replace(new RegExp(matches[0], 'g'), "" + matches[0] + ""); augmented_text = augmented_text.replace(new RegExp(_.unescape(matches[0]), 'g'), "" + matches[0] + ""); } } diff --git a/app/assets/javascripts/editor/evaluation.js b/app/assets/javascripts/editor/evaluation.js index 6fbec44a..31666c0c 100644 --- a/app/assets/javascripts/editor/evaluation.js +++ b/app/assets/javascripts/editor/evaluation.js @@ -174,11 +174,6 @@ CodeOceanEditorEvaluation = { /** * Output-Logic */ - renderWebsocketOutput: function (msg) { - var element = this.findOrCreateRenderElement(0); - element.append(msg.data); - }, - printWebsocketOutput: function (msg) { if (!msg.data || msg.data === "\r") { return; @@ -189,7 +184,7 @@ CodeOceanEditorEvaluation = { }, clearOutput: function () { - $('#output pre').remove(); + $('#output > .output-element').remove(); CodeOceanEditorTurtle.hideCanvas(); }, @@ -207,43 +202,54 @@ CodeOceanEditorEvaluation = { return; } - if (output.stdout !== undefined && !output.stdout.startsWith("
'); + + if (sanitizedStdout !== '') { + if (colorize) { + pre.addClass('text-success'); + } + pre.append(sanitizedStdout) } - var element = this.findOrCreateOutputElement(index); - // Switch all four lines below to enable the output of images and render
tags. - // Also consider `augmentStacktraceInOutput` in editor.js.erb - if (!colorize) { - if (output.stdout !== undefined && output.stdout !== '') { - output.stdout = output.stdout.replace(this.nonPrintableRegEx, "") - - element.append(output.stdout) - //element.text(element.text() + output.stdout) + if (sanitizedStderr !== '') { + if (colorize) { + pre.addClass('text-warning'); + } else { + pre.append('StdErr: '); } - - if (output.stderr !== undefined && output.stderr !== '') { - output.stderr = output.stderr.replace(this.nonPrintableRegEx, "") - - element.append('StdErr: ' + output.stderr); - //element.text('StdErr: ' + element.text() + output.stderr); - } - - } else if (output.stderr) { - output.stderr = output.stderr.replace(this.nonPrintableRegEx, "") - - element.addClass('text-warning').append(output.stderr); - //element.addClass('text-warning').text(element.text() + output.stderr); - this.QaApiOutputBuffer.stderr += output.stderr; - } else if (output.stdout) { - output.stdout = output.stdout.replace(this.nonPrintableRegEx, "") - - element.addClass('text-success').append(output.stdout); - //element.addClass('text-success').text(element.text() + output.stdout); - this.QaApiOutputBuffer.stdout += output.stdout; - } else { - element.addClass('text-muted').text($('#output').data('message-no-output')); + pre.append(sanitizedStderr); } + + if (sanitizedStdout === '' && sanitizedStderr === '') { + if (colorize) { + pre.addClass('text-muted'); + } + pre.text($('#output').data('message-no-output')) + } + + element.append(pre); + }, + + sanitizeOutput: function (rawContent) { + let sanitizedContent = _.escape(rawContent).replace(this.nonPrintableRegEx, ""); + + if (rawContent !== undefined && rawContent.trim().startsWith("
document + const parsedElement = doc.firstChild.lastChild.firstChild; + + if (parsedElement.src.startsWith("data:image")) { + const sanitizedImg = document.createElement('img'); + sanitizedImg.src = parsedElement.src; + sanitizedContent = sanitizedImg.outerHTML; + } + } + + return sanitizedContent; }, getDeadlineInformation: function(deadline, translation_key, otherwise) { diff --git a/app/assets/javascripts/editor/execution.js b/app/assets/javascripts/editor/execution.js index ec1784c9..2fd6245b 100644 --- a/app/assets/javascripts/editor/execution.js +++ b/app/assets/javascripts/editor/execution.js @@ -44,7 +44,7 @@ CodeOceanEditorWebsocket = { this.websocket.on('clear', this.clearOutput.bind(this)); this.websocket.on('turtle', this.handleTurtleCommand.bind(this)); this.websocket.on('turtlebatch', this.handleTurtlebatchCommand.bind(this)); - this.websocket.on('render', this.renderWebsocketOutput.bind(this)); + this.websocket.on('render', this.printWebsocketOutput.bind(this)); this.websocket.on('exit', this.handleExitCommand.bind(this)); this.websocket.on('status', this.showStatus.bind(this)); this.websocket.on('hint', this.showHint.bind(this)); diff --git a/app/assets/javascripts/pagedown/pagedown.js.erb b/app/assets/javascripts/pagedown/pagedown.js.erb index 63b8307e..4ab4062c 100644 --- a/app/assets/javascripts/pagedown/pagedown.js.erb +++ b/app/assets/javascripts/pagedown/pagedown.js.erb @@ -7,7 +7,7 @@ renderPagedown = function() { $(".wmd-output").each(function (i) { - const converter = new Markdown.Converter(); + const converter = Markdown.getSanitizingConverter(); const content = $(this).html(); return $(this).html(converter.makeHtml(content)); }) @@ -20,7 +20,7 @@ createPagedownEditor = function( selector, context ) { return; } const attr = $(input).attr('id').split('wmd-input')[1]; - const converter = new Markdown.Converter(); + const converter = Markdown.getSanitizingConverter(); Markdown.Extra.init(converter); const help = { handler() { diff --git a/app/assets/stylesheets/base.css.scss b/app/assets/stylesheets/base.css.scss index 09235a88..4f5a79f0 100644 --- a/app/assets/stylesheets/base.css.scss +++ b/app/assets/stylesheets/base.css.scss @@ -25,7 +25,7 @@ i.fa-solid, i.fa-regular, i.fa-solid { margin-right: 0.5em; } -pre { +pre, .output-element { background-color: #FAFAFA; margin: 0; padding: .25rem!important; diff --git a/app/assets/stylesheets/editor.css.scss b/app/assets/stylesheets/editor.css.scss index 7d2ae714..77e4eb2d 100644 --- a/app/assets/stylesheets/editor.css.scss +++ b/app/assets/stylesheets/editor.css.scss @@ -20,6 +20,9 @@ } } +#content-left-sidebar, #content-right-sidebar { + min-height: 250px; +} .frame { display: none; @@ -77,20 +80,13 @@ overflow: auto; } -#outputInformation { - #output { - max-height: 500px; - width: 100%; +#output { + white-space: pre; + font-family: var(--bs-font-monospace); + font-size: 14px; + + .output-element { overflow: auto; - margin: 2em 0; - - p { - margin: 0.5em; - } - - pre + pre { - margin-top: 1em; - } } } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 173f1e25..a08e780a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -9,7 +9,7 @@ class ApplicationController < ActionController::Base after_action :verify_authorized, except: %i[welcome] around_action :mnemosyne_trace around_action :switch_locale - before_action :set_sentry_context, :allow_iframe_requests, :load_embed_options + before_action :set_sentry_context, :load_embed_options protect_from_forgery(with: :exception, prepend: true) rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized rescue_from ActionController::InvalidAuthenticityToken, with: :render_csrf_error @@ -40,7 +40,10 @@ class ApplicationController < ActionController::Base token = AuthenticationToken.find_by(shared_secret: params[:token]) return unless token - auto_login(token.user) if token.expire_at.future? + if token.expire_at.future? + token.update(expire_at: Time.zone.now) + auto_login(token.user) + end end def set_sentry_context @@ -93,10 +96,6 @@ class ApplicationController < ActionController::Base # Show root page end - def allow_iframe_requests - response.headers.delete('X-Frame-Options') - end - def load_embed_options @embed_options = if session[:embed_options].present? && session[:embed_options].is_a?(Hash) session[:embed_options].symbolize_keys diff --git a/app/controllers/code_ocean/files_controller.rb b/app/controllers/code_ocean/files_controller.rb index 79331f6a..4d87684e 100644 --- a/app/controllers/code_ocean/files_controller.rb +++ b/app/controllers/code_ocean/files_controller.rb @@ -10,6 +10,15 @@ module CodeOcean end private :authorize! + def show_protected_upload + @file = CodeOcean::File.find(params[:id]) + authorize! + raise Pundit::NotAuthorizedError if @embed_options[:disable_download] || @file.name_with_extension != params[:filename] + + real_location = Pathname(@file.native_file.current_path).realpath + send_file(real_location, type: @file.native_file.content_type, filename: @file.name_with_extension, disposition: 'attachment') + end + def create @file = CodeOcean::File.new(file_params) if @file.file_template_id diff --git a/app/controllers/concerns/file_parameters.rb b/app/controllers/concerns/file_parameters.rb index dfe4b06b..5c4f48f8 100644 --- a/app/controllers/concerns/file_parameters.rb +++ b/app/controllers/concerns/file_parameters.rb @@ -5,8 +5,14 @@ module FileParameters if exercise && params params.reject do |_, file_attributes| file = CodeOcean::File.find_by(id: file_attributes[:file_id]) + next true if file.nil? || file.hidden || file.read_only # avoid that public files from other contexts can be created - file.nil? || file.hidden || file.read_only || (file.context_type == 'Exercise' && file.context_id != exercise.id) || (file.context_type == 'CommunitySolution' && controller_name != 'community_solutions') + # `next` is similar to an early return and will proceed with the next iteration of the loop + next true if file.context_type == 'Exercise' && file.context_id != exercise.id + next true if file.context_type == 'Submission' && file.context.user != current_user + next true if file.context_type == 'CommunitySolution' && controller_name != 'community_solutions' + + false end else [] diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index fafad3c2..5d5eec43 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -22,6 +22,8 @@ class ExercisesController < ApplicationController skip_after_action :verify_authorized, only: %i[import_exercise import_uuid_check] skip_after_action :verify_policy_scoped, only: %i[import_exercise import_uuid_check], raise: false + rescue_from Pundit::NotAuthorizedError, with: :not_authorized_for_exercise + def authorize! authorize(@exercise || @exercises) end @@ -294,8 +296,6 @@ class ExercisesController < ApplicationController private :update_exercise_tips def implement - redirect_to(@exercise, alert: t('exercises.implement.unpublished')) if @exercise.unpublished? && current_user.role != 'admin' && current_user.role != 'teacher' # TODO: TESTESTEST - redirect_to(@exercise, alert: t('exercises.implement.no_files')) unless @exercise.files.visible.exists? user_solved_exercise = @exercise.solved_by?(current_user) count_interventions_today = UserExerciseIntervention.where(user: current_user).where('created_at >= ?', Time.zone.now.beginning_of_day).count @@ -324,6 +324,8 @@ class ExercisesController < ApplicationController end end + @embed_options[:disable_score] = true unless @exercise.teacher_defined_assessment? + @hide_rfc_button = @embed_options[:disable_rfc] @search = Search.new @@ -432,6 +434,19 @@ class ExercisesController < ApplicationController authorize! end + def not_authorized_for_exercise(_exception) + return render_not_authorized unless current_user + return render_not_authorized unless %w[implement working_times intervention search reload].include?(action_name) + + if current_user.admin? || current_user.teacher? + redirect_to(@exercise, alert: t('exercises.implement.unpublished')) if @exercise.unpublished? + redirect_to(@exercise, alert: t('exercises.implement.no_files')) unless @exercise.files.visible.exists? + else + render_not_authorized + end + end + private :not_authorized_for_exercise + def set_execution_environments @execution_environments = ExecutionEnvironment.all.order(:name) end diff --git a/app/controllers/external_users_controller.rb b/app/controllers/external_users_controller.rb index 95fa2634..10f7a77c 100644 --- a/app/controllers/external_users_controller.rb +++ b/app/controllers/external_users_controller.rb @@ -9,7 +9,7 @@ class ExternalUsersController < ApplicationController private :authorize! def index - @search = ExternalUser.ransack(params[:q]) + @search = ExternalUser.ransack(params[:q], {auth_object: current_user}) @users = @search.result.in_study_group_of(current_user).includes(:consumer).paginate(page: params[:page], per_page: per_page_param) authorize! end @@ -43,15 +43,15 @@ class ExternalUsersController < ApplicationController (created_at - lag(created_at) over (PARTITION BY user_id, exercise_id ORDER BY created_at)) AS working_time FROM submissions - WHERE user_id = #{@user.id} + WHERE #{ExternalUser.sanitize_sql(['user_id = ?', @user.id])} AND user_type = 'ExternalUser' - #{current_user.admin? ? '' : "AND study_group_id IN (#{current_user.study_groups.pluck(:id).join(', ')}) AND cause = 'submit'"} + #{current_user.admin? ? '' : "AND #{ExternalUser.sanitize_sql(['study_group_id IN (?)', current_user.study_groups.pluck(:id)])} AND cause = 'submit'"} GROUP BY exercise_id, user_id, id ) AS foo ) AS bar - #{tag.nil? ? '' : " JOIN exercise_tags et ON et.exercise_id = bar.exercise_id AND et.tag_id = #{tag} "} + #{tag.nil? ? '' : " JOIN exercise_tags et ON et.exercise_id = bar.exercise_id AND #{ExternalUser.sanitize_sql(['et.tag_id = ?', tag])}"} GROUP BY user_id, bar.exercise_id; " @@ -60,10 +60,14 @@ class ExternalUsersController < ApplicationController def statistics @user = ExternalUser.find(params[:id]) authorize! + if params[:tag].present? + tag = Tag.find(params[:tag]) + authorize(tag, :show?) + end statistics = {} - ApplicationRecord.connection.execute(working_time_query(params[:tag])).each do |tuple| + ApplicationRecord.connection.execute(working_time_query(tag&.id)).each do |tuple| statistics[tuple['exercise_id'].to_i] = tuple end diff --git a/app/controllers/internal_users_controller.rb b/app/controllers/internal_users_controller.rb index aa528d25..a5125fc6 100644 --- a/app/controllers/internal_users_controller.rb +++ b/app/controllers/internal_users_controller.rb @@ -67,7 +67,7 @@ class InternalUsersController < ApplicationController end def index - @search = InternalUser.ransack(params[:q]) + @search = InternalUser.ransack(params[:q], {auth_object: current_user}) @users = @search.result.includes(:consumer).order(:name).paginate(page: params[:page], per_page: per_page_param) authorize! end diff --git a/app/controllers/proxy_exercises_controller.rb b/app/controllers/proxy_exercises_controller.rb index 94074521..178c26c8 100644 --- a/app/controllers/proxy_exercises_controller.rb +++ b/app/controllers/proxy_exercises_controller.rb @@ -70,12 +70,9 @@ class ProxyExercisesController < ApplicationController def show @search = @proxy_exercise.exercises.ransack - @exercises = @proxy_exercise.exercises.ransack.result.order(:title) # @search.result.order(:title) + @exercises = @proxy_exercise.exercises.ransack.result.order(:title) end - # we might want to think about auth here - def reload; end - def update myparams = proxy_exercise_params myparams[:exercises] = Exercise.find(myparams[:exercise_ids].compact_blank) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 680af5d7..71fe97f5 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -12,7 +12,17 @@ class SubmissionsController < ApplicationController before_action :set_testrun, only: %i[run score test] before_action :set_files, only: %i[download show] before_action :set_files_and_specific_file, only: %i[download_file render_file run test] - before_action :set_mime_type, only: %i[download_file render_file] + before_action :set_content_type_nosniff, only: %i[download download_file render_file] + + # Overwrite the CSP header for the :render_file action + content_security_policy only: :render_file do |policy| + policy.img_src :none + policy.script_src :none + policy.font_src :none + policy.style_src :none + policy.connect_src :none + policy.form_action :none + end def create @submission = Submission.new(submission_params) @@ -56,7 +66,11 @@ class SubmissionsController < ApplicationController def download_file raise Pundit::NotAuthorizedError if @embed_options[:disable_download] - send_data(@file.read, filename: @file.name_with_extension) + if @file.native_file? + redirect_to protected_upload_path(id: @file.id, filename: @file.name_with_extension) + else + send_data(@file.content, filename: @file.name_with_extension, disposition: 'attachment') + end end def index @@ -66,13 +80,13 @@ class SubmissionsController < ApplicationController end def render_file - if @file.native_file? - send_data(@file.read, filename: @file.name_with_extension, disposition: 'inline') - else - render(plain: @file.content) - end + # If a file should not be downloaded, it should not be rendered either + raise Pundit::NotAuthorizedError if @embed_options[:disable_download] + + send_data(@file.read, filename: @file.name_with_extension, disposition: 'inline') end + # rubocop:disable Metrics/CyclomaticComplexity def run # These method-local socket variables are required in order to use one socket # in the callbacks of the other socket. As the callbacks for the client socket @@ -83,7 +97,7 @@ class SubmissionsController < ApplicationController client_socket = tubesock client_socket.onopen do |_event| - kill_client_socket(client_socket) if @embed_options[:disable_run] + return kill_client_socket(client_socket) if @embed_options[:disable_run] end client_socket.onclose do |_event| @@ -167,7 +181,8 @@ class SubmissionsController < ApplicationController @testrun[:status] = :failed "\n#{t('exercises.implement.exit_failure', timestamp: l(Time.zone.now, format: :short), exit_code: exit_code)}" end - send_and_store client_socket, {cmd: :write, stream: :stdout, data: "#{exit_statement}\n"} + stream = @testrun[:status] == :ok ? :stdout : :stderr + send_and_store client_socket, {cmd: :write, stream: stream, data: "#{exit_statement}\n"} if exit_code == 137 send_and_store client_socket, {cmd: :status, status: :out_of_memory} @testrun[:status] = :out_of_memory @@ -194,12 +209,13 @@ class SubmissionsController < ApplicationController ensure save_testrun_output 'run' end + # rubocop:enable Metrics/CyclomaticComplexity: def score hijack do |tubesock| tubesock.onopen do |_event| switch_locale do - kill_client_socket(tubesock) if @embed_options[:disable_score] + return kill_client_socket(tubesock) if @embed_options[:disable_score] || !@submission.exercise.teacher_defined_assessment? # The score is stored separately, we can forward it to the client immediately tubesock.send_data(JSON.dump(@submission.calculate_score)) @@ -226,7 +242,7 @@ class SubmissionsController < ApplicationController hijack do |tubesock| tubesock.onopen do |_event| switch_locale do - kill_client_socket(tubesock) if @embed_options[:disable_run] + return kill_client_socket(tubesock) if @embed_options[:disable_run] # The score is stored separately, we can forward it to the client immediately tubesock.send_data(JSON.dump(@submission.test(@file))) @@ -363,9 +379,9 @@ class SubmissionsController < ApplicationController @files = @submission.collect_files.select(&:visible) end - def set_mime_type - @mime_type = Mime::Type.lookup_by_extension(@file.file_type.file_extension.gsub(/^\./, '')) - response.headers['Content-Type'] = @mime_type.to_s + def set_content_type_nosniff + # When sending a file, we want to ensure that browsers follow our Content-Type header + response.headers['X-Content-Type-Options'] = 'nosniff' end def set_submission diff --git a/app/controllers/user_exercise_feedbacks_controller.rb b/app/controllers/user_exercise_feedbacks_controller.rb index e07b9c4a..1daa0c46 100644 --- a/app/controllers/user_exercise_feedbacks_controller.rb +++ b/app/controllers/user_exercise_feedbacks_controller.rb @@ -74,7 +74,7 @@ class UserExerciseFeedbacksController < ApplicationController def update submission = begin - current_user.submissions.where(exercise_id: @exercise.id).order('created_at DESC').first + current_user.submissions.where(exercise_id: @exercise.id).order('created_at DESC').final.first rescue StandardError nil end @@ -127,14 +127,16 @@ class UserExerciseFeedbacksController < ApplicationController user_type = current_user.class.name latest_submission = Submission .where(user_id: user_id, user_type: user_type, exercise_id: exercise_id) - .order(created_at: :desc).first + .order(created_at: :desc).final.first + + authorize(latest_submission, :show?) params[:user_exercise_feedback] .permit(:feedback_text, :difficulty, :exercise_id, :user_estimated_worktime) .merge(user_id: user_id, user_type: user_type, submission: latest_submission, - normalized_score: latest_submission.normalized_score) + normalized_score: latest_submission&.normalized_score) end def validate_inputs(uef_params) diff --git a/app/helpers/exercise_helper.rb b/app/helpers/exercise_helper.rb index 26343917..4302a3fb 100644 --- a/app/helpers/exercise_helper.rb +++ b/app/helpers/exercise_helper.rb @@ -8,7 +8,7 @@ module ExerciseHelper end def qa_js_tag - javascript_include_tag "#{qa_url}/assets/qa_api.js" + javascript_include_tag "#{qa_url}/assets/qa_api.js", integrity: true, crossorigin: 'anonymous' end def qa_url diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index ffa928d1..1bc42c89 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -23,7 +23,7 @@ class UserMailer < ApplicationMailer token = AuthenticationToken.generate!(request_for_comment.user) @receiver_displayname = request_for_comment.user.displayname @commenting_user_displayname = commenting_user.displayname - @comment_text = comment.text + @comment_text = ERB::Util.html_escape comment.text @rfc_link = request_for_comment_url(request_for_comment, token: token.shared_secret) mail( subject: t('mailers.user_mailer.got_new_comment.subject', @@ -35,7 +35,7 @@ class UserMailer < ApplicationMailer token = AuthenticationToken.generate!(subscription.user) @receiver_displayname = subscription.user.displayname @author_displayname = from_user.displayname - @comment_text = comment.text + @comment_text = ERB::Util.html_escape comment.text @rfc_link = request_for_comment_url(subscription.request_for_comment, token: token.shared_secret) @unsubscribe_link = unsubscribe_subscription_url(subscription) mail( @@ -45,10 +45,10 @@ class UserMailer < ApplicationMailer end def send_thank_you_note(request_for_comment, receiver) - token = AuthenticationToken.generate!(request_for_comment.user) + token = AuthenticationToken.generate!(receiver) @receiver_displayname = receiver.displayname @author = request_for_comment.user.displayname - @thank_you_note = request_for_comment.thank_you_note + @thank_you_note = ERB::Util.html_escape request_for_comment.thank_you_note @rfc_link = request_for_comment_url(request_for_comment, token: token.shared_secret) mail(subject: t('mailers.user_mailer.send_thank_you_note.subject', author: @author), to: receiver.email) end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 2852279e..ae82f5a8 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -4,6 +4,7 @@ class ApplicationRecord < ActiveRecord::Base self.abstract_class = true before_validation :strip_strings + before_validation :remove_null_bytes def strip_strings # trim whitespace from beginning and end of string attributes @@ -16,6 +17,15 @@ class ApplicationRecord < ActiveRecord::Base end end + def remove_null_bytes + # remove null bytes from string attributes + attribute_names.each do |name| + if send(name.to_sym).respond_to?(:tr) + send("#{name}=".to_sym, send(name).tr("\0", '')) + end + end + end + def self.ransackable_associations(_auth_object = nil) [] end diff --git a/app/models/code_ocean/file.rb b/app/models/code_ocean/file.rb index 67796eae..5083f8c4 100644 --- a/app/models/code_ocean/file.rb +++ b/app/models/code_ocean/file.rb @@ -58,8 +58,7 @@ module CodeOcean def read if native_file? - valid = Pathname(native_file.current_path).fnmatch? ::File.join(native_file.root, '**') - return nil unless valid + return nil unless native_file_location_valid? native_file.read else @@ -67,6 +66,12 @@ module CodeOcean end end + def native_file_location_valid? + real_location = Pathname(native_file.current_path).realpath + upload_location = Pathname(::File.join(native_file.root, 'uploads')).realpath + real_location.fnmatch? ::File.join(upload_location.to_s, '**') + end + def ancestor_id file_id || id end diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 7711dbe7..9418f1f6 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -205,6 +205,10 @@ class Exercise < ApplicationRecord " end + def teacher_defined_assessment? + files.any?(&:teacher_defined_assessment?) + end + def get_working_times_for_study_group(study_group_id, user = nil) user_progress = [] additional_user_data = [] @@ -251,7 +255,6 @@ class Exercise < ApplicationRecord end def get_quantiles(quantiles) - quantiles_str = self.class.sanitize_sql("[#{quantiles.join(',')}]") result = ActiveRecord::Base.transaction do self.class.connection.execute(" SET LOCAL intervalstyle = 'iso_8601'; @@ -358,7 +361,7 @@ class Exercise < ApplicationRecord GROUP BY e.external_id, f.user_id, exercise_id ) - SELECT unnest(percentile_cont(array#{quantiles_str}) within GROUP (ORDER BY working_time)) + SELECT unnest(percentile_cont(#{self.class.sanitize_sql(['array[?]', quantiles])}) within GROUP (ORDER BY working_time)) FROM result ") end diff --git a/app/models/user.rb b/app/models/user.rb index fb718c57..ce519fc2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -42,7 +42,11 @@ class User < ApplicationRecord displayname end - def self.ransackable_attributes(_auth_object = nil) - %w[name email external_id consumer_id role] + def self.ransackable_attributes(auth_object) + if auth_object.admin? + %w[name email external_id consumer_id role] + else + %w[name external_id] + end end end diff --git a/app/policies/code_ocean/file_policy.rb b/app/policies/code_ocean/file_policy.rb index c9faf30d..d5be20cb 100644 --- a/app/policies/code_ocean/file_policy.rb +++ b/app/policies/code_ocean/file_policy.rb @@ -7,6 +7,8 @@ module CodeOcean end def show? + return false if @record.native_file? && !@record.native_file_location_valid? + if @record.context.is_a?(Exercise) admin? || author? || !@record.hidden else @@ -14,6 +16,16 @@ module CodeOcean end end + def show_protected_upload? + return false if @record.native_file? && !@record.native_file_location_valid? + + if @record.context.is_a?(Exercise) + admin? || author? || (!@record.context.unpublished && !@record.hidden) + else + admin? || author? + end + end + def create? if @record.context.is_a?(Exercise) admin? || author? diff --git a/app/policies/exercise_policy.rb b/app/policies/exercise_policy.rb index 81fab186..0452ce94 100644 --- a/app/policies/exercise_policy.rb +++ b/app/policies/exercise_policy.rb @@ -29,8 +29,16 @@ class ExercisePolicy < AdminOrAuthorPolicy define_method(action) { (admin? || teacher_in_study_group? || author?) && @user.codeharbor_link } end - %i[implement? working_times? intervention? search? submit? reload?].each do |action| - define_method(action) { everyone } + %i[implement? working_times? intervention? search? reload?].each do |action| + define_method(action) do + return no_one unless @record.files.visible.exists? + + admin? || teacher_in_study_group? || author? || (everyone && !@record.unpublished?) + end + end + + def submit? + everyone && @record.teacher_defined_assessment? end class Scope < Scope @@ -39,8 +47,8 @@ class ExercisePolicy < AdminOrAuthorPolicy @scope.all elsif @user.teacher? @scope.where( - 'user_id IN (SELECT user_id FROM study_group_memberships WHERE study_group_id IN (?)) - OR (user_id = ? AND user_type = ?) + 'exercises.user_id IN (SELECT user_id FROM study_group_memberships WHERE study_group_id IN (?)) + OR (exercises.user_id = ? AND exercises.user_type = ?) OR public = TRUE', @user.study_groups.pluck(:id), @user.id, @user.class.name diff --git a/app/policies/proxy_exercise_policy.rb b/app/policies/proxy_exercise_policy.rb index 4112064e..20c2e548 100644 --- a/app/policies/proxy_exercise_policy.rb +++ b/app/policies/proxy_exercise_policy.rb @@ -13,10 +13,6 @@ class ProxyExercisePolicy < AdminOrAuthorPolicy define_method(action) { admin? || author? } end - [:reload?].each do |action| - define_method(action) { everyone } - end - class Scope < Scope def resolve if @user.admin? diff --git a/app/views/exercises/_editor_output.html.slim b/app/views/exercises/_editor_output.html.slim index 2997ca3e..c4838cba 100644 --- a/app/views/exercises/_editor_output.html.slim +++ b/app/views/exercises/_editor_output.html.slim @@ -79,7 +79,7 @@ div.d-grid id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-botto .heading = t('exercises.implement.error_hints.heading') ul.body.mb-0 #output - pre.overflow-scroll = t('exercises.implement.no_output_yet') + .output-element.overflow-scroll = t('exercises.implement.no_output_yet') - if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled] && !@embed_options[:disable_hints] && !@embed_options[:hide_test_results] #flowrHint.mb-2.card.text-white.bg-info data-url=CodeOcean::Config.new(:code_ocean).read[:flowr][:url] role='tab' .card-header = t('exercises.implement.flowr.heading') diff --git a/app/views/exercises/feedback.html.slim b/app/views/exercises/feedback.html.slim index 5085ed8c..e52b5c5b 100644 --- a/app/views/exercises/feedback.html.slim +++ b/app/views/exercises/feedback.html.slim @@ -25,7 +25,7 @@ h1 = link_to_if(policy(@exercise).show?, @exercise, exercise_path(@exercise)) span.date = feedback.created_at .card-collapse role="tabpanel" .card-body.feedback - .text = render_markdown(feedback.feedback_text) + .text style="white-space: pre-wrap;" = feedback.feedback_text .difficulty = "#{t('user_exercise_feedback.difficulty')} #{comment_presets[feedback.difficulty].join(' - ')}" if feedback.difficulty .worktime = "#{t('user_exercise_feedback.working_time')} #{time_presets[feedback.user_estimated_worktime].join(' - ')}" if feedback.user_estimated_worktime - if policy(@exercise).detailed_statistics? @@ -36,4 +36,5 @@ h1 = link_to_if(policy(@exercise).show?, @exercise, exercise_path(@exercise)) = render('shared/pagination', collection: @feedbacks) - script type="text/javascript" $(function () { $('[data-bs-toggle="tooltip"]').tooltip() }); + = javascript_tag nonce: true do + | $(function () { $('[data-bs-toggle="tooltip"]').tooltip() }); diff --git a/app/views/external_users/index.html.slim b/app/views/external_users/index.html.slim index daa4aacb..20f86edb 100644 --- a/app/views/external_users/index.html.slim +++ b/app/views/external_users/index.html.slim @@ -1,24 +1,32 @@ h1 = ExternalUser.model_name.human(count: 2) = render(layout: 'shared/form_filters') do |f| - .col-md-9.col - .row.align-items-center - .col - = f.label(:name_cont, t('activerecord.attributes.external_user.name'), class: 'visually-hidden form-label') - = f.search_field(:name_cont, class: 'form-control', placeholder: t('activerecord.attributes.external_user.name')) - .col.mt-0.mt-sm-3.mt-md-0 - = f.label(:email_cont, t('activerecord.attributes.external_user.email'), class: 'visually-hidden form-label') - = f.search_field(:email_cont, class: 'form-control', placeholder: t('activerecord.attributes.external_user.email')) - .col.mt-3.mt-lg-0 - = f.label(:external_id_cont, t('activerecord.attributes.external_user.external_id'), class: 'visually-hidden form-label') - = f.search_field(:external_id_cont, class: 'form-control', placeholder: t('activerecord.attributes.external_user.external_id')) - .row - .col-auto - = f.label(:role_eq, t('activerecord.attributes.external_user.role'), class: 'visually-hidden form-label') - = f.select(:role_eq, User::ROLES.map { |role| [t("users.roles.#{role}"), role] }, { include_blank: true }, class: 'form-control', prompt: t('activerecord.attributes.external_user.role')) - .col-auto.mt-3.mt-lg-0 - = f.label(:consumer_id_eq, t('activerecord.attributes.external_user.consumer'), class: 'visually-hidden form-label') - = f.collection_select(:consumer_id_eq, Consumer.with_external_users, :id, :name, class: 'form-control', prompt: t('activerecord.attributes.external_user.consumer')) + - if current_user.admin? + .col-md-9.col + .row.align-items-center + .col + = f.label(:name_cont, t('activerecord.attributes.external_user.name'), class: 'visually-hidden form-label') + = f.search_field(:name_cont, class: 'form-control', placeholder: t('activerecord.attributes.external_user.name')) + .col.mt-0.mt-sm-3.mt-md-0 + = f.label(:email_cont, t('activerecord.attributes.external_user.email'), class: 'visually-hidden form-label') + = f.search_field(:email_cont, class: 'form-control', placeholder: t('activerecord.attributes.external_user.email')) + .col.mt-3.mt-lg-0 + = f.label(:external_id_cont, t('activerecord.attributes.external_user.external_id'), class: 'visually-hidden form-label') + = f.search_field(:external_id_cont, class: 'form-control', placeholder: t('activerecord.attributes.external_user.external_id')) + .row + .col-auto + = f.label(:role_eq, t('activerecord.attributes.external_user.role'), class: 'visually-hidden form-label') + = f.select(:role_eq, User::ROLES.map { |role| [t("users.roles.#{role}"), role] }, { include_blank: true }, class: 'form-control', prompt: t('activerecord.attributes.external_user.role')) + .col-auto.mt-3.mt-lg-0 + = f.label(:consumer_id_eq, t('activerecord.attributes.external_user.consumer'), class: 'visually-hidden form-label') + = f.collection_select(:consumer_id_eq, Consumer.with_external_users, :id, :name, class: 'form-control', prompt: t('activerecord.attributes.external_user.consumer')) + - else + .col-auto + = f.label(:name_cont, t('activerecord.attributes.external_user.name'), class: 'visually-hidden form-label') + = f.search_field(:name_cont, class: 'form-control', placeholder: t('activerecord.attributes.external_user.name')) + .col-auto + = f.label(:external_id_cont, t('activerecord.attributes.external_user.external_id'), class: 'visually-hidden form-label') + = f.search_field(:external_id_cont, class: 'form-control', placeholder: t('activerecord.attributes.external_user.external_id')) .table-responsive table.table thead diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim index 044088d1..5bd6fdff 100644 --- a/app/views/layouts/application.html.slim +++ b/app/views/layouts/application.html.slim @@ -9,14 +9,14 @@ html lang="#{I18n.locale || I18n.default_locale}" = favicon_link_tag('/favicon.png', type: 'image/png') = favicon_link_tag('/favicon.png', rel: 'apple-touch-icon', type: 'image/png') = action_cable_meta_tag - = stylesheet_pack_tag('application', 'stylesheets', media: 'all', 'data-turbolinks-track': true) - = stylesheet_link_tag('application', media: 'all', 'data-turbolinks-track': true) - = javascript_pack_tag('application', 'data-turbolinks-track': true, defer: false) - = javascript_include_tag('application', 'data-turbolinks-track': true) + = stylesheet_pack_tag('application', 'stylesheets', media: 'all', 'data-turbolinks-track': true, integrity: true, crossorigin: 'anonymous') + = stylesheet_link_tag('application', media: 'all', 'data-turbolinks-track': true, integrity: true, crossorigin: 'anonymous') + = javascript_pack_tag('application', 'data-turbolinks-track': true, defer: false, integrity: true, crossorigin: 'anonymous') + = javascript_include_tag('application', 'data-turbolinks-track': true, integrity: true, crossorigin: 'anonymous') = yield(:head) = csrf_meta_tags - = timeago_script_tag - script type="text/javascript" + = timeago_script_tag nonce: true + = javascript_tag nonce: true do | I18n.defaultLocale = "#{I18n.default_locale}"; | I18n.locale = "#{I18n.locale}"; - if SentryJavascript.active? diff --git a/app/views/proxy_exercises/reload.json.jbuilder b/app/views/proxy_exercises/reload.json.jbuilder deleted file mode 100644 index 3f5793b1..00000000 --- a/app/views/proxy_exercises/reload.json.jbuilder +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -json.set! :files do - json.array! @exercise.files.visible, :content, :id -end diff --git a/app/views/request_for_comments/show.html.slim b/app/views/request_for_comments/show.html.slim index aaaf578a..b8a2acf6 100644 --- a/app/views/request_for_comments/show.html.slim +++ b/app/views/request_for_comments/show.html.slim @@ -79,7 +79,7 @@ = render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.dialogtitle'), template: 'exercises/_comment_dialogcontent') -javascript: +javascript [nonce=content_security_policy_nonce]: $('.modal-content').draggable({ handle: '.modal-header' diff --git a/config/content_security_policy.yml.ci b/config/content_security_policy.yml.ci new file mode 100644 index 00000000..312725e9 --- /dev/null +++ b/config/content_security_policy.yml.ci @@ -0,0 +1,18 @@ +default: &default + default_src: [] + + +development: + <<: *default + # Allow the webpack-dev-server in development + connect_src: + - http://localhost:3035 + - ws://localhost:3035 + + +production: + <<: *default + + +test: + <<: *default diff --git a/config/content_security_policy.yml.example b/config/content_security_policy.yml.example new file mode 100644 index 00000000..000cca6f --- /dev/null +++ b/config/content_security_policy.yml.example @@ -0,0 +1,33 @@ +# This file allows to further customize the Content Security Policy (CSP) +# All settings will be applied **in addition** to the application CSP +# Default directives are defined here: `initializers/content_security_policy.rb` + +default: &default + # Allow the S3 service hosted by the openHPI Cloud to be used for images + img_src: + - https://s3.xopic.de + - https://*.s3.xopic.de + - https://s3.openhpicloud.de + - https://*.s3.openhpicloud.de + # Webkit didn't consider the WSS scheme as part of 'self', adding it explicitly + # See https://bugs.webkit.org/show_bug.cgi?id=235873 + connect_src: + - wss://codeocean.openhpi.de + # Optionally: Specify a custom, non-Sentry URL for reporting CSP violations + # report_uri: https://example.com/csp-report + + +development: + <<: *default + # Allow the webpack-dev-server in development + connect_src: + - http://localhost:3035 + - ws://localhost:3035 + + +production: + <<: *default + + +test: + <<: *default diff --git a/config/environments/production.rb b/config/environments/production.rb index a06058c7..4e6af6dd 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -38,7 +38,7 @@ Rails.application.configure do # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache - # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :local @@ -50,6 +50,7 @@ Rails.application.configure do # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. config.force_ssl = true + config.ssl_options = {hsts: {preload: true}} # Include generic and useful information about system operation, but avoid logging too much # information to avoid inadvertent exposure of personally identifiable information (PII). diff --git a/config/environments/staging.rb b/config/environments/staging.rb index f098937f..bf50b877 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'active_support/core_ext/integer/time' + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -9,18 +11,15 @@ Rails.application.configure do # since you don't have to restart the web server when you make code changes. config.cache_classes = false - # true: Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both threaded web servers - # and those relying on copy on write to perform better. - # Rake tasks automatically ignore this option for performance. - # Eager load code for prometheus exporter + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + # Eager load is also required for the prometheus exporter config.eager_load = true - # enable web console in staging - config.web_console.development_only = false - - # Show full error reports and disable caching. - config.consider_all_requests_local = true + # Full error reports as well as caching are disabled. + config.consider_all_requests_local = false config.action_controller.perform_caching = false # Raise an error on page load if there are pending migrations. @@ -39,47 +38,50 @@ Rails.application.configure do # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. # config.action_dispatch.rack_cache = true - # Disable Rails's static asset server (Apache or nginx will already do this). - config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? - # Compress JavaScripts and CSS. - # config.assets.js_compressor = :uglifier + # Compress CSS using a preprocessor. # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false - # Generate digests for assets URLs. - config.assets.digest = true - - # Version of your assets, change this if you want to expire all your assets. - config.assets.version = '1.0' + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = 'http://assets.example.com' # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache - # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Mount Action Cable outside main process or domain. + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. config.force_ssl = true + config.ssl_options = {hsts: {preload: true}} - # Set to :debug to see everything in the log. + # Include generic and useful information about system operation, but avoid logging too much + # information to avoid inadvertent exposure of personally identifiable information (PII). config.log_level = :info # Prepend all log lines with the following tags. - # config.log_tags = [ :subdomain, :uuid ] - - # Use a different logger for distributed setups. - # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + # config.log_tags = [ :subdomain, :uuid, :request_id ] # Use a different cache store in production. # config.cache_store = :mem_cache_store - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = "http://assets.example.com" + # Use a real queuing backend for Active Job (and separate queues per environment). + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "code_ocean_production" - # Precompile additional assets. - # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. - # config.assets.precompile += %w( search.js ) + config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. @@ -92,12 +94,46 @@ Rails.application.configure do # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify - # Disable automatic flushing of the log to improve performance. - # config.autoflush_log = false + # Log disallowed deprecations. + config.active_support.disallowed_deprecation = :log + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV['RAILS_LOG_TO_STDOUT'].present? + logger = ActiveSupport::Logger.new($stdout) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false + + # Inserts middleware to perform automatic connection switching. + # The `database_selector` hash is used to pass options to the DatabaseSelector + # middleware. The `delay` is used to determine how long to wait after a write + # to send a subsequent read to the primary. + # + # The `database_resolver` class is used by the middleware to determine which + # database is appropriate to use based on the time delay. + # + # The `database_resolver_context` class is used by the middleware to set + # timestamps for the last write to the primary. The resolver uses the context + # class timestamps to determine how long to wait before reading from the + # replica. + # + # By default Rails will store a last write timestamp in the session. The + # DatabaseSelector middleware is designed as such you can define your own + # strategy for connection switching and pass that into the middleware through + # these configuration options. + # config.active_record.database_selector = { delay: 2.seconds } + # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver + # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session end diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index f87cb06b..9bef6035 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -6,28 +6,57 @@ # For further information see the following documentation # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy -# Rails.application.config.content_security_policy do |policy| -# # If you are using webpack-dev-server then specify webpack-dev-server host -# policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? +require_relative 'sentry_csp' +require_relative 'sentry_javascript' -# policy.default_src :self, :https -# policy.font_src :self, :https, :data -# policy.img_src :self, :https, :data -# policy.object_src :none -# policy.script_src :self, :https -# policy.style_src :self, :https -# # If you are using webpack-dev-server then specify webpack-dev-server host -# policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? +def self.apply_yml_settings_for(policy) + csp_settings = CodeOcean::Config.new(:content_security_policy) -# # Specify URI for violation reports -# # policy.report_uri "/csp-violation-report-endpoint" -# end + csp_settings.read.each do |directive, additional_settings| + existing_settings = if directive == 'report_uri' + '' + else + policy.public_send(directive) || [] + end + all_settings = existing_settings + additional_settings + policy.public_send(directive, *all_settings) + end +end + +def self.apply_sentry_settings_for(policy) + sentry_domain = URI.parse SentryJavascript.dsn + additional_setting = "#{sentry_domain.scheme}://#{sentry_domain.host}" + existing_settings = policy.connect_src || [] + all_settings = existing_settings + [additional_setting] + policy.connect_src(*all_settings) +end + +Rails.application.config.content_security_policy do |policy| + policy.default_src :none + policy.base_uri :self + policy.font_src :self + # Code executions might return a base64 encoded image as a :data URI + policy.img_src :self, :data + policy.object_src :none + policy.script_src :self, :report_sample + # Our ACE editor unfortunately requires :unsafe_inline for the code highlighting + policy.style_src :self, :unsafe_inline, :report_sample + policy.connect_src :self + policy.form_action :self + policy.frame_ancestors :none + + # Specify URI for violation reports + policy.report_uri SentryCsp.report_url if SentryCsp.active? + + apply_yml_settings_for policy + apply_sentry_settings_for policy if SentryJavascript.active? +end # If you are using UJS then enable automatic nonce generation -# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } +Rails.application.config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } # Set the nonce only to specific directives -# Rails.application.config.content_security_policy_nonce_directives = %w(script-src) +Rails.application.config.content_security_policy_nonce_directives = %w[script-src] # Report CSP violations to a specified URI # For further information see the following documentation: diff --git a/config/initializers/permissions_policy.rb b/config/initializers/permissions_policy.rb index 50bcf4ea..57e689ba 100644 --- a/config/initializers/permissions_policy.rb +++ b/config/initializers/permissions_policy.rb @@ -1,12 +1,26 @@ # frozen_string_literal: true + # Define an application-wide HTTP permissions policy. For further # information see https://developers.google.com/web/updates/2018/06/feature-policy -# -# Rails.application.config.permissions_policy do |f| -# f.camera :none -# f.gyroscope :none -# f.microphone :none -# f.usb :none -# f.fullscreen :self -# f.payment :self, "https://secure.example.com" -# end +# TODO: Feature-Policy has been renamed to Permissions-Policy. The Permissions-Policy is +# not yet supported by Rails (even though the new name is already used for the method) +Rails.application.config.permissions_policy do |policy| + policy.accelerometer :none + policy.ambient_light_sensor :none + policy.autoplay :none + policy.camera :none + policy.encrypted_media :none + policy.fullscreen :none + policy.geolocation :none + policy.gyroscope :none + policy.magnetometer :none + policy.microphone :none + policy.midi :none + policy.payment :none + policy.picture_in_picture :none + # The `speaker` directive is used for selection of non-default audio output devices + policy.speaker :none + policy.usb :none + policy.vibrate :none + policy.vr :none +end diff --git a/config/initializers/sentry_csp.rb b/config/initializers/sentry_csp.rb new file mode 100644 index 00000000..891f5399 --- /dev/null +++ b/config/initializers/sentry_csp.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative 'sentry' + +class SentryCsp + def self.active? + dsn.present? && %w[development test].exclude?(environment) + end + + def self.report_url + parsed_url = URI.parse dsn + + # Add additional variables to the query string + query_params = CGI.parse(parsed_url.query || '') + query_params[:sentry_release] = release if release + query_params[:sentry_environment] = environment if environment + + # Add the query string back to the URL + parsed_url.query = URI.encode_www_form(query_params) + + # Return the full URL + parsed_url.to_s + end + + class << self + private + + def dsn + ENV.fetch('SENTRY_CSP_REPORT_URL', nil) + end + + def release + Sentry.configuration.release + end + + def environment + Sentry.configuration.environment + end + end +end diff --git a/config/initializers/sentry_javascript.rb b/config/initializers/sentry_javascript.rb index 584f39da..9fe3479c 100644 --- a/config/initializers/sentry_javascript.rb +++ b/config/initializers/sentry_javascript.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative 'sentry' + class SentryJavascript def self.active? dsn.present? && %w[development test].exclude?(environment) diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 57edb098..7bd809a2 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -2,8 +2,23 @@ # Be sure to restart your server when you modify this file. +def self.cookie_prefix + if (Rails.env.production? || Rails.env.staging?) \ + && Rails.application.config.relative_url_root == '/' + '__Host-' + elsif Rails.env.production? || Rails.env.staging? + '__Secure-' + else + '' + end +end + Rails.application.config.session_store :cookie_store, - key: '_code_ocean_session', + key: "#{cookie_prefix}CodeOcean-Session", expire_after: 1.month, secure: Rails.env.production? || Rails.env.staging?, - path: Rails.application.config.relative_url_root + path: Rails.application.config.relative_url_root, + # Signing in through LTI won't work with `SameSite=Strict` + # as the cookie is not sent when accessing the `implement` route + # following the LTI launch initiated by the LMS as a third party. + same_site: :lax diff --git a/config/locales/de.yml b/config/locales/de.yml index 592e9a20..7b059c17 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -296,7 +296,7 @@ de: form: hints: command: filename wird automatisch durch den richtigen Dateinamen ersetzt. - docker_image: 'Wählen Sie ein Docker-Image aus der Liste oder fügen Sie ein neues hinzu, welches über DockerHub verfügbar ist.' + docker_image: 'Wählen Sie ein Docker-Image aus der Liste oder fügen Sie ein neues hinzu, welches über DockerHub verfügbar ist.' exposed_ports_list: Während der Ausführung sind diese Ports für den Nutzer zugänglich. Die Portnummern müssen nummerisch und mit Komma voneinander getrennt sein. cpu_limit: Geben Sie die Mindestmenge an CPU-Anteilen an, die für jeden Runner reserviert werden soll, gemessen in MHz. errors: @@ -340,7 +340,7 @@ de: expand_output_sidebar: Ausgabe-Leiste Ausklappen input: Ihre Eingabe lastsaved: 'Zuletzt gespeichert: ' - network: 'Während Ihr Code läuft, ist Port %{port} unter folgender Adresse erreichbar: %{address}.' + network: 'Während Ihr Code läuft, ist Port %{port} unter folgender Adresse erreichbar: %{address}.' render: Anzeigen run: Ausführen run_failure: Ihr Code konnte nicht auf der Plattform ausgeführt werden. @@ -453,7 +453,7 @@ de: request: "Kommentaranfrage stellen" question: "Bitte beschreiben Sie kurz Ihre Probleme oder nennen Sie den Programmteil, zu dem Sie Feedback wünschen. Ihr Programmcode und eventuelle Fehlermeldungen werden automatisch zur Anfrage hinzugefügt." intervention: - explanation: "Diese Meldung erscheint, weil Sie %{duration} Minuten an dieser Aufgabe gearbeitet haben. 25% Ihrer Mitlernenden arbeiten länger daran, insofern ist das kein Problem, aber dies hat sich als effektiven Zeitpunkt für diese Meldung erwiesen." + explanation: "Diese Meldung erscheint, weil Sie %{duration} Minuten an dieser Aufgabe gearbeitet haben. 25% Ihrer Mitlernenden arbeiten länger daran, insofern ist das kein Problem, aber dies hat sich als effektiven Zeitpunkt für diese Meldung erwiesen." rfc_intervention: text: "Falls Sie bei dieser Aufgabe nicht weiterkommen und nicht selbst weiter knobeln möchten, können Ihre Mitlernenden bestimmt helfen! " break_intervention: @@ -780,7 +780,7 @@ de: runtime_output: "Programmausgabe" test_results: "Testergebnisse" sessions: - expired: Ihre Session ist abgelaufen. Bitte laden Sie diese Seite neu bevor Sie fortfahren. + expired: Ihre Session ist abgelaufen. Bitte laden Sie diese Seite neu bevor Sie fortfahren. create: failure: Fehlerhafte E-Mail oder Passwort. success: Sie haben sich erfolgreich angemeldet. @@ -793,7 +793,7 @@ de: destroy_through_lti: average_score: Durchschnittliche Punktzahl final_submissions: Abgaben anderer Nutzer - finished_with_consumer: 'Sie können dieses Fenster nun schließen oder zu %{consumer} zurückkehren.' + finished_with_consumer: 'Sie können dieses Fenster nun schließen oder zu %{consumer} zurückkehren.' finished_without_consumer: Sie können dieses Fenster nun schließen. headline: Gut gemacht! score: Ihre Punktzahl @@ -834,7 +834,7 @@ de: link: Hilfe index: Index message_failure: Leider ist ein Fehler auf unserer Plattform aufgetreten. Bitte probieren Sie es später noch einmal. - websocket_failure: Leider ist ein Verbindungsproblem aufgetreten. Bitte überprüfen Sie Websocket-Verbindungen mit diesem Tool und versuchen Sie es erneut. + websocket_failure: Leider ist ein Verbindungsproblem aufgetreten. Bitte überprüfen Sie Websocket-Verbindungen mit diesem Tool und versuchen Sie es erneut. new: Hinzufügen new_model: '%{model} hinzufügen' number: Nummer diff --git a/config/locales/en.yml b/config/locales/en.yml index 739e3096..6c7e7612 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -296,7 +296,7 @@ en: form: hints: command: filename is automatically replaced with the correct filename. - docker_image: Pick a Docker image listed above or add a new one which is available via DockerHub. + docker_image: Pick a Docker image listed above or add a new one which is available via DockerHub. exposed_ports_list: During code execution these ports are accessible for the user. Port numbers must be numeric and separated by a comma. cpu_limit: Specify the minimum amount of CPU shares to reserve for each runner, measured in MHz. errors: @@ -340,7 +340,7 @@ en: expand_output_sidebar: Expand Output Sidebar input: Your input lastsaved: 'Last saved: ' - network: 'While your code is running, port %{port} is accessible using the following address: %{address}.' + network: 'While your code is running, port %{port} is accessible using the following address: %{address}.' render: Render run: Run run_failure: Your code could not be run. @@ -453,7 +453,7 @@ en: request: "Request Comments" question: 'Please shortly describe your problem or the program part you would like to get feedback for. Your program code and potential error messages are automatically appended to your request.' intervention: - explanation: "This message appears because you have been working on this exercise for %{duration} minutes. 25% of your fellow learners took more time to solve the exercise, so in that sense it's not a problem, but this has proven to be an effective time for this message." + explanation: "This message appears because you have been working on this exercise for %{duration} minutes. 25% of your fellow learners took more time to solve the exercise, so in that sense it's not a problem, but this has proven to be an effective time for this message." rfc_intervention: text: "If you are struggling with this exercise and don't want to continue on your own, your fellow learners can help out! " break_intervention: @@ -780,7 +780,7 @@ en: runtime_output: "Runtime Output" test_results: "Test Results" sessions: - expired: Your session has expired. Please reload this page before continuing. + expired: Your session has expired. Please reload this page before continuing. create: failure: Invalid email or password. success: Successfully signed in. @@ -793,7 +793,7 @@ en: destroy_through_lti: average_score: Average Score final_submissions: Other Users' Submissions - finished_with_consumer: 'You may close this window now or return to %{consumer}.' + finished_with_consumer: 'You may close this window now or return to %{consumer}.' finished_without_consumer: You may close this window now. headline: Well done! score: Your Score @@ -834,7 +834,7 @@ en: link: Help index: Index message_failure: 'Sorry, something went wrong.' - websocket_failure: Sorry, a connection issue occoured. Please check WebSocket connections with this tool and try again. + websocket_failure: Sorry, a connection issue occoured. Please check WebSocket connections with this tool and try again. new: Add new_model: 'Add %{model}' number: Number diff --git a/config/routes.rb b/config/routes.rb index 69c3795b..dc276e13 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -128,6 +128,7 @@ Rails.application.routes.draw do namespace :code_ocean do resources :files, only: %i[create destroy] end + get '/uploads/files/:id/:filename', to: 'code_ocean/files#show_protected_upload', as: :protected_upload, constraints: {filename: FILENAME_REGEXP} resources :file_types diff --git a/config/webpack/webpack.config.js b/config/webpack/webpack.config.js index 355c3b3e..3ea79661 100644 --- a/config/webpack/webpack.config.js +++ b/config/webpack/webpack.config.js @@ -1,12 +1,14 @@ // See the shakacode/shakapacker README and docs directory for advice on customizing your webpackConfig. -const { webpackConfig, merge } = require('shakapacker') +const { webpackConfig, config, merge } = require('shakapacker') const webpack = require('webpack'); const CompressionPlugin = require("compression-webpack-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const TerserPlugin = require("terser-webpack-plugin"); +const WebpackAssetsManifest = require('webpack-assets-manifest'); +const { SubresourceIntegrityPlugin } = require("webpack-subresource-integrity"); // Custom ERB loader to disable Spring and prevent crashes const erb = require("./loaders/erb"); @@ -30,7 +32,9 @@ const envConfig = module.exports = { ], }, output: { - publicPath: relative_url_root + public_output_path + publicPath: relative_url_root + public_output_path, + // the following setting is required for SRI to work: + crossOriginLoading: 'anonymous', }, performance: { // Turn off size warnings for large assets @@ -53,6 +57,15 @@ const envConfig = module.exports = { }), new CompressionPlugin(), new MiniCssExtractPlugin(), + new SubresourceIntegrityPlugin(), + new WebpackAssetsManifest({ + entrypoints: true, + integrity: false, + writeToDisk: true, + entrypointsUseAssets: true, + publicPath: true, + output: config.manifestPath, + }) ], resolve: { extensions: ['.css', '.ts', '.tsx'], @@ -65,4 +78,8 @@ const envConfig = module.exports = { stats: 'minimal', } +// Use the two lines below to remove the original WebpackAssetsManifest and replace it with our custom config. +const filteredPlugins = webpackConfig.plugins.filter((plugin) => !(plugin instanceof WebpackAssetsManifest)) +webpackConfig.plugins = filteredPlugins; + module.exports = merge(webpackConfig, envConfig) diff --git a/docs/LOCAL_SETUP.md b/docs/LOCAL_SETUP.md index db5fdd36..2384fefe 100644 --- a/docs/LOCAL_SETUP.md +++ b/docs/LOCAL_SETUP.md @@ -194,7 +194,7 @@ source "$HOME/.profile" - Create all necessary config files: ```bash - for f in action_mailer.yml database.yml secrets.yml code_ocean.yml docker.yml.erb mnemosyne.yml + for f in action_mailer.yml database.yml secrets.yml code_ocean.yml docker.yml.erb mnemosyne.yml content_security_policy.yml do if [ ! -f config/$f ] then @@ -303,7 +303,7 @@ source "$HOME/.profile" ``` - Get a local copy of the config files and verify the settings: ```shell script - for f in action_mailer.yml database.yml secrets.yml code_ocean.yml docker.yml.erb mnemosyne.yml + for f in action_mailer.yml database.yml secrets.yml code_ocean.yml docker.yml.erb mnemosyne.yml content_security_policy.yml do if [ ! -f config/$f ] then diff --git a/lib/webpacker/sri_helper_extensions.rb b/lib/webpacker/sri_helper_extensions.rb new file mode 100644 index 00000000..74b7bdfc --- /dev/null +++ b/lib/webpacker/sri_helper_extensions.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Webpacker::SriHelperExtensions + def stylesheet_link_tag(*sources, **options) + tags = sources.map do |stylesheet| + if stylesheet.is_a?(Hash) + super(stylesheet[:src], options.merge(integrity: stylesheet[:integrity])) + else + super(stylesheet, options) + end + end + safe_join(tags) + end + + def javascript_include_tag(*sources, **options) + tags = sources.map do |javascript| + if javascript.is_a?(Hash) + super(javascript[:src], options.merge(integrity: javascript[:integrity])) + else + super(javascript, options) + end + end + safe_join(tags) + end +end + +Sprockets::Rails::Helper.prepend(Webpacker::SriHelperExtensions) diff --git a/lib/webpacker/sri_manifest_extensions.rb b/lib/webpacker/sri_manifest_extensions.rb new file mode 100644 index 00000000..143bf7a5 --- /dev/null +++ b/lib/webpacker/sri_manifest_extensions.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Webpacker::SriManifestExtensions + def lookup(name, pack_type = {}) + asset = super + + augment_with_integrity asset, pack_type + end + + def lookup_pack_with_chunks(name, pack_type = {}) + assets = super + + assets.map do |asset| + augment_with_integrity asset, pack_type + end + end + + def augment_with_integrity(asset, _pack_type = {}) + if asset.respond_to?(:dig) && asset['integrity'] + {src: asset['src'], integrity: asset['integrity']} + elsif asset.respond_to?(:dig) + asset['src'] + else + asset + end + end +end + +Webpacker::Manifest.prepend(Webpacker::SriManifestExtensions) diff --git a/package.json b/package.json index a2b70ea2..cd3e2549 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,17 @@ "name": "codeocean", "private": true, "dependencies": { - "@babel/core": "^7.18.10", + "@babel/core": "^7.19.0", "@babel/plugin-transform-runtime": "^7.18.10", "@babel/preset-env": "7", "@babel/runtime": "7", "@egjs/hammerjs": "^2.0.17", - "@fortawesome/fontawesome-free": "^6.1.2", + "@fortawesome/fontawesome-free": "^6.2.0", "@popperjs/core": "^2.11.6", - "@sentry/browser": "^6.11.0", + "@sentry/browser": "^7.11.1", "@webpack-cli/serve": "^1.7.0", "babel-loader": "^8.2.5", - "bootstrap": "^5.2.0", + "bootstrap": "^5.2.1", "bootswatch": "^5.2.0", "chosen-js": "^1.8.7", "component-emitter": "^1.3.0", @@ -22,8 +22,8 @@ "d3": "^7.6.1", "d3-tip": "^0.9.1", "highlight.js": "^11.5.1", - "i18n-js": "^4.0.2", - "jquery": "^3.6.0", + "i18n-js": "^4.1.1", + "jquery": "^3.6.1", "jquery-ui": "^1.13.1", "jquery-ujs": "^1.2.3", "jstree": "^3.3.12", @@ -34,13 +34,13 @@ "pnp-webpack-plugin": "^1.7.0", "propagating-hammerjs": "^2.0.1", "rails-erb-loader": "^5.5.2", - "sass": "^1.54.4", + "sass": "^1.54.9", "sass-loader": "^13.0.2", - "shakapacker": "6.5.1", + "shakapacker": "6.5.2", "sortablejs": "^1.15.0", "sorttable": "^1.0.2", "style-loader": "^3.3.1", - "terser-webpack-plugin": "^5.3.5", + "terser-webpack-plugin": "^5.3.6", "underscore": "^1.13.4", "uuid": "^8.3.2", "vis-data": "^7.1.4", @@ -51,10 +51,11 @@ "webpack-cli": "^4.10.0", "webpack-merge": "^5.8.0", "webpack-sources": "^3.2.3", + "webpack-subresource-integrity": "^5.1.0", "xss": "^1.0.14" }, "devDependencies": { - "webpack-dev-server": "^4.10.0" + "webpack-dev-server": "^4.11.0" }, "babel": { "presets": [ diff --git a/provision/provision.vagrant.sh b/provision/provision.vagrant.sh index ecaf297b..6137d7f2 100644 --- a/provision/provision.vagrant.sh +++ b/provision/provision.vagrant.sh @@ -92,7 +92,7 @@ gem install bundler cd /home/vagrant/codeocean # config -for f in action_mailer.yml database.yml secrets.yml docker.yml.erb mnemosyne.yml +for f in action_mailer.yml database.yml secrets.yml docker.yml.erb mnemosyne.yml content_security_policy.yml do if [ ! -f config/$f ] then diff --git a/spec/concerns/file_parameters_spec.rb b/spec/concerns/file_parameters_spec.rb index c7470689..1a4bc595 100644 --- a/spec/concerns/file_parameters_spec.rb +++ b/spec/concerns/file_parameters_spec.rb @@ -25,6 +25,8 @@ describe FileParameters do it 'new file' do submission = create(:submission, exercise: hello_world, id: 1337) + controller.instance_variable_set(:@current_user, submission.user) + new_file = create(:file, context: submission) expect(file_accepted?(new_file)).to be true end @@ -42,16 +44,27 @@ describe FileParameters do expect(file_accepted?(hidden_file)).to be false end - it 'read only file' do + it 'read-only file' do read_only_file = create(:file, context: hello_world, read_only: true) expect(file_accepted?(read_only_file)).to be false end - it 'non existent file' do + it 'non-existent file' do # Ensure to use an invalid id for the file. non_existent_file = build(:file, context: hello_world, id: -1) expect(file_accepted?(non_existent_file)).to be false end + + it 'file of another submission' do + learner1 = create(:learner) + learner2 = create(:learner) + submission_learner1 = create(:submission, exercise: hello_world, user: learner1) + _submission_learner2 = create(:submission, exercise: hello_world, user: learner2) + + controller.instance_variable_set(:@current_user, learner2) + other_submissions_file = create(:file, context: submission_learner1) + expect(file_accepted?(other_submissions_file)).to be false + end end end end diff --git a/spec/controllers/code_ocean/files_controller_spec.rb b/spec/controllers/code_ocean/files_controller_spec.rb index 2c555b08..476b72bc 100644 --- a/spec/controllers/code_ocean/files_controller_spec.rb +++ b/spec/controllers/code_ocean/files_controller_spec.rb @@ -7,6 +7,26 @@ describe CodeOcean::FilesController do before { allow(controller).to receive(:current_user).and_return(user) } + describe 'GET #show_protected_upload' do + context 'with a valid filename' do + let(:submission) { create(:submission, exercise: create(:audio_video)) } + + before { get :show_protected_upload, params: {filename: file.name_with_extension, id: file.id} } + + context 'with a binary file' do + let(:file) { submission.collect_files.detect {|file| file.file_type.file_extension == '.mp4' } } + + expect_assigns(file: :file) + expect_content_type('video/mp4') + expect_http_status(:ok) + + it 'sets the correct filename' do + expect(response.headers['Content-Disposition']).to include("attachment; filename=\"#{file.name_with_extension}\"") + end + end + end + end + describe 'POST #create' do let(:submission) { create(:submission, user: user) } diff --git a/spec/controllers/exercises_controller_spec.rb b/spec/controllers/exercises_controller_spec.rb index d0e9768a..05631045 100644 --- a/spec/controllers/exercises_controller_spec.rb +++ b/spec/controllers/exercises_controller_spec.rb @@ -6,7 +6,10 @@ describe ExercisesController do let(:exercise) { create(:dummy) } let(:user) { create(:admin) } - before { allow(controller).to receive(:current_user).and_return(user) } + before do + create(:test_file, context: exercise) + allow(controller).to receive(:current_user).and_return(user) + end describe 'PUT #batch_update' do let(:attributes) { {public: 'true'} } @@ -184,6 +187,17 @@ describe ExercisesController do expect_flash_message(:alert, :'exercises.implement.no_files') expect_redirect(:exercise) end + + context 'with other users accessing an unpublished exercise' do + let(:exercise) { create(:fibonacci, unpublished: true) } + let(:user) { create(:teacher) } + + before { perform_request.call } + + expect_assigns(exercise: :exercise) + expect_flash_message(:alert, :'exercises.implement.unpublished') + expect_redirect(:exercise) + end end describe 'GET #index' do @@ -220,6 +234,8 @@ describe ExercisesController do describe 'GET #reload' do context 'when being anyone' do + let(:exercise) { create(:fibonacci) } + before { get :reload, format: :json, params: {id: exercise.id} } expect_assigns(exercise: :exercise) diff --git a/spec/controllers/submissions_controller_spec.rb b/spec/controllers/submissions_controller_spec.rb index 29244a11..a55e1cf9 100644 --- a/spec/controllers/submissions_controller_spec.rb +++ b/spec/controllers/submissions_controller_spec.rb @@ -74,11 +74,9 @@ describe SubmissionsController do expect_assigns(file: :file) expect_assigns(submission: :submission) - expect_content_type('video/mp4') - expect_http_status(:ok) - it 'sets the correct filename' do - expect(response.headers['Content-Disposition']).to include("attachment; filename=\"#{file.name_with_extension}\"") + it 'sets the correct redirect' do + expect(response.location).to eq protected_upload_url(id: file, filename: file.name_with_extension) end end diff --git a/spec/features/authentication_spec.rb b/spec/features/authentication_spec.rb index 998d83fb..e28fce26 100644 --- a/spec/features/authentication_spec.rb +++ b/spec/features/authentication_spec.rb @@ -78,6 +78,25 @@ describe 'Authentication' do expect(page).to have_content(I18n.t('application.not_authorized')) end end + + context 'when the authentication token is used to login' do + let(:token) { create(:authentication_token, user: user) } + + it 'invalidates the token on login' do + mail.deliver_now + visit(rfc_link) + expect(token.reload.expire_at).to be_within(10.seconds).of(Time.zone.now) + end + + it 'does not allow a second login' do + mail.deliver_now + visit(rfc_link) + expect(page).to have_current_path(rfc_link) + visit(sign_out_path) + visit(rfc_link) + expect(page).to have_current_path(root_path) + end + end end end diff --git a/spec/features/editor_spec.rb b/spec/features/editor_spec.rb index 5892354b..193aad2d 100644 --- a/spec/features/editor_spec.rb +++ b/spec/features/editor_spec.rb @@ -23,6 +23,7 @@ describe 'Editor', js: true do }] end let(:user) { create(:teacher) } + let(:exercise_without_test) { create(:tdd) } before do visit(sign_in_path) @@ -93,12 +94,28 @@ describe 'Editor', js: true do end end + context 'when an exercise has one or more teacher-defined assessments' do + it 'displays the score button' do + visit(implement_exercise_path(exercise)) + expect(page).to have_content(exercise.title) + expect(page).to have_content(I18n.t('exercises.editor.score')) + end + end + + context 'when an exercise has no teacher-defined assessment' do + it 'disables the score button' do + visit(implement_exercise_path(exercise_without_test)) + expect(page).to have_content(exercise_without_test.title) + expect(page).not_to have_content(I18n.t('exercises.editor.score')) + end + end + it 'contains a button for submitting the exercise' do submission = build(:submission, user: user, exercise: exercise) allow(submission).to receive(:calculate_score).and_return(scoring_response) allow(Submission).to receive(:find).and_return(submission) click_button(I18n.t('exercises.editor.score')) - expect(page).not_to have_css('#submit_outdated') - expect(page).to have_css('#submit') + expect(page).not_to have_content(I18n.t('exercises.editor.tooltips.exercise_deadline_passed')) + expect(page).to have_content(I18n.t('exercises.editor.submit')) end end diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 1bd68eb3..324c0945 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -93,6 +93,25 @@ describe UserMailer do # A five minute tolerance is allowed to account for the time difference between `now` and the creation timestamp of the token. expect(token.expire_at - Time.zone.now).to be_within(5.minutes).of(7.days) end + + it 'sets the correct comment' do + expect(mail.body).to include(request_for_comment.comments.first.text) + end + + context 'with an HTML comment' do + let(:html_comment) { 'test' } + let(:escaped_comment) { '<b>test</b>' } + + before { request_for_comment.comments.first.update(text: html_comment) } + + it 'does not include the HTML tags' do + expect(mail.body).not_to include(html_comment) + end + + it 'includes escaped HTML tags' do + expect(mail.body).to include(escaped_comment) + end + end end describe '#got_new_comment_for_subscription' do @@ -128,21 +147,40 @@ describe UserMailer do # A five minute tolerance is allowed to account for the time difference between `now` and the creation timestamp of the token. expect(token.expire_at - Time.zone.now).to be_within(5.minutes).of(7.days) end + + it 'sets the correct comment' do + expect(mail.body).to include(request_for_comment.comments.first.text) + end + + context 'with an HTML comment' do + let(:html_comment) { 'test' } + let(:escaped_comment) { '<b>test</b>' } + + before { request_for_comment.comments.first.update(text: html_comment) } + + it 'does not include the HTML tags' do + expect(mail.body).not_to include(html_comment) + end + + it 'includes escaped HTML tags' do + expect(mail.body).to include(escaped_comment) + end + end end describe '#send_thank_you_note' do let(:user) { create(:learner) } - let(:token) { AuthenticationToken.find_by(user: user) } - let(:request_for_comments) { create(:rfc_with_comment, user: user) } - let(:receiver) { InternalUser.create(attributes_for(:teacher)) } - let(:mail) { described_class.send_thank_you_note(request_for_comments, receiver).deliver_now } + let(:receiver) { create(:teacher) } + let(:token) { AuthenticationToken.find_by(user: receiver) } + let(:request_for_comment) { create(:rfc_with_comment, user: user) } + let(:mail) { described_class.send_thank_you_note(request_for_comment, receiver).deliver_now } it 'sets the correct sender' do expect(mail.from).to include('codeocean@hpi.de') end it 'sets the correct subject' do - expect(mail.subject).to eq(I18n.t('mailers.user_mailer.send_thank_you_note.subject', author: request_for_comments.user.displayname)) + expect(mail.subject).to eq(I18n.t('mailers.user_mailer.send_thank_you_note.subject', author: request_for_comment.user.displayname)) end it 'sets the correct receiver' do @@ -150,7 +188,7 @@ describe UserMailer do end it 'includes the correct URL' do - expect(mail.body).to include(request_for_comment_url(request_for_comments, token: token.shared_secret)) + expect(mail.body).to include(request_for_comment_url(request_for_comment, token: token.shared_secret)) end it 'creates a new authentication token' do @@ -162,5 +200,24 @@ describe UserMailer do # A five minute tolerance is allowed to account for the time difference between `now` and the creation timestamp of the token. expect(token.expire_at - Time.zone.now).to be_within(5.minutes).of(7.days) end + + it 'sets the correct thank_you_note' do + expect(mail.body).to include(request_for_comment.thank_you_note) + end + + context 'with an HTML comment' do + let(:html_comment) { 'test' } + let(:escaped_comment) { '<b>test</b>' } + + before { request_for_comment.update(thank_you_note: html_comment) } + + it 'does not include the HTML tags' do + expect(mail.body).not_to include(html_comment) + end + + it 'includes escaped HTML tags' do + expect(mail.body).to include(escaped_comment) + end + end end end diff --git a/spec/models/code_ocean/file_spec.rb b/spec/models/code_ocean/file_spec.rb index 6a279a6d..cbbaf9f1 100644 --- a/spec/models/code_ocean/file_spec.rb +++ b/spec/models/code_ocean/file_spec.rb @@ -69,7 +69,27 @@ describe CodeOcean::File do end context 'when the path has been modified' do - before { file.update(native_file: '../../../../secrets.yml') } + before do + file.update_column(:native_file, '../../../../secrets.yml') # rubocop:disable Rails/SkipsModelValidations + file.reload + end + + it 'does not read the native file' do + expect(file.read).not_to be_present + end + end + + context 'when a symlink is used' do + let(:fake_upload_location) { File.join(CarrierWave::Uploader::Base.new.root, 'uploads', 'files', 'secrets.yml') } + + before do + FileUtils.touch Rails.root.join('config/secrets.yml') + File.symlink Rails.root.join('config/secrets.yml'), fake_upload_location + file.update_column(:native_file, '../secrets.yml') # rubocop:disable Rails/SkipsModelValidations + file.reload + end + + after { File.delete(fake_upload_location) } it 'does not read the native file' do expect(file.read).not_to be_present diff --git a/spec/models/exercise_spec.rb b/spec/models/exercise_spec.rb index 57bd66c9..8bbb7014 100644 --- a/spec/models/exercise_spec.rb +++ b/spec/models/exercise_spec.rb @@ -124,4 +124,41 @@ describe Exercise do expect(exercise.duplicate).to be_a(described_class) end end + + describe '#teacher_defined_assessment?' do + let(:exercise) { create(:dummy) } + + context 'when no assessment is defined' do + it 'returns false' do + expect(exercise).not_to be_teacher_defined_assessment + end + end + + context 'when unit tests are defined' do + before { create(:test_file, context: exercise) } + + it 'returns true' do + expect(exercise).to be_teacher_defined_assessment + end + end + + context 'when linter tests are defined' do + before { create(:test_file, context: exercise, role: 'teacher_defined_linter') } + + it 'returns true' do + expect(exercise).to be_teacher_defined_assessment + end + end + + context 'when unit and linter tests are defined' do + before do + create(:test_file, context: exercise) + create(:test_file, context: exercise, role: 'teacher_defined_linter') + end + + it 'returns true' do + expect(exercise).to be_teacher_defined_assessment + end + end + end end diff --git a/spec/policies/codeharbor_link_policy_spec.rb b/spec/policies/codeharbor_link_policy_spec.rb index 550700c1..f3541e50 100644 --- a/spec/policies/codeharbor_link_policy_spec.rb +++ b/spec/policies/codeharbor_link_policy_spec.rb @@ -54,7 +54,7 @@ describe CodeharborLinkPolicy do end end - permissions(:enabled?) do + permissions :enabled? do it 'reflects the config option' do %i[external_user admin teacher].each do |factory_name| expect(policy).to permit(create(factory_name), codeharbor_link) @@ -72,7 +72,7 @@ describe CodeharborLinkPolicy do allow(codeocean_config).to receive(:read).and_return(codeharbor_config) end - permissions(:enabled?) do + permissions :enabled? do it 'reflects the config option' do %i[external_user admin teacher].each do |factory_name| expect(policy).not_to permit(create(factory_name), codeharbor_link) diff --git a/spec/policies/execution_environment_policy_spec.rb b/spec/policies/execution_environment_policy_spec.rb index 651e9555..5654f920 100644 --- a/spec/policies/execution_environment_policy_spec.rb +++ b/spec/policies/execution_environment_policy_spec.rb @@ -7,19 +7,17 @@ describe ExecutionEnvironmentPolicy do let(:execution_environment) { build(:ruby) } - [:index?].each do |action| - permissions(action) do - it 'grants access to admins' do - expect(policy).to permit(build(:admin), execution_environment) - end + permissions :index? do + it 'grants access to admins' do + expect(policy).to permit(build(:admin), execution_environment) + end - it 'grants access to teachers' do - expect(policy).to permit(build(:teacher), execution_environment) - end + it 'grants access to teachers' do + expect(policy).to permit(build(:teacher), execution_environment) + end - it 'does not grant access to external users' do - expect(policy).not_to permit(build(:external_user), execution_environment) - end + it 'does not grant access to external users' do + expect(policy).not_to permit(build(:external_user), execution_environment) end end @@ -59,7 +57,7 @@ describe ExecutionEnvironmentPolicy do end end - permissions(:sync_all_to_runner_management?) do + permissions :sync_all_to_runner_management? do it 'grants access to the admin' do expect(policy).to permit(build(:admin)) end diff --git a/spec/policies/exercise_policy_spec.rb b/spec/policies/exercise_policy_spec.rb index 8fad12a6..3ccfe563 100644 --- a/spec/policies/exercise_policy_spec.rb +++ b/spec/policies/exercise_policy_spec.rb @@ -104,19 +104,79 @@ describe ExercisePolicy do end end - [:show?].each do |action| + permissions :show? do + it 'not grants access to external users' do + expect(policy).not_to permit(build(:external_user), exercise) + end + end + + %i[implement? working_times? intervention? search? reload?].each do |action| permissions(action) do - it 'not grants access to external users' do - expect(policy).not_to permit(build(:external_user), exercise) + context 'when the exercise has no visible files' do + let(:exercise) { create(:dummy) } + + it 'does not grant access to anyone' do + %i[admin external_user teacher].each do |factory_name| + expect(policy).not_to permit(build(factory_name), exercise) + end + end + end + + context 'when the exercise has visible files' do + let(:exercise) { create(:fibonacci) } + + it 'grants access to anyone' do + %i[admin external_user teacher].each do |factory_name| + expect(policy).to permit(build(factory_name), exercise) + end + end + end + + context 'when the exercise is published' do + let(:exercise) { create(:fibonacci, unpublished: false) } + + it 'grants access to anyone' do + %i[admin external_user teacher].each do |factory_name| + expect(policy).to permit(build(factory_name), exercise) + end + end + end + + context 'when the exercise is unpublished' do + let(:exercise) { create(:fibonacci, unpublished: true) } + + it 'grants access to admins' do + expect(policy).to permit(build(:admin), exercise) + end + + it 'grants access to the author' do + expect(policy).to permit(exercise.author, exercise) + end + + it 'does not grant access to everyone' do + %i[external_user teacher].each do |factory_name| + expect(policy).not_to permit(build(factory_name), exercise) + end + end end end end - %i[implement? submit?].each do |action| - permissions(action) do + permissions :submit? do + context 'when teacher-defined assessments are available' do + before { create(:test_file, context: exercise) } + it 'grants access to anyone' do %i[admin external_user teacher].each do |factory_name| - expect(policy).to permit(build(factory_name), Exercise.new) + expect(policy).to permit(build(factory_name), exercise) + end + end + end + + context 'when teacher-defined assessments are not available' do + it 'does not grant access to anyone' do + %i[admin external_user teacher].each do |factory_name| + expect(policy).not_to permit(build(factory_name), exercise) end end end diff --git a/spec/policies/external_user_policy_spec.rb b/spec/policies/external_user_policy_spec.rb index 5494b6fc..8e752327 100644 --- a/spec/policies/external_user_policy_spec.rb +++ b/spec/policies/external_user_policy_spec.rb @@ -16,14 +16,12 @@ describe ExternalUserPolicy do end end - [:index?].each do |action| - permissions(action) do - it 'grants access to admins and teachers only' do - expect(policy).to permit(build(:admin), ExternalUser.new) - expect(policy).to permit(build(:teacher), ExternalUser.new) - [:external_user].each do |factory_name| - expect(policy).not_to permit(build(factory_name), ExternalUser.new) - end + permissions :index? do + it 'grants access to admins and teachers only' do + expect(policy).to permit(build(:admin), ExternalUser.new) + expect(policy).to permit(build(:teacher), ExternalUser.new) + [:external_user].each do |factory_name| + expect(policy).not_to permit(build(factory_name), ExternalUser.new) end end end diff --git a/yarn.lock b/yarn.lock index f51a9762..8c87416f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,38 +17,38 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d" - integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.0.tgz#2a592fd89bacb1fcde68de31bee4f2f2dacb0e86" + integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== -"@babel/core@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8" - integrity sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw== +"@babel/core@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3" + integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.18.10" - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-module-transforms" "^7.18.9" - "@babel/helpers" "^7.18.9" - "@babel/parser" "^7.18.10" + "@babel/generator" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helpers" "^7.19.0" + "@babel/parser" "^7.19.0" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.18.10" - "@babel/types" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.18.10": - version "7.18.12" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.12.tgz#fa58daa303757bd6f5e4bbca91b342040463d9f4" - integrity sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg== +"@babel/generator@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a" + integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== dependencies: - "@babel/types" "^7.18.10" + "@babel/types" "^7.19.0" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" @@ -67,33 +67,33 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf" - integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz#537ec8339d53e806ed422f1e06c8f17d55b96bb0" + integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== dependencies: - "@babel/compat-data" "^7.18.8" + "@babel/compat-data" "^7.19.0" "@babel/helper-validator-option" "^7.18.6" browserslist "^4.20.2" semver "^6.3.0" "@babel/helper-create-class-features-plugin@^7.18.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz#d802ee16a64a9e824fcbf0a2ffc92f19d58550ce" - integrity sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" + integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" "@babel/helper-member-expression-to-functions" "^7.18.9" "@babel/helper-optimise-call-expression" "^7.18.6" "@babel/helper-replace-supers" "^7.18.9" "@babel/helper-split-export-declaration" "^7.18.6" -"@babel/helper-create-regexp-features-plugin@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz#3e35f4e04acbbf25f1b3534a657610a000543d3c" - integrity sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A== +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" + integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.1.0" @@ -122,13 +122,13 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz#940e6084a55dee867d33b4e487da2676365e86b0" - integrity sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A== +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== dependencies: - "@babel/template" "^7.18.6" - "@babel/types" "^7.18.9" + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" @@ -151,19 +151,19 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz#5a1079c005135ed627442df31a42887e80fcb712" - integrity sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g== +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" + integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" "@babel/helper-simple-access" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.18.6" - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -172,10 +172,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f" - integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== "@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" @@ -235,23 +235,23 @@ integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== "@babel/helper-wrap-function@^7.18.9": - version "7.18.11" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz#bff23ace436e3f6aefb61f85ffae2291c80ed1fb" - integrity sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz#89f18335cff1152373222f76a4b37799636ae8b1" + integrity sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg== dependencies: - "@babel/helper-function-name" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.18.11" - "@babel/types" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" -"@babel/helpers@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.9.tgz#4bef3b893f253a1eced04516824ede94dcfe7ff9" - integrity sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ== +"@babel/helpers@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18" + integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== dependencies: - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" "@babel/highlight@^7.18.6": version "7.18.6" @@ -262,10 +262,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.18.10", "@babel/parser@^7.18.11": - version "7.18.11" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" - integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== +"@babel/parser@^7.18.10", "@babel/parser@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c" + integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -283,13 +283,13 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-proposal-optional-chaining" "^7.18.9" -"@babel/plugin-proposal-async-generator-functions@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz#85ea478c98b0095c3e4102bff3b67d306ed24952" - integrity sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew== +"@babel/plugin-proposal-async-generator-functions@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.0.tgz#cf5740194f170467df20581712400487efc79ff1" + integrity sha512-nhEByMUTx3uZueJ/QkJuSlCfN4FGg+xy+vRsfGQGzSauq5ks2Deid2+05Q3KhfaUjvec1IGhw/Zm3cFm8JigTQ== dependencies: "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -547,16 +547,17 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-classes@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz#90818efc5b9746879b869d5ce83eb2aa48bbc3da" - integrity sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g== +"@babel/plugin-transform-classes@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20" + integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.19.0" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-replace-supers" "^7.18.9" "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" @@ -568,10 +569,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-destructuring@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz#68906549c021cb231bee1db21d3b5b095f8ee292" - integrity sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA== +"@babel/plugin-transform-destructuring@^7.18.13": + version "7.18.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz#9e03bc4a94475d62b7f4114938e6c5c33372cbf5" + integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow== dependencies: "@babel/helper-plugin-utils" "^7.18.9" @@ -647,14 +648,14 @@ "@babel/helper-simple-access" "^7.18.6" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz#545df284a7ac6a05125e3e405e536c5853099a06" - integrity sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A== +"@babel/plugin-transform-modules-systemjs@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz#5f20b471284430f02d9c5059d9b9a16d4b085a1f" + integrity sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A== dependencies: "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-validator-identifier" "^7.18.6" babel-plugin-dynamic-import-node "^2.3.3" @@ -666,13 +667,13 @@ "@babel/helper-module-transforms" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz#c89bfbc7cc6805d692f3a49bc5fc1b630007246d" - integrity sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg== +"@babel/plugin-transform-named-capturing-groups-regex@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.0.tgz#58c52422e4f91a381727faed7d513c89d7f41ada" + integrity sha512-HDSuqOQzkU//kfGdiHBt71/hkDTApw4U/cMVgKgX7PqfB3LOaK+2GtCEsBu1dL9CkswDm0Gwehht1dCr421ULQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-regexp-features-plugin" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-new-target@^7.18.6": version "7.18.6" @@ -737,12 +738,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-spread@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz#6ea7a6297740f381c540ac56caf75b05b74fb664" - integrity sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA== +"@babel/plugin-transform-spread@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" + integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-transform-sticky-regex@^7.18.6": @@ -782,17 +783,17 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/preset-env@7": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.10.tgz#83b8dfe70d7eea1aae5a10635ab0a5fe60dfc0f4" - integrity sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.0.tgz#fd18caf499a67d6411b9ded68dc70d01ed1e5da7" + integrity sha512-1YUju1TAFuzjIQqNM9WsF4U6VbD/8t3wEAlw3LFYuuEr+ywqLRcSXxFKz4DCEj+sN94l/XTDiUXYRrsvMpz9WQ== dependencies: - "@babel/compat-data" "^7.18.8" - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/compat-data" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-validator-option" "^7.18.6" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-async-generator-functions" "^7.18.10" + "@babel/plugin-proposal-async-generator-functions" "^7.19.0" "@babel/plugin-proposal-class-properties" "^7.18.6" "@babel/plugin-proposal-class-static-block" "^7.18.6" "@babel/plugin-proposal-dynamic-import" "^7.18.6" @@ -826,9 +827,9 @@ "@babel/plugin-transform-async-to-generator" "^7.18.6" "@babel/plugin-transform-block-scoped-functions" "^7.18.6" "@babel/plugin-transform-block-scoping" "^7.18.9" - "@babel/plugin-transform-classes" "^7.18.9" + "@babel/plugin-transform-classes" "^7.19.0" "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.18.13" "@babel/plugin-transform-dotall-regex" "^7.18.6" "@babel/plugin-transform-duplicate-keys" "^7.18.9" "@babel/plugin-transform-exponentiation-operator" "^7.18.6" @@ -838,9 +839,9 @@ "@babel/plugin-transform-member-expression-literals" "^7.18.6" "@babel/plugin-transform-modules-amd" "^7.18.6" "@babel/plugin-transform-modules-commonjs" "^7.18.6" - "@babel/plugin-transform-modules-systemjs" "^7.18.9" + "@babel/plugin-transform-modules-systemjs" "^7.19.0" "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.0" "@babel/plugin-transform-new-target" "^7.18.6" "@babel/plugin-transform-object-super" "^7.18.6" "@babel/plugin-transform-parameters" "^7.18.8" @@ -848,14 +849,14 @@ "@babel/plugin-transform-regenerator" "^7.18.6" "@babel/plugin-transform-reserved-words" "^7.18.6" "@babel/plugin-transform-shorthand-properties" "^7.18.6" - "@babel/plugin-transform-spread" "^7.18.9" + "@babel/plugin-transform-spread" "^7.19.0" "@babel/plugin-transform-sticky-regex" "^7.18.6" "@babel/plugin-transform-template-literals" "^7.18.9" "@babel/plugin-transform-typeof-symbol" "^7.18.9" "@babel/plugin-transform-unicode-escapes" "^7.18.10" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.18.10" + "@babel/types" "^7.19.0" babel-plugin-polyfill-corejs2 "^0.3.2" babel-plugin-polyfill-corejs3 "^0.5.3" babel-plugin-polyfill-regenerator "^0.4.0" @@ -874,13 +875,13 @@ esutils "^2.0.2" "@babel/runtime@7", "@babel/runtime@^7.8.4": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" - integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" + integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.18.10", "@babel/template@^7.18.6": +"@babel/template@^7.18.10": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== @@ -889,26 +890,26 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.18.10", "@babel/traverse@^7.18.11", "@babel/traverse@^7.18.9": - version "7.18.11" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.11.tgz#3d51f2afbd83ecf9912bcbb5c4d94e3d2ddaa16f" - integrity sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ== +"@babel/traverse@^7.18.9", "@babel/traverse@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed" + integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.18.10" + "@babel/generator" "^7.19.0" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.18.11" - "@babel/types" "^7.18.10" + "@babel/parser" "^7.19.0" + "@babel/types" "^7.19.0" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.4.4": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.10.tgz#4908e81b6b339ca7c6b7a555a5fc29446f26dde6" - integrity sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ== +"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.4.4": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" + integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== dependencies: "@babel/helper-string-parser" "^7.18.10" "@babel/helper-validator-identifier" "^7.18.6" @@ -926,10 +927,10 @@ dependencies: "@types/hammerjs" "^2.0.36" -"@fortawesome/fontawesome-free@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.1.2.tgz#d18880eddeadd42b1c64cb559f2f3d13d47a4a64" - integrity sha512-XwWADtfdSN73/udaFm+1mnGIj/ShDZNFMe/PRoqv3FhQ4GNI2PUN70yFTPsjq65Lw2C9i4TG5/hTbxXIXVCiqQ== +"@fortawesome/fontawesome-free@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.0.tgz#ba3510825b332816fe7190f28827f8cb33a298b5" + integrity sha512-CNR7qRIfCwWHNN7FnKUniva94edPdyQzil/zCwk3v6k4R6rR2Fr8i4s3PM7n/lyfPA6Zfko9z5WDzFxG9SW1uQ== "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" @@ -989,56 +990,46 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== -"@sentry/browser@^6.11.0": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.19.7.tgz#a40b6b72d911b5f1ed70ed3b4e7d4d4e625c0b5f" - integrity sha512-oDbklp4O3MtAM4mtuwyZLrgO1qDVYIujzNJQzXmi9YzymJCuzMLSRDvhY83NNDCRxf0pds4DShgYeZdbSyKraA== +"@sentry/browser@^7.11.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.12.1.tgz#2be6fa5c2529a2a75abac4d00aca786362302a1a" + integrity sha512-pgyL65CrGFLe8sKcEG8KXAuVTE8zkAsyTlv/AuME06cSdxzO/memPK/r3BI6EM7WupIdga+V5tQUldeT1kgHNA== dependencies: - "@sentry/core" "6.19.7" - "@sentry/types" "6.19.7" - "@sentry/utils" "6.19.7" + "@sentry/core" "7.12.1" + "@sentry/types" "7.12.1" + "@sentry/utils" "7.12.1" tslib "^1.9.3" -"@sentry/core@6.19.7": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.19.7.tgz#156aaa56dd7fad8c89c145be6ad7a4f7209f9785" - integrity sha512-tOfZ/umqB2AcHPGbIrsFLcvApdTm9ggpi/kQZFkej7kMphjT+SGBiQfYtjyg9jcRW+ilAR4JXC9BGKsdEQ+8Vw== +"@sentry/core@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.12.1.tgz#a22f1c530ed528a699ed204c36eb5fc8d308103d" + integrity sha512-DFHbzHFjukhlkRZ5xzfebx0IBzblW43kmfnalBBq7xEMscUvnhsYnlvL9Y20tuPZ/PrTcq4JAHbFluAvw6M0QQ== dependencies: - "@sentry/hub" "6.19.7" - "@sentry/minimal" "6.19.7" - "@sentry/types" "6.19.7" - "@sentry/utils" "6.19.7" + "@sentry/hub" "7.12.1" + "@sentry/types" "7.12.1" + "@sentry/utils" "7.12.1" tslib "^1.9.3" -"@sentry/hub@6.19.7": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.19.7.tgz#58ad7776bbd31e9596a8ec46365b45cd8b9cfd11" - integrity sha512-y3OtbYFAqKHCWezF0EGGr5lcyI2KbaXW2Ik7Xp8Mu9TxbSTuwTe4rTntwg8ngPjUQU3SUHzgjqVB8qjiGqFXCA== +"@sentry/hub@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-7.12.1.tgz#dffad40cd2b8f44df2d5f20a89df87879cbbf1c3" + integrity sha512-KLVnVqXf+CRmXNy9/T8K2/js7QvOQ94xtgP5KnWJbu2rl+JhxnIGiBRF51lPXFIatt7zWwB9qNdMS8lVsvLMGQ== dependencies: - "@sentry/types" "6.19.7" - "@sentry/utils" "6.19.7" + "@sentry/types" "7.12.1" + "@sentry/utils" "7.12.1" tslib "^1.9.3" -"@sentry/minimal@6.19.7": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.19.7.tgz#b3ee46d6abef9ef3dd4837ebcb6bdfd01b9aa7b4" - integrity sha512-wcYmSJOdvk6VAPx8IcmZgN08XTXRwRtB1aOLZm+MVHjIZIhHoBGZJYTVQS/BWjldsamj2cX3YGbGXNunaCfYJQ== - dependencies: - "@sentry/hub" "6.19.7" - "@sentry/types" "6.19.7" - tslib "^1.9.3" +"@sentry/types@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.12.1.tgz#eff76d938f9effc62a2ec76cd5c3f04de37f5c15" + integrity sha512-VGZs39SZgMcCGv7H0VyFy1LEFGsnFZH590JUopmz6nG63EpeYQ2xzhIoPNAiLKbyUvBEwukn+faCg3u3MGqhgQ== -"@sentry/types@6.19.7": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.19.7.tgz#c6b337912e588083fc2896eb012526cf7cfec7c7" - integrity sha512-jH84pDYE+hHIbVnab3Hr+ZXr1v8QABfhx39KknxqKWr2l0oEItzepV0URvbEhB446lk/S/59230dlUUIBGsXbg== - -"@sentry/utils@6.19.7": - version "6.19.7" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.19.7.tgz#6edd739f8185fd71afe49cbe351c1bbf5e7b7c79" - integrity sha512-z95ECmE3i9pbWoXQrD/7PgkBAzJYR+iXtPuTkpBjDKs86O3mT+PXOT3BAn79w2wkn7/i3vOGD2xVr1uiMl26dA== +"@sentry/utils@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.12.1.tgz#fcf80fdc332d0bd288e21b13efc7a2f0d604f75a" + integrity sha512-Dh8B13pC0u8uLM/zf+oZngyg808c6BDEO94F7H+h3IciCVVd92A0cOQwLGAEdf8srnJgpZJNAlSC8lFDhbFHzQ== dependencies: - "@sentry/types" "6.19.7" + "@sentry/types" "7.12.1" tslib "^1.9.3" "@trysound/sax@0.2.0": @@ -1085,9 +1076,9 @@ "@types/estree" "*" "@types/eslint@*": - version "8.4.5" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.5.tgz#acdfb7dd36b91cc5d812d7c093811a8f3d9b31e4" - integrity sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ== + version "8.4.6" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.6.tgz#7976f054c1bccfcf514bff0564c0c41df5c08207" + integrity sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -1144,9 +1135,9 @@ integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== "@types/node@*": - version "18.7.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.6.tgz#31743bc5772b6ac223845e18c3fc26f042713c83" - integrity sha512-EdxgKRXgYsNITy5mjjXjVE/CS8YENSdhiagGrLqjG0pvA2owgJ6i4l7wy/PFZGC0B1/H20lWKN7ONVDNYDZm7A== + version "18.7.16" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.16.tgz#0eb3cce1e37c79619943d2fd903919fc30850601" + integrity sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg== "@types/qs@*": version "6.9.7" @@ -1524,9 +1515,9 @@ body-parser@1.20.0: unpipe "1.0.0" bonjour-service@^1.0.11: - version "1.0.13" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.0.13.tgz#4ac003dc1626023252d58adf2946f57e5da450c1" - integrity sha512-LWKRU/7EqDUC9CTAQtuZl5HzBALoCYwtLhffW3et7vZMwv3bWLpJf8bRYlMD5OCcDpTfnPgNCV4yo9ZIaJGMiA== + version "1.0.14" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.0.14.tgz#c346f5bc84e87802d08f8d5a60b93f758e514ee7" + integrity sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ== dependencies: array-flatten "^2.1.2" dns-equal "^1.0.0" @@ -1538,10 +1529,10 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -bootstrap@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.0.tgz#838727fb60f1630db370fe57c63cbcf2962bb3d3" - integrity sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A== +bootstrap@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.1.tgz#45f97ff05cbe828bad807b014d8425f3aeb8ec3a" + integrity sha512-UQi3v2NpVPEi1n35dmRRzBJFlgvWHYwyem6yHhuT6afYF+sziEt46McRbT//kVXZ7b1YUYEVGdXEH74Nx3xzGA== bootswatch@^5.2.0: version "5.2.0" @@ -1607,9 +1598,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001370: - version "1.0.30001378" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz#3d2159bf5a8f9ca093275b0d3ecc717b00f27b67" - integrity sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA== + version "1.0.30001393" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001393.tgz#1aa161e24fe6af2e2ccda000fc2b94be0b0db356" + integrity sha512-N/od11RX+Gsk+1qY/jbPa0R6zJupEa0lxeBG598EbrtblxVCTJsQwbRBm6+V+rxpc5lHKdsXb9RY83cZIPLseA== chalk@^2.0.0: version "2.4.2" @@ -1784,12 +1775,11 @@ cookie@0.5.0: integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== core-js-compat@^3.21.0, core-js-compat@^3.22.1: - version "3.24.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.24.1.tgz#d1af84a17e18dfdd401ee39da9996f9a7ba887de" - integrity sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw== + version "3.25.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.1.tgz#6f13a90de52f89bbe6267e5620a412c7f7ff7e42" + integrity sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw== dependencies: browserslist "^4.21.3" - semver "7.0.0" core-util-is@~1.0.0: version "1.0.3" @@ -1806,9 +1796,9 @@ cross-spawn@^7.0.3: which "^2.0.1" css-declaration-sorter@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz#72ebd995c8f4532ff0036631f7365cce9759df14" - integrity sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og== + version "6.3.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz#be5e1d71b7a992433fb1c542c7a1b835e45682ec" + integrity sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w== css-loader@^6.7.1: version "6.7.1" @@ -2302,9 +2292,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.4.202: - version "1.4.224" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.224.tgz#ecf2eed395cfedcbbe634658ccc4b457f7b254c3" - integrity sha512-dOujC5Yzj0nOVE23iD5HKqrRSDj2SD7RazpZS/b/WX85MtO6/LzKDF4TlYZTBteB+7fvSg5JpWh0sN7fImNF8w== + version "1.4.246" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.246.tgz#802132d1bbd3ff32ce82fcd6a6ed6ab59b4366dc" + integrity sha512-/wFCHUE+Hocqr/LlVGsuKLIw4P2lBWwFIDcNMDpJGzyIysQV4aycpoOitAs32FT94EHKnNqDR/CVZJFbXEufJA== emojis-list@^3.0.0: version "3.0.0" @@ -2708,10 +2698,10 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -i18n-js@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/i18n-js/-/i18n-js-4.0.2.tgz#31a62520204e292675bd72e82abdfeeb9aed5f20" - integrity sha512-81geA9vPvWe4NI2uL8Ve+/2jM03Sj/IvGgFHPxij7DbSXbG3CfChRK1vBj2h2IRl6eVRZf3Eq4ug3PgeeWUUPA== +i18n-js@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/i18n-js/-/i18n-js-4.1.1.tgz#05ebd03c4d92f6dc26a00d7c5cfb90f9c326b67c" + integrity sha512-Uph8ghmfShexVhDcNtg5s40zprJZPrhW5iOEKsUwPiiBpIGN/0EJ9W7DTqhLFlWfAlpkFiaLxVxHsk8f+3KjIQ== dependencies: bignumber.js "*" lodash "*" @@ -2882,10 +2872,10 @@ jquery-ujs@^1.2.3: resolved "https://registry.yarnpkg.com/jquery-ujs/-/jquery-ujs-1.2.3.tgz#dcac6026ab7268e5ee41faf9d31c997cd4ddd603" integrity sha512-59wvfx5vcCTHMeQT1/OwFiAj+UffLIwjRIoXdpO7Z7BCFGepzq9T9oLVeoItjTqjoXfUrHJvV7QU6pUR+UzOoA== -"jquery@>=1.8.0 <4.0.0", jquery@>=1.9.1, jquery@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" - integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw== +"jquery@>=1.8.0 <4.0.0", jquery@>=1.9.1, jquery@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.1.tgz#fab0408f8b45fc19f956205773b62b292c147a16" + integrity sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw== js-tokens@^4.0.0: version "4.0.0" @@ -3830,10 +3820,10 @@ sass-loader@^13.0.2: klona "^2.0.4" neo-async "^2.6.2" -sass@^1.54.4: - version "1.54.4" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.4.tgz#803ff2fef5525f1dd01670c3915b4b68b6cba72d" - integrity sha512-3tmF16yvnBwtlPrNBHw/H907j8MlOX8aTBnlNX1yrKx24RKcJGPyLhFUwkoKBKesR3unP93/2z14Ll8NicwQUA== +sass@^1.54.9: + version "1.54.9" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.9.tgz#b05f14ed572869218d1a76961de60cd647221762" + integrity sha512-xb1hjASzEH+0L0WI9oFjqhRi51t/gagWnxLiwUNMltA0Ab6jIDkAacgKiGYKM9Jhy109osM7woEEai6SXeJo5Q== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -3873,17 +3863,12 @@ select-hose@^2.0.0: integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== selfsigned@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.1.tgz#8b2df7fa56bf014d19b6007655fff209c0ef0a56" - integrity sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ== + version "2.1.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61" + integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== dependencies: node-forge "^1" -semver@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -3955,10 +3940,10 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -shakapacker@6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/shakapacker/-/shakapacker-6.5.1.tgz#3c1554771c0498ea757cb86dfc06811875f4c2bd" - integrity sha512-yOVvek0fxAU3FUjWcT7GxpJ/zAbhKbGEw78HHYfMAHE95cDhxTAQjBwvChP/PlceiteXUryRI0Mrdy7jqXvmnw== +shakapacker@6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/shakapacker/-/shakapacker-6.5.2.tgz#dda95543107a71c7ada3f6ee102a1a31563c6738" + integrity sha512-32hpr/AuyQJEk/4J8quL/xLPl+NPR0mBvJ3D9AtwHIkbSTUA0++LZrvVO+aQ4S1Uy3Iz2KSI/JVRGGD/C4SFFg== dependencies: glob "^7.2.0" js-yaml "^4.1.0" @@ -4148,10 +4133,10 @@ tapable@^2.0, tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.5: - version "5.3.5" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.5.tgz#f7d82286031f915a4f8fb81af4bd35d2e3c011bc" - integrity sha512-AOEDLDxD2zylUGf/wxHxklEkOe2/r+seuyOWujejFrIxHf11brA1/dWQNIgXa1c6/Wkxgu7zvv0JhOWfc2ELEA== +terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.6: + version "5.3.6" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz#5590aec31aa3c6f771ce1b1acca60639eab3195c" + integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ== dependencies: "@jridgewell/trace-mapping" "^0.3.14" jest-worker "^27.4.5" @@ -4160,9 +4145,9 @@ terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.5: terser "^5.14.1" terser@^5.14.1: - version "5.14.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" - integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== + version "5.15.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.15.0.tgz#e16967894eeba6e1091509ec83f0c60e179f2425" + integrity sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA== dependencies: "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0" @@ -4209,6 +4194,11 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typed-assert@^1.0.8: + version "1.0.9" + resolved "https://registry.yarnpkg.com/typed-assert/-/typed-assert-1.0.9.tgz#8af9d4f93432c4970ec717e3006f33f135b06213" + integrity sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg== + underscore@^1.13.4: version "1.13.4" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee" @@ -4243,9 +4233,9 @@ unpipe@1.0.0, unpipe@~1.0.0: integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== update-browserslist-db@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38" - integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q== + version "1.0.7" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz#16279639cff1d0f800b14792de43d97df2d11b7d" + integrity sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg== dependencies: escalade "^3.1.1" picocolors "^1.0.0" @@ -4349,10 +4339,10 @@ webpack-dev-middleware@^5.3.1: range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@^4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.10.0.tgz#de270d0009eba050546912be90116e7fd740a9ca" - integrity sha512-7dezwAs+k6yXVFZ+MaL8VnE+APobiO3zvpp3rBHe/HmWQ+avwh0Q3d0xxacOiBybZZ3syTZw9HXzpa3YNbAZDQ== +webpack-dev-server@^4.11.0: + version "4.11.0" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.11.0.tgz#290ee594765cd8260adfe83b2d18115ea04484e7" + integrity sha512-L5S4Q2zT57SK7tazgzjMiSMBdsw+rGYIX27MgPgx7LDhWO0lViPrHKoLS7jo5In06PWYAhlYu3PbyoC6yAThbw== dependencies: "@types/bonjour" "^3.5.9" "@types/connect-history-api-fallback" "^1.3.5" @@ -4397,6 +4387,13 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== +webpack-subresource-integrity@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz#8b7606b033c6ccac14e684267cb7fb1f5c2a132a" + integrity sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q== + dependencies: + typed-assert "^1.0.8" + webpack@^5.74.0: version "5.74.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"