diff --git a/.annotaterb.yml b/.annotaterb.yml new file mode 100644 index 00000000..3b53dce0 --- /dev/null +++ b/.annotaterb.yml @@ -0,0 +1,58 @@ +--- +:position: before +:position_in_additional_file_patterns: before +:position_in_class: before +:position_in_factory: before +:position_in_fixture: before +:position_in_routes: before +:position_in_serializer: before +:position_in_test: before +:classified_sort: true +:exclude_controllers: true +:exclude_factories: true +:exclude_fixtures: false +:exclude_helpers: true +:exclude_scaffolds: true +:exclude_serializers: false +:exclude_sti_subclasses: false +:exclude_tests: false +:force: false +:format_markdown: false +:format_rdoc: false +:format_yard: false +:frozen: false +:ignore_model_sub_dir: false +:ignore_unknown_models: false +:include_version: false +:show_check_constraints: false +:show_complete_foreign_keys: false +:show_foreign_keys: true +:show_indexes: true +:simple_indexes: false +:sort: false +:timestamp: false +:trace: false +:with_comment: true +:with_column_comments: true +:with_table_comments: true +:active_admin: false +:command: +:debug: false +:hide_default_column_types: '' +:hide_limit_column_types: '' +:ignore_columns: +:ignore_routes: +:models: true +:routes: false +:skip_on_db_migrate: false +:target_action: :do_annotations +:wrapper: +:wrapper_close: +:wrapper_open: +:classes_default_to_s: [] +:additional_file_patterns: [] +:model_dir: +- app/models +:require: [] +:root_dir: +- '' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a28aa8e..ae1b667c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,6 +77,33 @@ jobs: run: | ./node_modules/eslint/bin/eslint.js "app/javascript/**/*.js" + brakeman: + name: Brakeman + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.4.1 + - name: Cache gems + uses: actions/cache@v4 + with: + path: vendor/bundle + key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gem- + - name: Install gems + run: | + gem install bundler + bundle config path vendor/bundle + bundle install --jobs 4 --retry 4 + yarn install + sudo apt-get update + sudo apt-get install libvips + - name: Run Brakeman + run: bundle exec brakeman -q --exit-on-warn + rspec: name: RSpec runs-on: ubuntu-latest diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2315d2b1..3250e475 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04-arm steps: - name: Checkout code diff --git a/Dockerfile b/Dockerfile index cfabc332..23fa1ad7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.4.1-alpine as fonts +FROM ruby:3.4.1-alpine AS fonts WORKDIR /fonts @@ -6,7 +6,7 @@ RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto- RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")' -FROM ruby:3.4.1-alpine as webpack +FROM ruby:3.4.1-alpine AS webpack ENV RAILS_ENV=production ENV NODE_ENV=production @@ -32,7 +32,7 @@ COPY ./app/views ./app/views RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker -FROM ruby:3.4.1-alpine as app +FROM ruby:3.4.1-alpine AS app ENV RAILS_ENV=production ENV BUNDLE_WITHOUT="development:test" diff --git a/Gemfile b/Gemfile index 39cf1bea..6961ac80 100644 --- a/Gemfile +++ b/Gemfile @@ -46,7 +46,6 @@ gem 'twitter_cldr', require: false gem 'tzinfo-data' group :development, :test do - gem 'annotate' gem 'better_html' gem 'bullet' gem 'debug' @@ -63,6 +62,9 @@ group :development, :test do end group :development do + gem 'annotaterb' + gem 'brakeman', require: false + gem 'foreman', require: false gem 'letter_opener_web' gem 'web-console' end diff --git a/Gemfile.lock b/Gemfile.lock index de6e71b3..984b23c4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -74,9 +74,7 @@ GEM uri (>= 0.13.1) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) - annotate (2.6.5) - activerecord (>= 2.3.0) - rake (>= 0.8.7) + annotaterb (4.14.0) arabic-letter-connector (0.1.1) ast (2.4.2) aws-eventstream (1.3.0) @@ -120,6 +118,8 @@ GEM bindex (0.8.1) bootsnap (1.18.4) msgpack (~> 1.2) + brakeman (7.0.0) + racc builder (3.3.0) bullet (8.0.0) activesupport (>= 3.0.0) @@ -229,6 +229,7 @@ GEM ffi (1.17.1-arm64-darwin) ffi (1.17.1-x86_64-linux-gnu) ffi (1.17.1-x86_64-linux-musl) + foreman (0.88.1) geom2d (0.4.1) globalid (1.2.1) activesupport (>= 6.1) @@ -337,18 +338,18 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.4) - nokogiri (1.18.2) + nokogiri (1.18.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.18.2-aarch64-linux-gnu) + nokogiri (1.18.3-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.2-aarch64-linux-musl) + nokogiri (1.18.3-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.18.2-arm64-darwin) + nokogiri (1.18.3-arm64-darwin) racc (~> 1.4) - nokogiri (1.18.2-x86_64-linux-gnu) + nokogiri (1.18.3-x86_64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.2-x86_64-linux-musl) + nokogiri (1.18.3-x86_64-linux-musl) racc (~> 1.4) oj (3.16.8) bigdecimal (>= 3.0) @@ -588,13 +589,14 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES - annotate + annotaterb arabic-letter-connector aws-sdk-s3 aws-sdk-secretsmanager azure-storage-blob better_html bootsnap + brakeman bullet cancancan capybara @@ -610,6 +612,7 @@ DEPENDENCIES faker faraday faraday-follow_redirects + foreman google-cloud-storage hexapdf image_processing diff --git a/app/controllers/account_configs_controller.rb b/app/controllers/account_configs_controller.rb index 02aee521..21c380a2 100644 --- a/app/controllers/account_configs_controller.rb +++ b/app/controllers/account_configs_controller.rb @@ -39,7 +39,7 @@ class AccountConfigsController < ApplicationController end def account_config_params - params.required(:account_config).permit!.tap do |attrs| + params.required(:account_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs| attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0]) end end diff --git a/app/controllers/api/submitters_controller.rb b/app/controllers/api/submitters_controller.rb index f85e2f60..1fd06d74 100644 --- a/app/controllers/api/submitters_controller.rb +++ b/app/controllers/api/submitters_controller.rb @@ -146,9 +146,15 @@ module Api if attrs[:completed] submitter.values = Submitters::SubmitValues.merge_default_values(submitter) - submitter.values = Submitters::SubmitValues.merge_formula_values(submitter) submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter) + formula_values = Submitters::SubmitValues.build_formula_values(submitter) + + if formula_values.present? + submitter.values = submitter.values.merge(formula_values) + submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter) + end + submitter.values = submitter.values.transform_values do |v| v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v end diff --git a/app/controllers/notifications_settings_controller.rb b/app/controllers/notifications_settings_controller.rb index 57b030fc..f03f09c8 100644 --- a/app/controllers/notifications_settings_controller.rb +++ b/app/controllers/notifications_settings_controller.rb @@ -39,7 +39,7 @@ class NotificationsSettingsController < ApplicationController end def email_config_params - params.require(:account_config).permit!.tap do |attrs| + params.require(:account_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs| attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMINDERS]) end end diff --git a/app/controllers/personalization_settings_controller.rb b/app/controllers/personalization_settings_controller.rb index 76d3f886..9812aaee 100644 --- a/app/controllers/personalization_settings_controller.rb +++ b/app/controllers/personalization_settings_controller.rb @@ -50,7 +50,7 @@ class PersonalizationSettingsController < ApplicationController end def account_config_params - attrs = params.require(:account_config).permit! + attrs = params.require(:account_config).permit(:key, :value, { value: {} }, { value: [] }) return attrs if attrs[:value].is_a?(String) diff --git a/app/controllers/submissions_preview_controller.rb b/app/controllers/submissions_preview_controller.rb index 1df14e21..7986e799 100644 --- a/app/controllers/submissions_preview_controller.rb +++ b/app/controllers/submissions_preview_controller.rb @@ -20,7 +20,7 @@ class SubmissionsPreviewController < ApplicationController @submission ||= Submission.find_by!(slug: params[:slug]) - raise ActionController::RoutingError if @submission.account.archived_at? + raise ActionController::RoutingError, I18n.t('not_found') if @submission.account.archived_at? if !@submission.submitters.all?(&:completed_at?) && !signature_valid && (!current_user || !current_ability.can?(:read, @submission)) diff --git a/app/controllers/user_configs_controller.rb b/app/controllers/user_configs_controller.rb index 834cc1d9..09b7e6c5 100644 --- a/app/controllers/user_configs_controller.rb +++ b/app/controllers/user_configs_controller.rb @@ -26,7 +26,7 @@ class UserConfigsController < ApplicationController end def user_config_params - params.required(:user_config).permit!.tap do |attrs| + params.required(:user_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs| attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0]) end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4a195104..47a88e05 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -45,17 +45,16 @@ class UsersController < ApplicationController return redirect_to settings_users_path, notice: I18n.t('unable_to_update_user') if Docuseal.demo? attrs = user_params.compact_blank.merge(user_params.slice(:archived_at)) - attrs.delete(:role) if !role_valid?(attrs[:role]) || current_user == @user if params.dig(:user, :account_id).present? - account = Account.accessible_by(current_ability).find(params[:user][:account_id]) + account = Account.accessible_by(current_ability).find(params.dig(:user, :account_id)) authorize!(:manage, account) @user.account = account end - if @user.update(attrs) + if @user.update(attrs.except(current_user == @user ? :role : nil)) redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_updated') else render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity @@ -84,8 +83,11 @@ class UsersController < ApplicationController def user_params if params.key?(:user) - params.require(:user).permit(:email, :first_name, :last_name, :password, - :role, :archived_at, :account_id) + permitted_params = %i[email first_name last_name password archived_at] + + permitted_params << :role if role_valid?(params.dig(:user, :role)) + + params.require(:user).permit(permitted_params) else {} end diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index b7d17e27..addf7570 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -1037,7 +1037,7 @@ export default { }, []) }, formulaFields () { - return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment') + return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment' && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f)) }, attachmentsIndex () { return this.attachments.reduce((acc, a) => { diff --git a/app/models/submitter.rb b/app/models/submitter.rb index 97a93773..45e38292 100644 --- a/app/models/submitter.rb +++ b/app/models/submitter.rb @@ -55,6 +55,7 @@ class Submitter < ApplicationRecord has_many_attached :attachments has_many_attached :preview_documents has_many :template_accesses, through: :template + has_many :email_events, as: :emailable, dependent: (Docuseal.multitenant? ? nil : :destroy) has_many :document_generation_events, dependent: :destroy has_many :submission_events, dependent: :destroy @@ -63,6 +64,8 @@ class Submitter < ApplicationRecord scope :completed, -> { where.not(completed_at: nil) } + after_destroy :anonymize_email_events, if: -> { Docuseal.multitenant? } + def status if declined_at? 'declined' @@ -108,4 +111,12 @@ class Submitter < ApplicationRecord fields.any? { |f| f['submitter_uuid'] == uuid && signature_field_types.include?(f['type']) } end end + + private + + def anonymize_email_events + email_events.each do |event| + event.update!(email: Digest::MD5.base64digest(event.email)) + end + end end diff --git a/app/views/templates_preferences/show.html.erb b/app/views/templates_preferences/show.html.erb index 54532ad0..12c020b1 100644 --- a/app/views/templates_preferences/show.html.erb +++ b/app/views/templates_preferences/show.html.erb @@ -285,12 +285,12 @@
<%= ff.text_field :name, class: 'w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer mb-2', autocomplete: 'off', placeholder: "#{index + 1}#{(index + 1).ordinal} Party", required: true %> <% if @template.submitters.size == 2 %> - <%= ff.email_field :email, class: 'base-input', autocomplete: 'off', placeholder: t('default_email'), disabled: ff.object.is_requester || ff.object.invite_by_uuid.present? || ff.object.optional_invite_by_uuid.present?, id: field_uuid = SecureRandom.uuid %> + <%= tag.input name: ff.field_name(:email), value: ff.object.email, type: :email, class: 'base-input', multiple: true, autocomplete: 'off', placeholder: t('default_email'), disabled: ff.object.is_requester || ff.object.invite_by_uuid.present? || ff.object.optional_invite_by_uuid.present?, id: field_uuid = SecureRandom.uuid %> <% else %> <%= ff.select :option, [[t('not_specified'), 'not_set'], [t('submission_requester'), 'is_requester'], [t('specified_email'), 'email'], *(@template.submitters - [submitter]).flat_map { |e| [[t('invite_by_name', name: e['name']), "invite_by_#{e['uuid']}"], [t('invite_by_name', name: e['name']) + " (#{t(:optional).capitalize})", "optional_invite_by_#{e['uuid']}"]] }, *(@template.submitters - [submitter]).map { |e| [t('same_as_name', name: e['name']), "linked_to_#{e['uuid']}"] }], {}, class: 'base-select mb-3' %> - <%= ff.email_field :email, class: "base-input #{'hidden' if item.option != 'email'}", autocomplete: 'off', placeholder: t('default_email'), id: email_field_uuid %> + <%= tag.input name: ff.field_name(:email), type: :email, value: ff.object.email, multiple: true, class: "base-input #{'hidden' if item.option != 'email'}", autocomplete: 'off', placeholder: t('default_email'), id: email_field_uuid %> <% end %>
<% if @template.submitters.size == 2 %> diff --git a/config/brakeman.ignore b/config/brakeman.ignore new file mode 100644 index 00000000..0b0632ab --- /dev/null +++ b/config/brakeman.ignore @@ -0,0 +1,8 @@ +{ + "ignored_warnings": [ + { + "fingerprint": "25f4ce5fee1e1180fa1919dc4ee78db3ab3457a956e4679503aa745771a43836", + "note": "Permitted parameters are necessary for creating submitters via API" + } + ] +} diff --git a/db/migrate/20250225111255_create_console1984_tables.console1984.rb b/db/migrate/20250225111255_create_console1984_tables.console1984.rb new file mode 100644 index 00000000..fe6a454a --- /dev/null +++ b/db/migrate/20250225111255_create_console1984_tables.console1984.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class CreateConsole1984Tables < ActiveRecord::Migration[7.0] + def change + create_table :console1984_sessions do |t| + t.text :reason + t.references :user, null: false, index: false + t.timestamps + + t.index :created_at + t.index %i[user_id created_at] + end + + create_table :console1984_users do |t| + t.string :username, null: false + t.timestamps + + t.index [:username] + end + + create_table :console1984_commands do |t| + t.text :statements + t.references :sensitive_access + t.references :session, null: false, index: false + t.timestamps + + t.index %i[session_id created_at sensitive_access_id], name: 'on_session_and_sensitive_chronologically' + end + + create_table :console1984_sensitive_accesses do |t| + t.text :justification + t.references :session, null: false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 89cf0fae..34aaa8bf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_12_07_172237) do +ActiveRecord::Schema[8.0].define(version: 2025_02_25_111255) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -112,6 +112,40 @@ ActiveRecord::Schema[7.2].define(version: 2024_12_07_172237) do t.index ["submitter_id"], name: "index_completed_submitters_on_submitter_id", unique: true end + create_table "console1984_commands", force: :cascade do |t| + t.text "statements" + t.bigint "sensitive_access_id" + t.bigint "session_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["sensitive_access_id"], name: "index_console1984_commands_on_sensitive_access_id" + t.index ["session_id", "created_at", "sensitive_access_id"], name: "on_session_and_sensitive_chronologically" + end + + create_table "console1984_sensitive_accesses", force: :cascade do |t| + t.text "justification" + t.bigint "session_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["session_id"], name: "index_console1984_sensitive_accesses_on_session_id" + end + + create_table "console1984_sessions", force: :cascade do |t| + t.text "reason" + t.bigint "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["created_at"], name: "index_console1984_sessions_on_created_at" + t.index ["user_id", "created_at"], name: "index_console1984_sessions_on_user_id_and_created_at" + end + + create_table "console1984_users", force: :cascade do |t| + t.string "username", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["username"], name: "index_console1984_users_on_username" + end + create_table "document_generation_events", force: :cascade do |t| t.bigint "submitter_id", null: false t.string "event_name", null: false diff --git a/lib/download_utils.rb b/lib/download_utils.rb index 3fc32950..78316b80 100644 --- a/lib/download_utils.rb +++ b/lib/download_utils.rb @@ -8,19 +8,20 @@ module DownloadUtils module_function def call(url) - uri = Addressable::URI.parse(url) + uri = begin + URI(url) + rescue URI::Error + Addressable::URI.parse(url).normalize + end if Docuseal.multitenant? - raise UnableToDownload, "Error loading: #{uri.display_uri}. Only HTTPS is allowed." if uri.scheme != 'https' - - if uri.host.in?(LOCALHOSTS) - raise UnableToDownload, "Error loading: #{uri.display_uri}. Can't download from localhost." - end + raise UnableToDownload, "Error loading: #{uri}. Only HTTPS is allowed." if uri.scheme != 'https' + raise UnableToDownload, "Error loading: #{uri}. Can't download from localhost." if uri.host.in?(LOCALHOSTS) end - resp = conn.get(uri.display_uri.to_s) + resp = conn.get(uri) - raise UnableToDownload, "Error loading: #{uri.display_uri}" if resp.status >= 400 + raise UnableToDownload, "Error loading: #{uri}" if resp.status >= 400 resp end diff --git a/lib/replace_email_variables.rb b/lib/replace_email_variables.rb index 430b5757..a78cedf9 100644 --- a/lib/replace_email_variables.rb +++ b/lib/replace_email_variables.rb @@ -15,7 +15,10 @@ module ReplaceEmailVariables SUBMITTER_SLUG = /\{+submitter\.slug\}+/i SUBMISSION_LINK = /\{+submission\.link\}+/i SUBMISSION_ID = /\{+submission\.id\}+/i - SUBMISSION_SUBMITTERS = /\{+submission\.submitters\}+/i + SUBMITTERS = /\{+(?:submission\.)?submitters\}+/i + SUBMITTERS_N_EMAIL = /\{+submitters\[(?\d+)\]\.email\}+/i + SUBMITTERS_N_NAME = /\{+submitters\[(?\d+)\]\.name\}+/i + SUBMITTERS_N_FIRST_NAME = /\{+submitters\[(?\d+)\]\.first_name\}+/i DOCUMENTS_LINKS = /\{+documents\.links\}+/i DOCUMENTS_LINK = /\{+documents\.link\}+/i @@ -37,13 +40,25 @@ module ReplaceEmailVariables text = replace(text, SUBMISSION_LINK, html_escape:) do submitter.submission ? build_submission_link(submitter.submission) : '' end - text = replace(text, SUBMISSION_SUBMITTERS, html_escape:) { build_submission_submitters(submitter.submission) } + text = replace(text, SUBMITTERS, html_escape:) { build_submission_submitters(submitter.submission) } text = replace(text, DOCUMENTS_LINKS, html_escape:) { build_documents_links_text(submitter, sig) } text = replace(text, DOCUMENTS_LINK, html_escape:) { build_documents_links_text(submitter, sig) } text = replace(text, ACCOUNT_NAME, html_escape:) { submitter.submission.account.name } text = replace(text, SENDER_NAME, html_escape:) { submitter.submission.created_by_user&.full_name } text = replace(text, SENDER_FIRST_NAME, html_escape:) { submitter.submission.created_by_user&.first_name } + text = replace(text, SUBMITTERS_N_NAME, html_escape:) do |match| + build_submitters_n_field(submitter.submission, match[:index].to_i - 1, :name) + end + + text = replace(text, SUBMITTERS_N_EMAIL, html_escape:) do |match| + build_submitters_n_field(submitter.submission, match[:index].to_i - 1, :email) + end + + text = replace(text, SUBMITTERS_N_FIRST_NAME, html_escape:) do |match| + build_submitters_n_field(submitter.submission, match[:index].to_i - 1, :first_name) + end + replace(text, SENDER_EMAIL, html_escape:) { submitter.submission.created_by_user&.email.to_s.sub(/\+\w+@/, '@') } end # rubocop:enable Metrics @@ -54,12 +69,18 @@ module ReplaceEmailVariables ) end + def build_submitters_n_field(submission, index, field_name) + uuid = (submission.template_submitters || submission.template.submitters).dig(index, 'uuid') + + submission.submitters.find { |s| s.uuid == uuid }.try(field_name) + end + def replace(text, var, html_escape: false) text.gsub(var) do if html_escape - ERB::Util.html_escape(yield) + ERB::Util.html_escape(yield(Regexp.last_match)) else - yield + yield(Regexp.last_match) end end end diff --git a/lib/send_webhook_request.rb b/lib/send_webhook_request.rb index d2e6ebf4..96442441 100644 --- a/lib/send_webhook_request.rb +++ b/lib/send_webhook_request.rb @@ -3,10 +3,28 @@ module SendWebhookRequest USER_AGENT = 'DocuSeal.com Webhook' + LOCALHOSTS = %w[0.0.0.0 127.0.0.1 localhost].freeze + + HttpsError = Class.new(StandardError) + LocalhostError = Class.new(StandardError) + module_function def call(webhook_url, event_type:, data:) - Faraday.post(webhook_url.url) do |req| + uri = begin + URI(webhook_url.url) + rescue URI::Error + Addressable::URI.parse(webhook_url.url).normalize + end + + if Docuseal.multitenant? + raise HttpsError, 'Only HTTPS is allowed.' if uri.scheme != 'https' && + !AccountConfig.exists?(key: :allow_http, + account_id: webhook_url.account_id) + raise LocalhostError, "Can't send to localhost." if uri.host.in?(LOCALHOSTS) + end + + Faraday.post(uri) do |req| req.headers['Content-Type'] = 'application/json' req.headers['User-Agent'] = USER_AGENT req.headers.merge!(webhook_url.secret.to_h) if webhook_url.secret.present? diff --git a/lib/submissions.rb b/lib/submissions.rb index 8a89aaa6..9843fd0f 100644 --- a/lib/submissions.rb +++ b/lib/submissions.rb @@ -107,6 +107,7 @@ module Submissions def normalize_email(email) return if email.blank? + return if email.is_a?(Numeric) return email.downcase if email.to_s.include?(',') || email.to_s.match?(/\.(?:gob|om|mm|cm|et|mo|nz|za|ie)\z/) || diff --git a/lib/submissions/create_from_submitters.rb b/lib/submissions/create_from_submitters.rb index c2144ad8..083c3573 100644 --- a/lib/submissions/create_from_submitters.rb +++ b/lib/submissions/create_from_submitters.rb @@ -225,9 +225,15 @@ module Submissions def assign_completed_attributes(submitter) submitter.values = Submitters::SubmitValues.merge_default_values(submitter) - submitter.values = Submitters::SubmitValues.merge_formula_values(submitter) submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter) + formula_values = Submitters::SubmitValues.build_formula_values(submitter) + + if formula_values.present? + submitter.values = submitter.values.merge(formula_values) + submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter) + end + submitter.values = submitter.values.transform_values do |v| v == '{{date}}' ? Time.current.in_time_zone(submitter.submission.account.timezone).to_date.to_s : v end diff --git a/lib/submitters/submit_values.rb b/lib/submitters/submit_values.rb index b23e7005..15f2da02 100644 --- a/lib/submitters/submit_values.rb +++ b/lib/submitters/submit_values.rb @@ -53,9 +53,17 @@ module Submitters submitter.completed_at = Time.current submitter.ip = request.remote_ip submitter.ua = request.user_agent + submitter.values = merge_default_values(submitter) submitter.values = maybe_remove_condition_values(submitter) - submitter.values = merge_formula_values(submitter) + + formula_values = build_formula_values(submitter) + + if formula_values.present? + submitter.values = submitter.values.merge(formula_values) + submitter.values = maybe_remove_condition_values(submitter) + end + submitter.values = submitter.values.transform_values do |v| v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v end @@ -149,7 +157,7 @@ module Submitters default_values.compact_blank.merge(submitter.values) end - def merge_formula_values(submitter) + def build_formula_values(submitter) computed_values = submitter.submission.template_fields.each_with_object({}) do |field, acc| next if field['submitter_uuid'] != submitter.uuid next if field['type'] == 'payment' @@ -161,7 +169,7 @@ module Submitters acc[field['uuid']] = calculate_formula_value(formula, submitter.values.merge(acc.compact_blank)) end - submitter.values.merge(computed_values.compact_blank) + computed_values.compact_blank end def calculate_formula_value(_formula, _values) diff --git a/lib/tasks/annotate_rb.rake b/lib/tasks/annotate_rb.rake new file mode 100644 index 00000000..e8368b2e --- /dev/null +++ b/lib/tasks/annotate_rb.rake @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# This rake task was added by annotate_rb gem. + +# Can set `ANNOTATERB_SKIP_ON_DB_TASKS` to be anything to skip this +if Rails.env.development? && ENV['ANNOTATERB_SKIP_ON_DB_TASKS'].nil? + require 'annotate_rb' + + AnnotateRb::Core.load_rake_tasks +end diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake deleted file mode 100644 index d4cb5881..00000000 --- a/lib/tasks/auto_annotate_models.rake +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -if Rails.env.development? - require 'annotate' - - task set_annotation_options: :environment do - Annotate.set_defaults( - 'active_admin' => 'false', - 'additional_file_patterns' => [], - 'routes' => 'false', - 'models' => 'true', - 'position_in_routes' => 'before', - 'position_in_class' => 'before', - 'position_in_test' => 'before', - 'position_in_fixture' => 'before', - 'position_in_factory' => 'before', - 'position_in_serializer' => 'before', - 'show_foreign_keys' => 'true', - 'show_complete_foreign_keys' => 'false', - 'show_indexes' => 'true', - 'simple_indexes' => 'false', - 'model_dir' => 'app/models', - 'root_dir' => '', - 'include_version' => 'false', - 'require' => '', - 'exclude_tests' => 'true', - 'exclude_fixtures' => 'true', - 'exclude_factories' => 'true', - 'exclude_serializers' => 'false', - 'exclude_scaffolds' => 'true', - 'exclude_controllers' => 'true', - 'exclude_helpers' => 'true', - 'exclude_sti_subclasses' => 'false', - 'ignore_model_sub_dir' => 'false', - 'ignore_columns' => nil, - 'ignore_routes' => nil, - 'ignore_unknown_models' => 'false', - 'hide_limit_column_types' => 'integer,bigint,boolean', - 'hide_default_column_types' => 'json,jsonb,hstore', - 'skip_on_db_migrate' => 'false', - 'format_bare' => 'true', - 'format_rdoc' => 'false', - 'format_yard' => 'false', - 'format_markdown' => 'false', - 'sort' => 'false', - 'force' => 'false', - 'frozen' => 'false', - 'classified_sort' => 'true', - 'trace' => 'false', - 'wrapper_open' => nil, - 'wrapper_close' => nil, - 'with_comment' => 'true' - ) - end - - Annotate.load_tasks -end diff --git a/lib/templates/clone.rb b/lib/templates/clone.rb index 6210354d..2c15858f 100644 --- a/lib/templates/clone.rb +++ b/lib/templates/clone.rb @@ -9,7 +9,6 @@ module Templates template.external_id = external_id template.author = author - template.preferences = original_template.preferences.deep_dup template.name = name.presence || "#{original_template.name} (#{I18n.t('clone')})" if folder_name.present? @@ -18,10 +17,11 @@ module Templates template.folder_id = original_template.folder_id end - template.submitters, template.fields, template.schema = + template.submitters, template.fields, template.schema, template.preferences = update_submitters_and_fields_and_schema(original_template.submitters.deep_dup, original_template.fields.deep_dup, - original_template.schema.deep_dup) + original_template.schema.deep_dup, + original_template.preferences.deep_dup) if name.present? && template.schema.size == 1 && original_template.schema.first['name'] == original_template.name && @@ -33,7 +33,7 @@ module Templates end # rubocop:disable Metrics, Style/CombinableLoops - def update_submitters_and_fields_and_schema(cloned_submitters, cloned_fields, cloned_schema) + def update_submitters_and_fields_and_schema(cloned_submitters, cloned_fields, cloned_schema, cloned_preferences) submitter_uuids_replacements = {} field_uuids_replacements = {} @@ -58,6 +58,10 @@ module Templates end end + cloned_preferences['submitters'].to_a.each do |submitter| + submitter['uuid'] = submitter_uuids_replacements[submitter['uuid']] + end + cloned_fields.each do |field| new_field_uuid = SecureRandom.uuid @@ -88,7 +92,7 @@ module Templates end end - [cloned_submitters, cloned_fields, cloned_schema] + [cloned_submitters, cloned_fields, cloned_schema, cloned_preferences] end # rubocop:enable Metrics, Style/CombinableLoops end diff --git a/lib/templates/find_acro_fields.rb b/lib/templates/find_acro_fields.rb index 69bc462a..11c319c7 100644 --- a/lib/templates/find_acro_fields.rb +++ b/lib/templates/find_acro_fields.rb @@ -101,7 +101,7 @@ module Templates { uuid: SecureRandom.uuid, - required: false, + required: field.flags.include?(:required), preferences: {}, areas:, **field_properties