summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-10-04 06:09:08 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-04 06:09:08 +0000
commitc99b40d5a7f93e2d51c3716676ff7c345ca19f06 (patch)
treef08d9dfbd053ae6967c7fe2c1e547e61223cad95
parent7e96b8ca7aca03af3c66d00c83eff81a3273c107 (diff)
downloadgitlab-ce-c99b40d5a7f93e2d51c3716676ff7c345ca19f06.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml6
-rw-r--r--.gitlab/ci/vendored-gems.gitlab-ci.yml8
-rw-r--r--Gemfile5
-rw-r--r--Gemfile.checksum1
-rw-r--r--Gemfile.lock10
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue7
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue7
-rw-r--r--app/assets/stylesheets/pages/notes.scss3
-rw-r--r--app/services/bulk_imports/repository_bundle_export_service.rb8
-rw-r--r--app/views/discussions/_discussion.html.haml22
-rw-r--r--app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml3
-rw-r--r--config/initializers/attr_encrypted_no_db_connection.rb44
-rw-r--r--config/initializers/attr_encrypted_thread_safe.rb17
-rw-r--r--db/post_migrate/20221003192827_add_index_resolved_on_default_branch_to_vulnerabilities_read.rb18
-rw-r--r--db/schema_migrations/202210031928271
-rw-r--r--db/structure.sql2
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--locale/gitlab.pot9
-rw-r--r--package.json2
-rw-r--r--spec/features/merge_request/close_reopen_report_toggle_spec.rb10
-rw-r--r--spec/features/merge_request/merge_request_discussion_lock_spec.rb4
-rw-r--r--spec/features/merge_request/user_manages_subscription_spec.rb2
-rw-r--r--spec/features/merge_request/user_marks_merge_request_as_draft_spec.rb4
-rw-r--r--spec/frontend/badges/components/badge_form_spec.js194
-rw-r--r--spec/frontend/badges/components/badge_list_row_spec.js137
-rw-r--r--spec/frontend/badges/components/badge_list_spec.js125
-rw-r--r--spec/frontend/badges/components/badge_spec.js150
-rw-r--r--spec/initializers/attr_encrypted_no_db_connection_spec.rb40
-rw-r--r--spec/initializers/attr_encrypted_thread_safe_spec.rb28
-rw-r--r--spec/services/bulk_imports/repository_bundle_export_service_spec.rb10
-rw-r--r--vendor/gems/attr_encrypted/.gitignore5
-rw-r--r--vendor/gems/attr_encrypted/.gitlab-ci.yml26
-rw-r--r--vendor/gems/attr_encrypted/CHANGELOG.md98
-rw-r--r--vendor/gems/attr_encrypted/Gemfile3
-rw-r--r--vendor/gems/attr_encrypted/Gemfile.lock152
-rw-r--r--vendor/gems/attr_encrypted/MIT-LICENSE20
-rw-r--r--vendor/gems/attr_encrypted/README.md466
-rw-r--r--vendor/gems/attr_encrypted/Rakefile25
-rw-r--r--vendor/gems/attr_encrypted/attr_encrypted.gemspec52
-rw-r--r--vendor/gems/attr_encrypted/lib/attr_encrypted.rb473
-rw-r--r--vendor/gems/attr_encrypted/lib/attr_encrypted/adapters/active_record.rb146
-rw-r--r--vendor/gems/attr_encrypted/lib/attr_encrypted/adapters/data_mapper.rb24
-rw-r--r--vendor/gems/attr_encrypted/lib/attr_encrypted/adapters/sequel.rb16
-rw-r--r--vendor/gems/attr_encrypted/lib/attr_encrypted/version.rb19
-rw-r--r--vendor/gems/attr_encrypted/test/active_record_test.rb346
-rw-r--r--vendor/gems/attr_encrypted/test/attr_encrypted_test.rb490
-rw-r--r--vendor/gems/attr_encrypted/test/compatibility_test.rb109
-rw-r--r--vendor/gems/attr_encrypted/test/data_mapper_test.rb59
-rw-r--r--vendor/gems/attr_encrypted/test/legacy_active_record_test.rb120
-rw-r--r--vendor/gems/attr_encrypted/test/legacy_attr_encrypted_test.rb300
-rw-r--r--vendor/gems/attr_encrypted/test/legacy_compatibility_test.rb95
-rw-r--r--vendor/gems/attr_encrypted/test/legacy_data_mapper_test.rb57
-rw-r--r--vendor/gems/attr_encrypted/test/legacy_sequel_test.rb54
-rwxr-xr-xvendor/gems/attr_encrypted/test/run.sh12
-rw-r--r--vendor/gems/attr_encrypted/test/sequel_test.rb55
-rw-r--r--vendor/gems/attr_encrypted/test/test_helper.rb63
-rw-r--r--yarn.lock8
57 files changed, 3669 insertions, 503 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 6c1a135573b..155c8bbd793 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -1507,6 +1507,12 @@
changes: ["vendor/gems/mail-smtp_pool/**/*"]
- <<: *if-merge-request-labels-run-all-rspec
+.vendor:rules:attr_encrypted:
+ rules:
+ - <<: *if-merge-request
+ changes: ["vendor/gems/attr_encrypted/**/*"]
+ - <<: *if-merge-request-labels-run-all-rspec
+
.vendor:rules:microsoft_graph_mailer:
rules:
- <<: *if-merge-request
diff --git a/.gitlab/ci/vendored-gems.gitlab-ci.yml b/.gitlab/ci/vendored-gems.gitlab-ci.yml
index 577bd37ca9e..8068429c002 100644
--- a/.gitlab/ci/vendored-gems.gitlab-ci.yml
+++ b/.gitlab/ci/vendored-gems.gitlab-ci.yml
@@ -6,6 +6,14 @@ vendor mail-smtp_pool:
include: vendor/gems/mail-smtp_pool/.gitlab-ci.yml
strategy: depend
+vendor attr_encrypted:
+ extends:
+ - .vendor:rules:attr_encrypted
+ needs: []
+ trigger:
+ include: vendor/gems/attr_encrypted/.gitlab-ci.yml
+ strategy: depend
+
vendor microsoft_graph_mailer:
extends:
- .vendor:rules:microsoft_graph_mailer
diff --git a/Gemfile b/Gemfile
index f1e3960b637..25d21017bff 100644
--- a/Gemfile
+++ b/Gemfile
@@ -10,6 +10,9 @@ end
gem 'bundler-checksum', '~> 0.1.0', path: 'vendor/gems/bundler-checksum', require: false
+# NOTE: When incrementing the major or minor version here, also increment activerecord_version
+# in vendor/gems/attr_encrypted/attr_encrypted.gemspec until we resolve
+# https://gitlab.com/gitlab-org/gitlab/-/issues/375713
gem 'rails', '~> 6.1.6.1'
gem 'bootsnap', '~> 1.13.0', require: false
@@ -79,7 +82,7 @@ gem 'invisible_captcha', '~> 1.1.0'
# Two-factor authentication
gem 'devise-two-factor', '~> 4.0.2'
gem 'rqrcode-rails3', '~> 0.1.7'
-gem 'attr_encrypted', '~> 3.1.0'
+gem 'attr_encrypted', '~> 3.2.4', path: 'vendor/gems/attr_encrypted'
gem 'u2f', '~> 0.2.1'
# GitLab Pages
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 66bc388c922..c22dc2c8804 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -26,7 +26,6 @@
{"name":"asciidoctor-plantuml","version":"0.0.16","platform":"ruby","checksum":"407e47cd1186ded5ccc75f0c812e5524c26c571d542247c5132abb8f47bd1793"},
{"name":"ast","version":"2.4.2","platform":"ruby","checksum":"1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12"},
{"name":"atlassian-jwt","version":"0.2.0","platform":"ruby","checksum":"52e653e9d6062d7a740c3675b0e79fa08367927c6fc17f5476d1b6b3798c6eb2"},
-{"name":"attr_encrypted","version":"3.1.0","platform":"ruby","checksum":"4f0682604714ed4599cf00771ad27e82f0b51b0ed8644af51a43d21fbe129b59"},
{"name":"attr_required","version":"1.0.1","platform":"ruby","checksum":"024e10393bd30901e1adf6769bd756b873a5ef7da60f86f8f11066116b5742bc"},
{"name":"autoprefixer-rails","version":"10.2.5.1","platform":"ruby","checksum":"3711d67f1112361c7628847ac192d8aa6f3b8abe47527aee8a69dc8985e798ee"},
{"name":"awesome_print","version":"1.9.2","platform":"ruby","checksum":"e99b32b704acff16d768b3468680793ced40bfdc4537eb07e06a4be11133786e"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 355cffc292d..8b023661438 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,4 +1,10 @@
PATH
+ remote: vendor/gems/attr_encrypted
+ specs:
+ attr_encrypted (3.2.4)
+ encryptor (~> 3.0.0)
+
+PATH
remote: vendor/gems/bundler-checksum
specs:
bundler-checksum (0.1.0)
@@ -178,8 +184,6 @@ GEM
ast (2.4.2)
atlassian-jwt (0.2.0)
jwt (~> 2.1.0)
- attr_encrypted (3.1.0)
- encryptor (~> 3.0.0)
attr_required (1.0.1)
autoprefixer-rails (10.2.5.1)
execjs (> 0)
@@ -1534,7 +1538,7 @@ DEPENDENCIES
asciidoctor-kroki (~> 0.5.0)
asciidoctor-plantuml (~> 0.0.16)
atlassian-jwt (~> 0.2.0)
- attr_encrypted (~> 3.1.0)
+ attr_encrypted (~> 3.2.4)!
autoprefixer-rails (= 10.2.5.1)
awesome_print
aws-sdk-cloudformation (~> 1)
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 9f359a25234..eb889344c1e 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -4,7 +4,6 @@ import { mapGetters, mapActions, mapState } from 'vuex';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { visitUrl, updateHistory, getParameterByName } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { formType } from '../constants';
import createBoardMutation from '../graphql/board_create.mutation.graphql';
@@ -45,7 +44,6 @@ export default {
BoardConfigurationOptions,
GlAlert,
},
- mixins: [glFeatureFlagMixin()],
inject: {
fullPath: {
default: '',
@@ -233,9 +231,8 @@ export default {
}
},
setIteration(iteration) {
- if (this.glFeatures.iterationCadences) {
- this.board.iterationCadenceId = iteration.iterationCadenceId;
- }
+ this.board.iterationCadenceId = iteration.iterationCadenceId;
+
this.$set(this.board, 'iteration', {
id: iteration.id,
});
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index 6521b86edbb..37935e9c3c6 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -81,16 +81,18 @@ export default {
:class="{
'gl-bg-orange-50': blocksMerge && !allResolved,
'gl-bg-gray-50': !blocksMerge || allResolved,
- 'gl-pr-2': !allResolved,
}"
data-testid="discussions-counter-text"
>
<template v-if="allResolved">
{{ __('All threads resolved!') }}
<gl-dropdown
+ v-gl-tooltip:discussionCounter.hover.bottom
size="small"
category="tertiary"
right
+ :title="__('Thread options')"
+ :aria-label="__('Thread options')"
toggle-class="btn-icon"
class="gl-pt-0! gl-px-2 gl-h-full gl-ml-2"
>
@@ -133,9 +135,12 @@ export default {
@click="jumpNext"
/>
<gl-dropdown
+ v-gl-tooltip:discussionCounter.hover.bottom
size="small"
category="tertiary"
right
+ :title="__('Thread options')"
+ :aria-label="__('Thread options')"
toggle-class="btn-icon"
class="gl-pt-0! gl-px-2"
>
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 5fa047a36ec..6df739f0b40 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -1095,8 +1095,7 @@ $system-note-svg-size: 1rem;
// See https://gitlab.com/gitlab-org/gitlab-foss/issues/53918#note_117038785
.unstyled-comments {
.discussion-header {
- padding: $gl-padding;
- border-bottom: 1px solid $border-color;
+ padding: $gl-padding 0;
}
.discussion-form-container {
diff --git a/app/services/bulk_imports/repository_bundle_export_service.rb b/app/services/bulk_imports/repository_bundle_export_service.rb
index 31a2ed6d1af..86159f5189d 100644
--- a/app/services/bulk_imports/repository_bundle_export_service.rb
+++ b/app/services/bulk_imports/repository_bundle_export_service.rb
@@ -9,13 +9,19 @@ module BulkImports
end
def execute
- repository.bundle_to_disk(bundle_filepath) if repository.exists?
+ return unless repository_exists?
+
+ repository.bundle_to_disk(bundle_filepath)
end
private
attr_reader :repository, :export_path, :export_filename
+ def repository_exists?
+ repository.exists? && !repository.empty?
+ end
+
def bundle_filepath
File.join(export_path, "#{export_filename}.bundle")
end
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index 477f6c73388..224930e28df 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -3,19 +3,13 @@
.timeline-entry-inner
.timeline-content
.discussion.js-toggle-container{ data: { discussion_id: discussion.id, is_expanded: expanded.to_s } }
- .discussion-header
- .timeline-icon
+ .discussion-header.gl-display-flex.gl--flex-center
+ .timeline-icon.gl-flex-shrink-0
= link_to user_path(discussion.author) do
- = image_tag avatar_icon_for_user(discussion.author), class: "avatar s40"
- .discussion-actions
- %button.note-action-button.discussion-toggle-button.js-toggle-button{ type: "button", class: ("js-toggle-lazy-diff" unless expanded) }
- = sprite_icon('chevron-up', css_class: "js-sidebar-collapse #{'hidden' unless expanded}")
- = sprite_icon('chevron-down', css_class: "js-sidebar-expand #{'hidden' if expanded}")
- %span.js-sidebar-collapse{ class: "#{'hidden' unless expanded}" }= _('Hide thread')
- %span.js-sidebar-expand{ class: "#{'hidden' if expanded}" }= _('Show thread')
+ = render Pajamas::AvatarComponent.new(discussion.author, size: 32, class: 'gl-mr-3')
= link_to_member(@project, discussion.author, avatar: false)
- .inline.discussion-headline-light
+ .inline.discussion-headline-light.gl-mx-3
= discussion.author.to_reference
started a thread
@@ -42,10 +36,16 @@
an old version of
the diff
-
= time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago")
= render "discussions/headline", discussion: discussion
+ .discussion-actions.gl-ml-auto
+ %button.note-action-button.discussion-toggle-button.js-toggle-button{ type: "button", class: ("js-toggle-lazy-diff" unless expanded) }
+ = sprite_icon('chevron-up', css_class: "js-sidebar-collapse #{'hidden' unless expanded}")
+ = sprite_icon('chevron-down', css_class: "js-sidebar-expand #{'hidden' if expanded}")
+ %span.js-sidebar-collapse{ class: "#{'hidden' unless expanded}" }= _('Hide thread')
+ %span.js-sidebar-expand{ class: "#{'hidden' if expanded}" }= _('Show thread')
+
.discussion-body.js-toggle-content{ class: ("hide" unless expanded) }
- if discussion.diff_discussion? && discussion.diff_file
= render "discussions/diff_with_notes", discussion: discussion
diff --git a/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml b/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml
index 840635ca39a..478db70877d 100644
--- a/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml
+++ b/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml
@@ -1,8 +1,7 @@
- display_issuable_type = issuable_display_type(@merge_request)
.float-left.btn-group.gl-md-ml-3.gl-display-flex.dropdown.gl-new-dropdown.gl-md-w-auto.gl-w-full
- = button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret gl-display-none! gl-md-display-inline-flex!", data: { 'toggle' => 'dropdown' } do
- %span.gl-sr-only= _('Toggle dropdown')
+ = button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret has-tooltip gl-display-none! gl-md-display-inline-flex!", data: { toggle: 'dropdown', title: _('Merge request actions'), testid: 'merge-request-actions' } do
= sprite_icon "ellipsis_v", size: 16, css_class: "dropdown-icon gl-icon"
= button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md btn-block gl-button gl-dropdown-toggle gl-md-display-none!", data: { 'toggle' => 'dropdown' } do
%span.gl-new-dropdown-button-text= _('Merge request actions')
diff --git a/config/initializers/attr_encrypted_no_db_connection.rb b/config/initializers/attr_encrypted_no_db_connection.rb
deleted file mode 100644
index d9e943bd249..00000000000
--- a/config/initializers/attr_encrypted_no_db_connection.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-raise 'This patch is only tested with attr_encrypted v3.1.0' unless AttrEncrypted::Version.string == '3.1.0'
-
-module AttrEncrypted
- module Adapters
- module ActiveRecord
- module GitlabMonkeyPatches
- # Prevent attr_encrypted from defining virtual accessors for encryption
- # data when the code and schema are out of sync. See this issue for more
- # details: https://github.com/attr-encrypted/attr_encrypted/issues/332
- def attribute_instance_methods_as_symbols_available?
- false
- end
-
- protected
-
- # The attr_encrypted gem is not actively maintained
- # At the same time it contains the code that raises kwargs deprecation warnings:
- # https://github.com/attr-encrypted/attr_encrypted/blob/master/lib/attr_encrypted/adapters/active_record.rb#L65
- #
- def attr_encrypted(*attrs)
- super
-
- attr = attrs.first
-
- redefine_method(:"#{attr}_changed?") do |**options|
- attribute_changed?(attr, **options)
- end
- end
- end
- end
- end
-end
-
-# As of v3.1.0, the attr_encrypted gem defines the AttrEncrypted and
-# AttrEncrypted::Adapters::ActiveRecord modules, and uses "extend" to mix them
-# into the ActiveRecord::Base class. This intervention overrides utility methods
-# defined by attr_encrypted to fix two bugs, as detailed above.
-#
-# The methods are used here: https://github.com/attr-encrypted/attr_encrypted/blob/3.1.0/lib/attr_encrypted.rb#L145-158
-ActiveSupport.on_load(:active_record) do
- extend AttrEncrypted::Adapters::ActiveRecord::GitlabMonkeyPatches
-end
diff --git a/config/initializers/attr_encrypted_thread_safe.rb b/config/initializers/attr_encrypted_thread_safe.rb
deleted file mode 100644
index be0bb56ffdc..00000000000
--- a/config/initializers/attr_encrypted_thread_safe.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-# As of v3.1.0, attr_encrypted is not thread-safe because all instances share the same `encrypted_attributes`
-# This was fixed in https://github.com/attr-encrypted/attr_encrypted/commit/d4ca0e2073ca6ba5035997ce25f7fc0b4bfbe39e
-# but no release was made after that so we have to patch it ourselves here
-
-module AttrEncrypted
- module InstanceMethods
- def encrypted_attributes
- @encrypted_attributes ||= begin
- duplicated = {}
- self.class.encrypted_attributes.map { |key, value| duplicated[key] = value.dup }
- duplicated
- end
- end
- end
-end
diff --git a/db/post_migrate/20221003192827_add_index_resolved_on_default_branch_to_vulnerabilities_read.rb b/db/post_migrate/20221003192827_add_index_resolved_on_default_branch_to_vulnerabilities_read.rb
new file mode 100644
index 00000000000..e352e324187
--- /dev/null
+++ b/db/post_migrate/20221003192827_add_index_resolved_on_default_branch_to_vulnerabilities_read.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddIndexResolvedOnDefaultBranchToVulnerabilitiesRead < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_vuln_reads_on_resolved_on_default_branch'
+ COLUMNS = %i[project_id state id]
+
+ def up
+ add_concurrent_index :vulnerability_reads, COLUMNS,
+ where: 'resolved_on_default_branch IS TRUE',
+ name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :vulnerability_reads, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20221003192827 b/db/schema_migrations/20221003192827
new file mode 100644
index 00000000000..803c269b6e0
--- /dev/null
+++ b/db/schema_migrations/20221003192827
@@ -0,0 +1 @@
+7fe33b22601469d1f15ee67241775e7a14d96841a49129fe98bfd2f44cf6666f \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index a03211b90f8..d45381bdbe3 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -30651,6 +30651,8 @@ CREATE INDEX index_vuln_reads_on_namespace_id_state_severity_and_vuln_id ON vuln
CREATE INDEX index_vuln_reads_on_project_id_state_severity_and_vuln_id ON vulnerability_reads USING btree (project_id, state, severity, vulnerability_id DESC);
+CREATE INDEX index_vuln_reads_on_resolved_on_default_branch ON vulnerability_reads USING btree (project_id, state, id) WHERE (resolved_on_default_branch IS TRUE);
+
CREATE INDEX index_vulnerabilities_common_finder_query_on_default_branch ON vulnerabilities USING btree (project_id, state, report_type, present_on_default_branch, severity, id);
CREATE INDEX index_vulnerabilities_on_author_id ON vulnerabilities USING btree (author_id);
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index a4e18babd17..b9a0be19fa1 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -13808,7 +13808,7 @@ Represents an iteration object.
| <a id="iterationsequence"></a>`sequence` | [`Int!`](#int) | Sequence number for the iteration when you sort the containing cadence's iterations by the start and end date. The earliest starting and ending iteration is assigned 1. |
| <a id="iterationstartdate"></a>`startDate` | [`Time`](#time) | Timestamp of the iteration start date. |
| <a id="iterationstate"></a>`state` | [`IterationState!`](#iterationstate) | State of the iteration. |
-| <a id="iterationtitle"></a>`title` | [`String`](#string) | Title of the iteration. Title must be specified unless iteration_cadences feature flag is enabled. |
+| <a id="iterationtitle"></a>`title` | [`String`](#string) | Title of the iteration. |
| <a id="iterationupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of last iteration update. |
| <a id="iterationwebpath"></a>`webPath` | [`String!`](#string) | Web path of the iteration. |
| <a id="iterationweburl"></a>`webUrl` | [`String!`](#string) | Web URL of the iteration. |
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 46084e22952..536c5f376b1 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -22677,9 +22677,6 @@ msgstr ""
msgid "Iterations|Upcoming iterations"
msgstr ""
-msgid "Iteration|Dates cannot overlap with other existing Iterations within this group"
-msgstr ""
-
msgid "Iteration|Dates cannot overlap with other existing Iterations within this iterations cadence"
msgstr ""
@@ -41361,6 +41358,9 @@ msgstr ""
msgid "This will remove the fork relationship between this project and other projects in the fork network."
msgstr ""
+msgid "Thread options"
+msgstr ""
+
msgid "Thread to reply to cannot be found"
msgstr ""
@@ -41959,9 +41959,6 @@ msgstr ""
msgid "Toggle commit list"
msgstr ""
-msgid "Toggle dropdown"
-msgstr ""
-
msgid "Toggle emoji award"
msgstr ""
diff --git a/package.json b/package.json
index 9445e3e7ef6..d77a00f2c05 100644
--- a/package.json
+++ b/package.json
@@ -105,7 +105,7 @@
"codesandbox-api": "0.0.23",
"compression-webpack-plugin": "^5.0.2",
"copy-webpack-plugin": "^6.4.1",
- "core-js": "^3.25.4",
+ "core-js": "^3.25.5",
"cron-validator": "^1.1.1",
"cronstrue": "^1.122.0",
"cropper": "^2.3.0",
diff --git a/spec/features/merge_request/close_reopen_report_toggle_spec.rb b/spec/features/merge_request/close_reopen_report_toggle_spec.rb
index dea9a10a4ec..5e9400935c3 100644
--- a/spec/features/merge_request/close_reopen_report_toggle_spec.rb
+++ b/spec/features/merge_request/close_reopen_report_toggle_spec.rb
@@ -24,14 +24,14 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
context 'close/reopen/report toggle' do
it 'opens a dropdown when toggle is clicked' do
- click_button 'Toggle dropdown'
+ find('[data-testid="merge-request-actions"]').click
expect(container).to have_link("Close merge request")
expect(container).to have_link('Report abuse')
end
it 'links to Report Abuse' do
- click_button 'Toggle dropdown'
+ find('[data-testid="merge-request-actions"]').click
click_link 'Report abuse'
expect(page).to have_content('Report abuse to admin')
@@ -42,7 +42,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
let(:issuable) { create(:merge_request, :opened, source_project: project) }
it 'shows the `Edit` and `Mark as draft` buttons' do
- click_button 'Toggle dropdown'
+ find('[data-testid="merge-request-actions"]').click
expect(container).to have_link('Edit')
expect(container).to have_link('Mark as draft')
@@ -56,7 +56,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
let(:issuable) { create(:merge_request, :closed, source_project: project) }
it 'shows both the `Edit` and `Reopen` button' do
- click_button 'Toggle dropdown'
+ find('[data-testid="merge-request-actions"]').click
expect(container).to have_link('Edit')
expect(container).to have_link('Report abuse')
@@ -68,7 +68,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do
let(:issuable) { create(:merge_request, :closed, source_project: project, author: user) }
it 'shows both the `Edit` and `Reopen` button' do
- click_button 'Toggle dropdown'
+ find('[data-testid="merge-request-actions"]').click
expect(container).to have_link('Edit')
expect(container).to have_link('Reopen merge request')
diff --git a/spec/features/merge_request/merge_request_discussion_lock_spec.rb b/spec/features/merge_request/merge_request_discussion_lock_spec.rb
index a7bc2a062af..d69295744f7 100644
--- a/spec/features/merge_request/merge_request_discussion_lock_spec.rb
+++ b/spec/features/merge_request/merge_request_discussion_lock_spec.rb
@@ -90,7 +90,7 @@ RSpec.describe 'Merge Request Discussion Lock', :js do
end
it 'the user can lock the merge_request' do
- click_button 'Toggle dropdown'
+ find('[data-testid="merge-request-actions"]').click
expect(page).to have_content('Lock merge request')
end
@@ -103,7 +103,7 @@ RSpec.describe 'Merge Request Discussion Lock', :js do
end
it 'the user can unlock the merge_request' do
- click_button 'Toggle dropdown'
+ find('[data-testid="merge-request-actions"]').click
expect(page).to have_content('Unlock merge request')
end
diff --git a/spec/features/merge_request/user_manages_subscription_spec.rb b/spec/features/merge_request/user_manages_subscription_spec.rb
index ec22201b88f..a8d59a6ffb5 100644
--- a/spec/features/merge_request/user_manages_subscription_spec.rb
+++ b/spec/features/merge_request/user_manages_subscription_spec.rb
@@ -43,7 +43,7 @@ RSpec.describe 'User manages subscription', :js do
it 'toggles subscription' do
wait_for_requests
- click_button 'Toggle dropdown'
+ find('[data-testid="merge-request-actions"]').click
expect(page).to have_selector('.gl-toggle:not(.is-checked)')
find('[data-testid="notifications-toggle"] .gl-toggle').click
diff --git a/spec/features/merge_request/user_marks_merge_request_as_draft_spec.rb b/spec/features/merge_request/user_marks_merge_request_as_draft_spec.rb
index c3a61476442..d85f275b724 100644
--- a/spec/features/merge_request/user_marks_merge_request_as_draft_spec.rb
+++ b/spec/features/merge_request/user_marks_merge_request_as_draft_spec.rb
@@ -16,12 +16,12 @@ RSpec.describe 'Merge request > User marks merge request as draft', :js do
end
it 'toggles draft status' do
- click_button 'Toggle dropdown'
+ find('[data-testid="merge-request-actions"]').click
click_link 'Mark as draft'
expect(page).to have_content("Draft: #{merge_request.title}")
- click_button 'Toggle dropdown'
+ find('[data-testid="merge-request-actions"]').click
page.within('.detail-page-header-actions') do
click_link 'Mark as ready'
diff --git a/spec/frontend/badges/components/badge_form_spec.js b/spec/frontend/badges/components/badge_form_spec.js
index 6d8a00eb50b..0a736df7075 100644
--- a/spec/frontend/badges/components/badge_form_spec.js
+++ b/spec/frontend/badges/components/badge_form_spec.js
@@ -1,195 +1,183 @@
import MockAdapter from 'axios-mock-adapter';
-import Vue, { nextTick } from 'vue';
-import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import Vue from 'vue';
+import Vuex from 'vuex';
+import { mount } from '@vue/test-utils';
import { DUMMY_IMAGE_URL, TEST_HOST } from 'helpers/test_constants';
-import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import BadgeForm from '~/badges/components/badge_form.vue';
import createEmptyBadge from '~/badges/empty_badge';
-import store from '~/badges/store';
+
+import createState from '~/badges/store/state';
+import mutations from '~/badges/store/mutations';
+import actions from '~/badges/store/actions';
+
import axios from '~/lib/utils/axios_utils';
-// avoid preview background process
-BadgeForm.methods.debouncedPreview = () => {};
+Vue.use(Vuex);
describe('BadgeForm component', () => {
- const Component = Vue.extend(BadgeForm);
let axiosMock;
- let vm;
+ let mockedActions;
+ let wrapper;
+
+ const createComponent = (propsData, customState = {}) => {
+ mockedActions = Object.fromEntries(Object.keys(actions).map((name) => [name, jest.fn()]));
+
+ const store = new Vuex.Store({
+ state: {
+ ...createState(),
+ ...customState,
+ },
+ mutations,
+ actions: mockedActions,
+ });
- beforeEach(() => {
- setHTMLFixture(`
- <div id="dummy-element"></div>
- `);
+ wrapper = mount(BadgeForm, {
+ store,
+ propsData,
+ attachTo: document.body,
+ });
+ };
+ beforeEach(() => {
axiosMock = new MockAdapter(axios);
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
axiosMock.restore();
- resetHTMLFixture();
});
- describe('methods', () => {
- beforeEach(() => {
- vm = mountComponentWithStore(Component, {
- el: '#dummy-element',
- store,
- props: {
- isEditing: false,
- },
- });
- });
+ it('stops editing when cancel button is clicked', async () => {
+ createComponent({ isEditing: true });
- describe('onCancel', () => {
- it('calls stopEditing', () => {
- jest.spyOn(vm, 'stopEditing').mockImplementation(() => {});
+ const cancelButton = wrapper.find('.row-content-block button');
- vm.onCancel();
+ await cancelButton.trigger('click');
- expect(vm.stopEditing).toHaveBeenCalled();
- });
- });
+ expect(mockedActions.stopEditing).toHaveBeenCalled();
});
- const sharedSubmitTests = (submitAction) => {
+ const sharedSubmitTests = (submitAction, props) => {
const nameSelector = '#badge-name';
const imageUrlSelector = '#badge-image-url';
- const findImageUrlElement = () => vm.$el.querySelector(imageUrlSelector);
+ const findImageUrl = () => wrapper.find(imageUrlSelector);
const linkUrlSelector = '#badge-link-url';
- const findLinkUrlElement = () => vm.$el.querySelector(linkUrlSelector);
+ const findLinkUrl = () => wrapper.find(linkUrlSelector);
const setValue = (inputElementSelector, value) => {
- const inputElement = vm.$el.querySelector(inputElementSelector);
- inputElement.value = value;
- inputElement.dispatchEvent(new Event('input'));
+ const input = wrapper.find(inputElementSelector);
+ return input.setValue(value);
};
const submitForm = () => {
- const submitButton = vm.$el.querySelector('button[type="submit"]');
- submitButton.click();
+ const submitButton = wrapper.find('button[type="submit"]');
+ return submitButton.trigger('click');
};
const expectInvalidInput = (inputElementSelector) => {
- const inputElement = vm.$el.querySelector(inputElementSelector);
+ const input = wrapper.find(inputElementSelector);
- expect(inputElement.checkValidity()).toBe(false);
- const feedbackElement = vm.$el.querySelector(`${inputElementSelector} + .invalid-feedback`);
+ expect(input.element.checkValidity()).toBe(false);
+ const feedbackElement = wrapper.find(`${inputElementSelector} + .invalid-feedback`);
- expect(feedbackElement).toBeVisible();
+ expect(feedbackElement.isVisible()).toBe(true);
};
- beforeEach(async () => {
- jest.spyOn(vm, submitAction).mockReturnValue(Promise.resolve());
- store.replaceState({
- ...store.state,
+ beforeEach(() => {
+ createComponent(props, {
badgeInAddForm: createEmptyBadge(),
badgeInEditForm: createEmptyBadge(),
isSaving: false,
});
- await nextTick();
setValue(nameSelector, 'TestBadge');
setValue(linkUrlSelector, `${TEST_HOST}/link/url`);
setValue(imageUrlSelector, `${window.location.origin}${DUMMY_IMAGE_URL}`);
});
- it('returns immediately if imageUrl is empty', () => {
- setValue(imageUrlSelector, '');
+ it('returns immediately if imageUrl is empty', async () => {
+ await setValue(imageUrlSelector, '');
- submitForm();
+ await submitForm();
expectInvalidInput(imageUrlSelector);
- expect(vm[submitAction]).not.toHaveBeenCalled();
+ expect(mockedActions[submitAction]).not.toHaveBeenCalled();
});
- it('returns immediately if imageUrl is malformed', () => {
- setValue(imageUrlSelector, 'not-a-url');
+ it('returns immediately if imageUrl is malformed', async () => {
+ await setValue(imageUrlSelector, 'not-a-url');
- submitForm();
+ await submitForm();
expectInvalidInput(imageUrlSelector);
- expect(vm[submitAction]).not.toHaveBeenCalled();
+ expect(mockedActions[submitAction]).not.toHaveBeenCalled();
});
- it('returns immediately if linkUrl is empty', () => {
- setValue(linkUrlSelector, '');
+ it('returns immediately if linkUrl is empty', async () => {
+ await setValue(linkUrlSelector, '');
- submitForm();
+ await submitForm();
expectInvalidInput(linkUrlSelector);
- expect(vm[submitAction]).not.toHaveBeenCalled();
+ expect(mockedActions[submitAction]).not.toHaveBeenCalled();
});
- it('returns immediately if linkUrl is malformed', () => {
- setValue(linkUrlSelector, 'not-a-url');
+ it('returns immediately if linkUrl is malformed', async () => {
+ await setValue(linkUrlSelector, 'not-a-url');
- submitForm();
+ await submitForm();
expectInvalidInput(linkUrlSelector);
- expect(vm[submitAction]).not.toHaveBeenCalled();
+ expect(mockedActions[submitAction]).not.toHaveBeenCalled();
});
- it(`calls ${submitAction}`, () => {
- submitForm();
+ it(`calls ${submitAction}`, async () => {
+ await submitForm();
- expect(findImageUrlElement().checkValidity()).toBe(true);
- expect(findLinkUrlElement().checkValidity()).toBe(true);
- expect(vm[submitAction]).toHaveBeenCalled();
+ expect(findImageUrl().element.checkValidity()).toBe(true);
+ expect(findLinkUrl().element.checkValidity()).toBe(true);
+ expect(mockedActions[submitAction]).toHaveBeenCalled();
});
};
describe('if isEditing is false', () => {
- beforeEach(() => {
- vm = mountComponentWithStore(Component, {
- el: '#dummy-element',
- store,
- props: {
- isEditing: false,
- },
- });
- });
+ const props = { isEditing: false };
it('renders one button', () => {
- expect(vm.$el.querySelector('.row-content-block')).toBeNull();
- const buttons = vm.$el.querySelectorAll('.form-group:last-of-type button');
+ createComponent(props);
+
+ expect(wrapper.find('.row-content-block').exists()).toBe(false);
+ const buttons = wrapper.findAll('.form-group:last-of-type button');
- expect(buttons.length).toBe(1);
- const buttonAddElement = buttons[0];
+ expect(buttons).toHaveLength(1);
+ const buttonAddWrapper = buttons.at(0);
- expect(buttonAddElement).toBeVisible();
- expect(buttonAddElement).toHaveText('Add badge');
+ expect(buttonAddWrapper.isVisible()).toBe(true);
+ expect(buttonAddWrapper.text()).toBe('Add badge');
});
- sharedSubmitTests('addBadge');
+ sharedSubmitTests('addBadge', props);
});
describe('if isEditing is true', () => {
- beforeEach(() => {
- vm = mountComponentWithStore(Component, {
- el: '#dummy-element',
- store,
- props: {
- isEditing: true,
- },
- });
- });
+ const props = { isEditing: true };
it('renders two buttons', () => {
- const buttons = vm.$el.querySelectorAll('.row-content-block button');
+ createComponent(props);
+ const buttons = wrapper.findAll('.row-content-block button');
- expect(buttons.length).toBe(2);
- const buttonSaveElement = buttons[1];
+ expect(buttons).toHaveLength(2);
- expect(buttonSaveElement).toBeVisible();
- expect(buttonSaveElement).toHaveText('Save changes');
- const buttonCancelElement = buttons[0];
+ const saveButton = buttons.at(1);
+ expect(saveButton.isVisible()).toBe(true);
+ expect(saveButton.text()).toBe('Save changes');
- expect(buttonCancelElement).toBeVisible();
- expect(buttonCancelElement).toHaveText('Cancel');
+ const cancelButton = buttons.at(0);
+ expect(cancelButton.isVisible()).toBe(true);
+ expect(cancelButton.text()).toBe('Cancel');
});
- sharedSubmitTests('saveBadge');
+ sharedSubmitTests('saveBadge', props);
});
});
diff --git a/spec/frontend/badges/components/badge_list_row_spec.js b/spec/frontend/badges/components/badge_list_row_spec.js
index ad8426f3168..ee7ccac974a 100644
--- a/spec/frontend/badges/components/badge_list_row_spec.js
+++ b/spec/frontend/badges/components/badge_list_row_spec.js
@@ -1,103 +1,118 @@
-import Vue, { nextTick } from 'vue';
+import Vue from 'vue';
+import Vuex from 'vuex';
+import { mount } from '@vue/test-utils';
+
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import BadgeListRow from '~/badges/components/badge_list_row.vue';
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
-import store from '~/badges/store';
+
+import createState from '~/badges/store/state';
+import mutations from '~/badges/store/mutations';
+import actions from '~/badges/store/actions';
+
import { createDummyBadge } from '../dummy_badge';
+Vue.use(Vuex);
+
describe('BadgeListRow component', () => {
- const Component = Vue.extend(BadgeListRow);
let badge;
- let vm;
-
- beforeEach(() => {
- setHTMLFixture(`
- <div id="delete-badge-modal" class="modal"></div>
- <div id="dummy-element"></div>
- `);
- store.replaceState({
- ...store.state,
- kind: PROJECT_BADGE,
+ let wrapper;
+ let mockedActions;
+
+ const createComponent = (kind) => {
+ setHTMLFixture(`<div id="delete-badge-modal" class="modal"></div>`);
+
+ mockedActions = Object.fromEntries(Object.keys(actions).map((name) => [name, jest.fn()]));
+
+ const store = new Vuex.Store({
+ state: {
+ ...createState(),
+ kind: PROJECT_BADGE,
+ },
+ mutations,
+ actions: mockedActions,
});
+
badge = createDummyBadge();
- vm = mountComponentWithStore(Component, {
- el: '#dummy-element',
+ badge.kind = kind;
+ wrapper = mount(BadgeListRow, {
+ attachTo: document.body,
store,
- props: { badge },
+ propsData: { badge },
});
- });
+ };
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
resetHTMLFixture();
});
- it('renders the badge', () => {
- const badgeElement = vm.$el.querySelector('.project-badge');
+ describe('for a project badge', () => {
+ beforeEach(() => {
+ createComponent(PROJECT_BADGE);
+ });
- expect(badgeElement).not.toBeNull();
- expect(badgeElement.getAttribute('src')).toBe(badge.renderedImageUrl);
- });
+ it('renders the badge', () => {
+ const badgeImage = wrapper.find('.project-badge');
- it('renders the badge name', () => {
- expect(vm.$el.innerText).toMatch(badge.name);
- });
+ expect(badgeImage.exists()).toBe(true);
+ expect(badgeImage.attributes('src')).toBe(badge.renderedImageUrl);
+ });
- it('renders the badge link', () => {
- expect(vm.$el.innerText).toMatch(badge.linkUrl);
- });
+ it('renders the badge name', () => {
+ expect(wrapper.text()).toMatch(badge.name);
+ });
- it('renders the badge kind', () => {
- expect(vm.$el.innerText).toMatch('Project Badge');
- });
+ it('renders the badge link', () => {
+ expect(wrapper.text()).toMatch(badge.linkUrl);
+ });
- it('shows edit and delete buttons', () => {
- const buttons = vm.$el.querySelectorAll('.table-button-footer button');
+ it('renders the badge kind', () => {
+ expect(wrapper.text()).toMatch('Project Badge');
+ });
- expect(buttons).toHaveLength(2);
- const buttonEditElement = buttons[0];
+ it('shows edit and delete buttons', () => {
+ const buttons = wrapper.findAll('.table-button-footer button');
- expect(buttonEditElement).toBeVisible();
- expect(buttonEditElement).toHaveSpriteIcon('pencil');
- const buttonDeleteElement = buttons[1];
+ expect(buttons).toHaveLength(2);
+ const editButton = buttons.at(0);
- expect(buttonDeleteElement).toBeVisible();
- expect(buttonDeleteElement).toHaveSpriteIcon('remove');
- });
+ expect(editButton.isVisible()).toBe(true);
+ expect(editButton.element).toHaveSpriteIcon('pencil');
- it('calls editBadge when clicking then edit button', () => {
- jest.spyOn(vm, 'editBadge').mockImplementation(() => {});
+ const deleteButton = buttons.at(1);
+ expect(deleteButton.isVisible()).toBe(true);
+ expect(deleteButton.element).toHaveSpriteIcon('remove');
+ });
- const editButton = vm.$el.querySelector('.table-button-footer button:first-of-type');
- editButton.click();
+ it('calls editBadge when clicking then edit button', async () => {
+ const editButton = wrapper.find('.table-button-footer button:first-of-type');
- expect(vm.editBadge).toHaveBeenCalled();
- });
+ await editButton.trigger('click');
+
+ expect(mockedActions.editBadge).toHaveBeenCalled();
+ });
- it('calls updateBadgeInModal and shows modal when clicking then delete button', async () => {
- jest.spyOn(vm, 'updateBadgeInModal').mockImplementation(() => {});
+ it('calls updateBadgeInModal and shows modal when clicking then delete button', async () => {
+ const deleteButton = wrapper.find('.table-button-footer button:last-of-type');
- const deleteButton = vm.$el.querySelector('.table-button-footer button:last-of-type');
- deleteButton.click();
+ await deleteButton.trigger('click');
- await nextTick();
- expect(vm.updateBadgeInModal).toHaveBeenCalled();
+ expect(mockedActions.updateBadgeInModal).toHaveBeenCalled();
+ });
});
describe('for a group badge', () => {
- beforeEach(async () => {
- badge.kind = GROUP_BADGE;
-
- await nextTick();
+ beforeEach(() => {
+ createComponent(GROUP_BADGE);
});
it('renders the badge kind', () => {
- expect(vm.$el.innerText).toMatch('Group Badge');
+ expect(wrapper.text()).toMatch('Group Badge');
});
it('hides edit and delete buttons', () => {
- const buttons = vm.$el.querySelectorAll('.table-button-footer button');
+ const buttons = wrapper.findAll('.table-button-footer button');
expect(buttons).toHaveLength(0);
});
diff --git a/spec/frontend/badges/components/badge_list_spec.js b/spec/frontend/badges/components/badge_list_spec.js
index 32cd9483ef8..606b1bc9cce 100644
--- a/spec/frontend/badges/components/badge_list_spec.js
+++ b/spec/frontend/badges/components/badge_list_spec.js
@@ -1,83 +1,96 @@
-import Vue, { nextTick } from 'vue';
-import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
+import Vue from 'vue';
+import Vuex from 'vuex';
+import { mount } from '@vue/test-utils';
+
import BadgeList from '~/badges/components/badge_list.vue';
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
-import store from '~/badges/store';
+
+import createState from '~/badges/store/state';
+import mutations from '~/badges/store/mutations';
+import actions from '~/badges/store/actions';
+
import { createDummyBadge } from '../dummy_badge';
-describe('BadgeList component', () => {
- const Component = Vue.extend(BadgeList);
- const numberOfDummyBadges = 3;
- let vm;
-
- beforeEach(() => {
- setHTMLFixture('<div id="dummy-element"></div>');
- const badges = [];
- for (let id = 0; id < numberOfDummyBadges; id += 1) {
- badges.push({ id, ...createDummyBadge() });
- }
- store.replaceState({
- ...store.state,
- badges,
- kind: PROJECT_BADGE,
- isLoading: false,
- });
+Vue.use(Vuex);
- // Can be removed once GlLoadingIcon no longer throws a warning
- jest.spyOn(global.console, 'warn').mockImplementation(() => jest.fn());
+const numberOfDummyBadges = 3;
+const badges = Array.from({ length: numberOfDummyBadges }).map((_, idx) => ({
+ ...createDummyBadge(),
+ id: idx,
+}));
- vm = mountComponentWithStore(Component, {
- el: '#dummy-element',
- store,
+describe('BadgeList component', () => {
+ let wrapper;
+
+ const createComponent = (customState) => {
+ const mockedActions = Object.fromEntries(Object.keys(actions).map((name) => [name, jest.fn()]));
+
+ const store = new Vuex.Store({
+ state: {
+ ...createState(),
+ isLoading: false,
+ ...customState,
+ },
+ mutations,
+ actions: mockedActions,
});
- });
+
+ wrapper = mount(BadgeList, { store });
+ };
afterEach(() => {
- vm.$destroy();
- resetHTMLFixture();
+ wrapper.destroy();
});
- it('renders a header with the badge count', () => {
- const header = vm.$el.querySelector('.card-header');
+ describe('for project badges', () => {
+ it('renders a header with the badge count', () => {
+ createComponent({
+ kind: PROJECT_BADGE,
+ badges,
+ });
- expect(header).toHaveText(new RegExp(`Your badges\\s+${numberOfDummyBadges}`));
- });
+ const header = wrapper.find('.card-header');
- it('renders a row for each badge', () => {
- const rows = vm.$el.querySelectorAll('.gl-responsive-table-row');
+ expect(header.text()).toMatchInterpolatedText('Your badges 3');
+ });
- expect(rows).toHaveLength(numberOfDummyBadges);
- });
+ it('renders a row for each badge', () => {
+ createComponent({
+ kind: PROJECT_BADGE,
+ badges,
+ });
- it('renders a message if no badges exist', async () => {
- store.state.badges = [];
+ const rows = wrapper.findAll('.gl-responsive-table-row');
- await nextTick();
- expect(vm.$el.innerText).toMatch('This project has no badges');
- });
+ expect(rows).toHaveLength(numberOfDummyBadges);
+ });
- it('shows a loading icon when loading', async () => {
- store.state.isLoading = true;
+ it('renders a message if no badges exist', () => {
+ createComponent({
+ kind: PROJECT_BADGE,
+ badges: [],
+ });
- await nextTick();
- const loadingIcon = vm.$el.querySelector('.gl-spinner');
+ expect(wrapper.text()).toMatch('This project has no badges');
+ });
- expect(loadingIcon).toBeVisible();
- });
+ it('shows a loading icon when loading', () => {
+ createComponent({ isLoading: true });
- describe('for group badges', () => {
- beforeEach(async () => {
- store.state.kind = GROUP_BADGE;
+ const loadingIcon = wrapper.find('.gl-spinner');
- await nextTick();
+ expect(loadingIcon.isVisible()).toBe(true);
});
+ });
- it('renders a message if no badges exist', async () => {
- store.state.badges = [];
+ describe('for group badges', () => {
+ it('renders a message if no badges exist', () => {
+ createComponent({
+ kind: GROUP_BADGE,
+ badges: [],
+ });
- await nextTick();
- expect(vm.$el.innerText).toMatch('This group has no badges');
+ expect(wrapper.text()).toMatch('This group has no badges');
});
});
});
diff --git a/spec/frontend/badges/components/badge_spec.js b/spec/frontend/badges/components/badge_spec.js
index 19b3a9f23a6..b468e38f19e 100644
--- a/spec/frontend/badges/components/badge_spec.js
+++ b/spec/frontend/badges/components/badge_spec.js
@@ -1,138 +1,78 @@
-import Vue, { nextTick } from 'vue';
-import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { nextTick } from 'vue';
+import { mount } from '@vue/test-utils';
+
import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants';
import Badge from '~/badges/components/badge.vue';
describe('Badge component', () => {
- const Component = Vue.extend(Badge);
const dummyProps = {
imageUrl: DUMMY_IMAGE_URL,
linkUrl: `${TEST_HOST}/badge/link/url`,
};
- let vm;
+ let wrapper;
const findElements = () => {
- const buttons = vm.$el.querySelectorAll('button');
+ const buttons = wrapper.findAll('button');
return {
- badgeImage: vm.$el.querySelector('img.project-badge'),
- loadingIcon: vm.$el.querySelector('.gl-spinner'),
- reloadButton: buttons[buttons.length - 1],
+ badgeImage: wrapper.find('img.project-badge'),
+ loadingIcon: wrapper.find('.gl-spinner'),
+ reloadButton: buttons.at(buttons.length - 1),
};
};
- const createComponent = (props, el = null) => {
- vm = mountComponent(Component, props, el);
- const { badgeImage } = findElements();
- return new Promise((resolve) => {
- badgeImage.addEventListener('load', resolve);
- // Manually dispatch load event as it is not triggered
- badgeImage.dispatchEvent(new Event('load'));
- }).then(() => nextTick());
+ const createComponent = (propsData) => {
+ wrapper = mount(Badge, { propsData });
};
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
- describe('watchers', () => {
- describe('imageUrl', () => {
- it('sets isLoading and resets numRetries and hasError', async () => {
- const props = { ...dummyProps };
- await createComponent(props);
- expect(vm.isLoading).toBe(false);
- vm.hasError = true;
- vm.numRetries = 42;
-
- vm.imageUrl = `${props.imageUrl}#something/else`;
- await nextTick();
- expect(vm.isLoading).toBe(true);
- expect(vm.numRetries).toBe(0);
- expect(vm.hasError).toBe(false);
- });
- });
+ beforeEach(() => {
+ return createComponent({ ...dummyProps }, '#dummy-element');
});
- describe('methods', () => {
- beforeEach(async () => {
- await createComponent({ ...dummyProps });
- });
+ it('shows a badge image after loading', async () => {
+ const { badgeImage, loadingIcon, reloadButton } = findElements();
+ badgeImage.element.dispatchEvent(new Event('load'));
- it('onError resets isLoading and sets hasError', () => {
- vm.hasError = false;
- vm.isLoading = true;
+ await nextTick();
- vm.onError();
+ expect(badgeImage.isVisible()).toBe(true);
+ expect(loadingIcon.isVisible()).toBe(false);
+ expect(reloadButton.isVisible()).toBe(false);
+ expect(wrapper.find('.btn-group').isVisible()).toBe(false);
+ });
- expect(vm.hasError).toBe(true);
- expect(vm.isLoading).toBe(false);
- });
+ it('shows a loading icon when loading', () => {
+ const { badgeImage, loadingIcon, reloadButton } = findElements();
- it('onLoad sets isLoading', () => {
- vm.isLoading = true;
+ expect(badgeImage.isVisible()).toBe(false);
+ expect(loadingIcon.isVisible()).toBe(true);
+ expect(reloadButton.isVisible()).toBe(false);
+ expect(wrapper.find('.btn-group').isVisible()).toBe(false);
+ });
- vm.onLoad();
+ it('shows an error and reload button if loading failed', async () => {
+ const { badgeImage, loadingIcon, reloadButton } = findElements();
+ badgeImage.element.dispatchEvent(new Event('error'));
- expect(vm.isLoading).toBe(false);
- });
+ await nextTick();
- it('reloadImage resets isLoading and hasError and increases numRetries', () => {
- vm.hasError = true;
- vm.isLoading = false;
- vm.numRetries = 0;
+ expect(badgeImage.isVisible()).toBe(false);
+ expect(loadingIcon.isVisible()).toBe(false);
+ expect(reloadButton.isVisible()).toBe(true);
+ expect(reloadButton.element).toHaveSpriteIcon('retry');
+ expect(wrapper.text()).toBe('No badge image');
+ });
- vm.reloadImage();
+ it('retries an image when loading failed and reload button is clicked', async () => {
+ const { badgeImage, reloadButton } = findElements();
+ badgeImage.element.dispatchEvent(new Event('error'));
+ await nextTick();
- expect(vm.hasError).toBe(false);
- expect(vm.isLoading).toBe(true);
- expect(vm.numRetries).toBe(1);
- });
- });
+ await reloadButton.trigger('click');
- describe('behavior', () => {
- beforeEach(() => {
- setHTMLFixture('<div id="dummy-element"></div>');
- return createComponent({ ...dummyProps }, '#dummy-element');
- });
-
- afterEach(() => {
- resetHTMLFixture();
- });
-
- it('shows a badge image after loading', () => {
- expect(vm.isLoading).toBe(false);
- expect(vm.hasError).toBe(false);
- const { badgeImage, loadingIcon, reloadButton } = findElements();
-
- expect(badgeImage).toBeVisible();
- expect(loadingIcon).toBeHidden();
- expect(reloadButton).toBeHidden();
- expect(vm.$el.querySelector('.btn-group')).toBeHidden();
- });
-
- it('shows a loading icon when loading', async () => {
- vm.isLoading = true;
-
- await nextTick();
- const { badgeImage, loadingIcon, reloadButton } = findElements();
-
- expect(badgeImage).toBeHidden();
- expect(loadingIcon).toBeVisible();
- expect(reloadButton).toBeHidden();
- expect(vm.$el.querySelector('.btn-group')).toBeHidden();
- });
-
- it('shows an error and reload button if loading failed', async () => {
- vm.hasError = true;
-
- await nextTick();
- const { badgeImage, loadingIcon, reloadButton } = findElements();
-
- expect(badgeImage).toBeHidden();
- expect(loadingIcon).toBeHidden();
- expect(reloadButton).toBeVisible();
- expect(reloadButton).toHaveSpriteIcon('retry');
- expect(vm.$el.innerText.trim()).toBe('No badge image');
- });
+ expect(badgeImage.attributes('src')).toBe(`${dummyProps.imageUrl}#retries=1`);
});
});
diff --git a/spec/initializers/attr_encrypted_no_db_connection_spec.rb b/spec/initializers/attr_encrypted_no_db_connection_spec.rb
deleted file mode 100644
index 34d9e182370..00000000000
--- a/spec/initializers/attr_encrypted_no_db_connection_spec.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'GitLab monkey-patches to AttrEncrypted' do
- describe '#attribute_instance_methods_as_symbols_available?' do
- let(:klass) do
- Class.new(ActiveRecord::Base) do
- # We need some sort of table to work on
- self.table_name = 'projects'
-
- attr_encrypted :foo
- end
- end
-
- it 'returns false' do
- expect(ActiveRecord::Base.__send__(:attribute_instance_methods_as_symbols_available?)).to be_falsy
- end
-
- it 'does not define virtual attributes' do
- instance = klass.new
-
- aggregate_failures do
- %w[
- encrypted_foo encrypted_foo=
- encrypted_foo_iv encrypted_foo_iv=
- encrypted_foo_salt encrypted_foo_salt=
- ].each do |method_name|
- expect(instance).not_to respond_to(method_name)
- end
- end
- end
-
- it 'calls attr_changed? method with kwargs' do
- obj = klass.new
-
- expect(obj.foo_changed?).to eq(false)
- end
- end
-end
diff --git a/spec/initializers/attr_encrypted_thread_safe_spec.rb b/spec/initializers/attr_encrypted_thread_safe_spec.rb
deleted file mode 100644
index e79b7c716ec..00000000000
--- a/spec/initializers/attr_encrypted_thread_safe_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe AttrEncrypted do
- describe '#encrypted_attributes' do
- subject do
- Class.new(ActiveRecord::Base) do
- self.table_name = 'projects'
-
- attr_accessor :encrypted_foo
- attr_accessor :encrypted_foo_iv
-
- attr_encrypted :foo, key: 'This is a key that is 256 bits!!'
- end
- end
-
- it 'does not share state with other instances' do
- instance = subject.new
- instance.foo = 'bar'
-
- another_instance = subject.new
-
- expect(instance.encrypted_attributes[:foo][:operation]).to eq(:encrypting)
- expect(another_instance.encrypted_attributes[:foo][:operation]).to be_nil
- end
- end
-end
diff --git a/spec/services/bulk_imports/repository_bundle_export_service_spec.rb b/spec/services/bulk_imports/repository_bundle_export_service_spec.rb
index a7d98a7474a..f0d63de1ab9 100644
--- a/spec/services/bulk_imports/repository_bundle_export_service_spec.rb
+++ b/spec/services/bulk_imports/repository_bundle_export_service_spec.rb
@@ -17,6 +17,7 @@ RSpec.describe BulkImports::RepositoryBundleExportService do
context 'when repository exists' do
it 'bundles repository to disk' do
allow(repository).to receive(:exists?).and_return(true)
+ allow(repository).to receive(:empty?).and_return(false)
expect(repository).to receive(:bundle_to_disk).with(File.join(export_path, "#{export_filename}.bundle"))
service.execute
@@ -31,6 +32,15 @@ RSpec.describe BulkImports::RepositoryBundleExportService do
service.execute
end
end
+
+ context 'when repository is empty' do
+ it 'does not bundle repository to disk' do
+ allow(repository).to receive(:empty?).and_return(true)
+ expect(repository).not_to receive(:bundle_to_disk)
+
+ service.execute
+ end
+ end
end
include_examples 'repository export' do
diff --git a/vendor/gems/attr_encrypted/.gitignore b/vendor/gems/attr_encrypted/.gitignore
new file mode 100644
index 00000000000..b25958999b0
--- /dev/null
+++ b/vendor/gems/attr_encrypted/.gitignore
@@ -0,0 +1,5 @@
+.bundle
+.DS_Store
+.ruby-version
+pkg
+coverage
diff --git a/vendor/gems/attr_encrypted/.gitlab-ci.yml b/vendor/gems/attr_encrypted/.gitlab-ci.yml
new file mode 100644
index 00000000000..42b2b9e1e01
--- /dev/null
+++ b/vendor/gems/attr_encrypted/.gitlab-ci.yml
@@ -0,0 +1,26 @@
+workflow:
+ rules:
+ - if: $CI_MERGE_REQUEST_ID
+
+default:
+ image: "ruby:${RUBY_VERSION}"
+
+rspec:
+ cache:
+ key: attr_encrypted-ruby
+ paths:
+ - vendor/gems/attr_encrypted/vendor/ruby
+ before_script:
+ - cd vendor/gems/attr_encrypted
+ - ruby -v # Print out ruby version for debugging
+ - gem install bundler --no-document # Bundler is not installed with the image
+ - bundle config set --local path 'vendor' # Install dependencies into ./vendor/ruby
+ - bundle config set with 'development' # This is set to 'deployment' otherwise
+ - bundle config set --local frozen 'true' # Disallow Gemfile.lock changes on CI
+ - bundle config # Show bundler configuration
+ - bundle install -j $(nproc)
+ script:
+ - bundle exec rake test
+ parallel:
+ matrix:
+ - RUBY_VERSION: ["2.7", "3.0"]
diff --git a/vendor/gems/attr_encrypted/CHANGELOG.md b/vendor/gems/attr_encrypted/CHANGELOG.md
new file mode 100644
index 00000000000..52ef721b311
--- /dev/null
+++ b/vendor/gems/attr_encrypted/CHANGELOG.md
@@ -0,0 +1,98 @@
+# attr_encrypted #
+
+## 3.1.0 ##
+* Added: Abitilty to encrypt empty values. (@tamird)
+* Added: MIT license
+* Added: MRI 2.5.x support (@saghaulor)
+* Fixed: No long generate IV and salt if value is empty, unless :allow_empty_value options is passed. (@saghaulor)
+* Fixed: Only generate IV and salt when :if and :unless options evaluate such that encryption should be performed. (@saghaulor)
+* Fixed: Private methods are correctly evaluated as options. (@saghaulor)
+* Fixed: Mark virtual attributes for Rails 5.x compatibility (@grosser)
+* Fixed: Only check empty on strings, allows for encrypting non-string type objects
+* Fixed: Fixed how accessors for db columns are defined in the ActiveRecord adapter, preventing premature definition. (@nagachika)
+
+## 3.0.3 ##
+* Fixed: attr_was would decrypt the attribute upon every call. This is inefficient and introduces problems when the options change between decrypting an old value and encrypting a new value; for example, when rotating the encryption key. As such, the new approach caches the decrypted value of the old encrypted value such that the old options are no longer needed. (@johnny-lai) (@saghaulor)
+
+## 3.0.2 ##
+* Changed: Removed alias_method_chain for compatibility with Rails v5.x (@grosser)
+* Changed: Updated Travis build matrix to include Rails 5. (@saghaulor) (@connorshea)
+* Changed: Removed `.silence_stream` from tests as it has been removed from Rails 5. (@sblackstone)
+
+## 3.0.1 ##
+* Fixed: attr_was method no longer calls undefined methods. (@saghaulor)
+
+## 3.0.0 ##
+* Changed: Updated gemspec to use Encryptor v3.0.0. (@saghaulor)
+* Changed: Updated README with instructions related to moving from v2.0.0 to v3.0.0. (@saghaulor)
+* Fixed: ActiveModel::Dirty methods in the ActiveRecord adapter. (@saghaulor)
+
+## 2.0.0 ##
+* Added: Now using Encryptor v2.0.0 (@saghaulor)
+* Added: Options are copied to the instance. (@saghaulor)
+* Added: Operation option is set during encryption/decryption to allow options to be evaluated in the context of the current operation. (@saghaulor)
+* Added: IV and salt can be conditionally encoded. (@saghaulor)
+* Added: Changelog! (@saghaulor)
+* Changed: attr_encrypted no longer extends object, to use with PORO extend your class, all supported ORMs are already extended. (@saghaulor)
+* Changed: Salt is now generated with more entropy. (@saghaulor)
+* Changed: The default algorithm is now `aes-256-gcm`. (@saghaulor)
+* Changed: The default mode is now `:per_attribute_iv`' (@saghaulor)
+* Changed: Extracted class level default options hash to a private method. (@saghaulor)
+* Changed: Dynamic finders only work with `:single_iv_and_salt` mode. (@saghaulor)
+* Changed: Updated documentation to include v2.0.0 changes and 'things to consider' section. (@saghaulor)
+* Fixed: All options are evaluated correctly. (@saghaulor)
+* Fixed: IV is generated for every encryption operation. (@saghaulor)
+* Deprecated: `:single_iv_and_salt` and `:per_attribute_iv_and_salt` modes are deprecated and will be removed in the next major release. (@saghaulor)
+* Deprecated: Dynamic finders via `method_missing` is deprecated and will be removed in the next major release. (@saghaulor)
+* Removed: Support for Ruby < 2.x (@saghaulor)
+* Removed: Support for Rails < 3.x (@saghaulor)
+* Removed: Unnecessary use of `alias_method` from ActiveRecord adapter. (@saghaulor)
+
+## 1.4.0 ##
+* Added: ActiveModel::Dirty#attribute_was (@saghaulor)
+* Added: ActiveModel::Dirty#attribute_changed? (@mwean)
+
+## 1.3.5 ##
+* Changed: Fixed gemspec to explicitly depend on Encryptor v1.3.0 (@saghaulor)
+* Fixed: Evaluate `:mode` option as a symbol or proc. (@cheynewallace)
+
+## 1.3.4 ##
+* Added: ActiveRecord::Base.reload support. (@rcook)
+* Fixed: ActiveRecord adapter no longer forces attribute hashes to be string-keyed. (@tamird)
+* Fixed: Mass assignment protection in ActiveRecord 4. (@tamird)
+* Changed: Now using rubygems over https. (@tamird)
+* Changed: Let ActiveRecord define attribute methods. (@saghaulor)
+
+## 1.3.3 ##
+* Added: Alias attr_encryptor and attr_encrpted. (@Billy Monk)
+
+## 1.3.2 ##
+* Fixed: Bug regarding strong parameters. (@S. Brent Faulkner)
+* Fixed: Bug regarding loading per instance IV and salt. (@S. Brent Faulkner)
+* Fixed: Bug regarding assigning nil. (@S. Brent Faulkner)
+* Added: Support for protected attributes. (@S. Brent Faulkner)
+* Added: Support for ActiveRecord 4. (@S. Brent Faulkner)
+
+## 1.3.1 ##
+* Added: Support for Rails 2.3.x and 3.1.x. (@S. Brent Faulkner)
+
+## 1.3.0 ##
+* Fixed: Serialization bug. (@Billy Monk)
+* Added: Support for :per_attribute_iv_and_salt mode. (@rcook)
+* Fixed: Added dependencies to gemspec. (@jmazzi)
+
+## 1.2.1 ##
+* Added: Force encoding when not marshaling. (@mosaicxm)
+* Fixed: Issue specifying multiple attributes on the same line. (@austintaylor)
+* Added: Typecasting to String before encryption (@shuber)
+* Added: `"#{attribute}?"` method. (@shuber)
+
+## 1.2.0 ##
+* Changed: General code refactoring (@shuber)
+
+## 1.1.2 ##
+* No significant changes
+
+## 1.1.1 ##
+* Changled: Updated README. (@shuber)
+* Added: `before_type_cast` alias to ActiveRecord adapter. (@shuber)
diff --git a/vendor/gems/attr_encrypted/Gemfile b/vendor/gems/attr_encrypted/Gemfile
new file mode 100644
index 00000000000..fa75df15632
--- /dev/null
+++ b/vendor/gems/attr_encrypted/Gemfile
@@ -0,0 +1,3 @@
+source 'https://rubygems.org'
+
+gemspec
diff --git a/vendor/gems/attr_encrypted/Gemfile.lock b/vendor/gems/attr_encrypted/Gemfile.lock
new file mode 100644
index 00000000000..72626225cf7
--- /dev/null
+++ b/vendor/gems/attr_encrypted/Gemfile.lock
@@ -0,0 +1,152 @@
+PATH
+ remote: .
+ specs:
+ attr_encrypted (3.2.4)
+ encryptor (~> 3.0.0)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ 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)
+ 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)
+ activemodel (6.1.7)
+ activesupport (= 6.1.7)
+ activerecord (6.1.7)
+ activemodel (= 6.1.7)
+ activesupport (= 6.1.7)
+ 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.1)
+ public_suffix (>= 2.0.2, < 6.0)
+ bcrypt (3.1.18)
+ bcrypt-ruby (3.1.5)
+ bcrypt (>= 3.1.3)
+ builder (3.2.4)
+ codeclimate-test-reporter (0.6.0)
+ simplecov (>= 0.7.1, < 1.0.0)
+ concurrent-ruby (1.1.10)
+ crass (1.0.6)
+ data_objects (0.10.17)
+ addressable (~> 2.1)
+ datamapper (1.2.0)
+ dm-aggregates (~> 1.2.0)
+ dm-constraints (~> 1.2.0)
+ dm-core (~> 1.2.0)
+ dm-migrations (~> 1.2.0)
+ dm-serializer (~> 1.2.0)
+ dm-timestamps (~> 1.2.0)
+ dm-transactions (~> 1.2.0)
+ dm-types (~> 1.2.0)
+ dm-validations (~> 1.2.0)
+ dm-aggregates (1.2.0)
+ dm-core (~> 1.2.0)
+ dm-constraints (1.2.0)
+ dm-core (~> 1.2.0)
+ dm-core (1.2.1)
+ addressable (~> 2.3)
+ dm-do-adapter (1.2.0)
+ data_objects (~> 0.10.6)
+ dm-core (~> 1.2.0)
+ dm-migrations (1.2.0)
+ dm-core (~> 1.2.0)
+ dm-serializer (1.2.2)
+ dm-core (~> 1.2.0)
+ fastercsv (~> 1.5)
+ json (~> 1.6)
+ json_pure (~> 1.6)
+ multi_json (~> 1.0)
+ dm-sqlite-adapter (1.2.0)
+ dm-do-adapter (~> 1.2.0)
+ do_sqlite3 (~> 0.10.6)
+ dm-timestamps (1.2.0)
+ dm-core (~> 1.2.0)
+ dm-transactions (1.2.0)
+ dm-core (~> 1.2.0)
+ dm-types (1.2.2)
+ bcrypt-ruby (~> 3.0)
+ dm-core (~> 1.2.0)
+ fastercsv (~> 1.5)
+ json (~> 1.6)
+ multi_json (~> 1.0)
+ stringex (~> 1.4)
+ uuidtools (~> 2.1)
+ dm-validations (1.2.0)
+ dm-core (~> 1.2.0)
+ do_sqlite3 (0.10.17)
+ data_objects (= 0.10.17)
+ docile (1.4.0)
+ encryptor (3.0.0)
+ erubi (1.11.0)
+ fastercsv (1.5.5)
+ i18n (1.12.0)
+ concurrent-ruby (~> 1.0)
+ json (1.8.6)
+ json_pure (1.8.6)
+ loofah (2.19.0)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.5.9)
+ minitest (5.16.3)
+ multi_json (1.15.0)
+ nokogiri (1.13.8-x86_64-linux)
+ racc (~> 1.4)
+ public_suffix (5.0.0)
+ racc (1.6.0)
+ rack (2.2.4)
+ rack-test (2.0.2)
+ rack (>= 1.3)
+ rails-dom-testing (2.0.3)
+ activesupport (>= 4.2.0)
+ nokogiri (>= 1.6)
+ rails-html-sanitizer (1.4.3)
+ loofah (~> 2.3)
+ rake (13.0.6)
+ sequel (5.60.1)
+ simplecov (0.21.2)
+ docile (~> 1.1)
+ simplecov-html (~> 0.11)
+ simplecov_json_formatter (~> 0.1)
+ simplecov-html (0.12.3)
+ simplecov-rcov (0.3.1)
+ simplecov (>= 0.4.1)
+ simplecov_json_formatter (0.1.4)
+ sqlite3 (1.5.0-x86_64-linux)
+ stringex (1.5.1)
+ tzinfo (2.0.5)
+ concurrent-ruby (~> 1.0)
+ uuidtools (2.2.0)
+ zeitwerk (2.6.0)
+
+PLATFORMS
+ x86_64-linux
+
+DEPENDENCIES
+ actionpack (~> 6.1)
+ activerecord (~> 6.1)
+ attr_encrypted!
+ codeclimate-test-reporter (<= 0.6.0)
+ datamapper
+ dm-sqlite-adapter
+ minitest
+ rake
+ sequel
+ simplecov
+ simplecov-rcov
+ sqlite3
+
+BUNDLED WITH
+ 2.3.19
diff --git a/vendor/gems/attr_encrypted/MIT-LICENSE b/vendor/gems/attr_encrypted/MIT-LICENSE
new file mode 100644
index 00000000000..07f53f78f58
--- /dev/null
+++ b/vendor/gems/attr_encrypted/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 Sean Huber - shuber@huberry.com
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/vendor/gems/attr_encrypted/README.md b/vendor/gems/attr_encrypted/README.md
new file mode 100644
index 00000000000..87ac7219a92
--- /dev/null
+++ b/vendor/gems/attr_encrypted/README.md
@@ -0,0 +1,466 @@
+## Maintainer(s) wanted!!!
+
+**If you have an interest in maintaining this project... please see https://github.com/attr-encrypted/attr_encrypted/issues/379**
+
+# attr_encrypted
+
+[![Build Status](https://secure.travis-ci.org/attr-encrypted/attr_encrypted.svg)](https://travis-ci.org/attr-encrypted/attr_encrypted) [![Test Coverage](https://codeclimate.com/github/attr-encrypted/attr_encrypted/badges/coverage.svg)](https://codeclimate.com/github/attr-encrypted/attr_encrypted/coverage) [![Code Climate](https://codeclimate.com/github/attr-encrypted/attr_encrypted/badges/gpa.svg)](https://codeclimate.com/github/attr-encrypted/attr_encrypted) [![Gem Version](https://badge.fury.io/rb/attr_encrypted.svg)](https://badge.fury.io/rb/attr_encrypted) [![security](https://hakiri.io/github/attr-encrypted/attr_encrypted/master.svg)](https://hakiri.io/github/attr-encrypted/attr_encrypted/master)
+
+Generates attr_accessors that transparently encrypt and decrypt attributes.
+
+It works with ANY class, however, you get a few extra features when you're using it with `ActiveRecord`, `DataMapper`, or `Sequel`.
+
+
+## Installation
+
+Add attr_encrypted to your gemfile:
+
+```ruby
+ gem "attr_encrypted", "~> 3.1.0"
+```
+
+Then install the gem:
+
+```bash
+ bundle install
+```
+
+## Usage
+
+If you're using an ORM like `ActiveRecord`, `DataMapper`, or `Sequel`, using attr_encrypted is easy:
+
+```ruby
+ class User
+ attr_encrypted :ssn, key: 'This is a key that is 256 bits!!'
+ end
+```
+
+If you're using a PORO, you have to do a little bit more work by extending the class:
+
+```ruby
+ class User
+ extend AttrEncrypted
+ attr_accessor :name
+ attr_encrypted :ssn, key: 'This is a key that is 256 bits!!'
+
+ def load
+ # loads the stored data
+ end
+
+ def save
+ # saves the :name and :encrypted_ssn attributes somewhere (e.g. filesystem, database, etc)
+ end
+ end
+
+ user = User.new
+ user.ssn = '123-45-6789'
+ user.ssn # returns the unencrypted object ie. '123-45-6789'
+ user.encrypted_ssn # returns the encrypted version of :ssn
+ user.save
+
+ user = User.load
+ user.ssn # decrypts :encrypted_ssn and returns '123-45-6789'
+```
+
+### Encrypt/decrypt attribute class methods
+
+Two class methods are available for each attribute: `User.encrypt_email` and `User.decrypt_email`. They accept as arguments the same options that the `attr_encrypted` class method accepts. For example:
+
+```ruby
+ key = SecureRandom.random_bytes(32)
+ iv = SecureRandom.random_bytes(12)
+ encrypted_email = User.encrypt_email('test@test.com', iv: iv, key: key)
+ email = User.decrypt_email(encrypted_email, iv: iv, key: key)
+```
+
+The `attr_encrypted` class method is also aliased as `attr_encryptor` to conform to Ruby's `attr_` naming conventions. I should have called this project `attr_encryptor` but it was too late when I realized it ='(.
+
+### attr_encrypted with database persistence
+
+By default, `attr_encrypted` uses the `:per_attribute_iv` encryption mode. This mode requires a column to store your cipher text and a column to store your IV (initialization vector).
+
+Create or modify the table that your model uses to add a column with the `encrypted_` prefix (which can be modified, see below), e.g. `encrypted_ssn` via a migration like the following:
+
+```ruby
+ create_table :users do |t|
+ t.string :name
+ t.string :encrypted_ssn
+ t.string :encrypted_ssn_iv
+ t.timestamps
+ end
+```
+
+You can use a string or binary column type. (See the encode option section below for more info)
+
+If you use the same key for each record, add a unique index on the IV. Repeated IVs with AES-GCM (the default algorithm) allow an attacker to recover the key.
+
+```ruby
+ add_index :users, :encrypted_ssn_iv, unique: true
+```
+
+### Specifying the encrypted attribute name
+
+By default, the encrypted attribute name is `encrypted_#{attribute}` (e.g. `attr_encrypted :email` would create an attribute named `encrypted_email`). So, if you're storing the encrypted attribute in the database, you need to make sure the `encrypted_#{attribute}` field exists in your table. You have a couple of options if you want to name your attribute or db column something else, see below for more details.
+
+
+## attr_encrypted options
+
+#### Options are evaluated
+All options will be evaluated at the instance level. If you pass in a symbol it will be passed as a message to the instance of your class. If you pass a proc or any object that responds to `:call` it will be called. You can pass in the instance of your class as an argument to the proc. Anything else will be returned. For example:
+
+##### Symbols representing instance methods
+
+Here is an example class that uses an instance method to determines the encryption key to use.
+
+```ruby
+ class User
+ attr_encrypted :email, key: :encryption_key
+
+ def encryption_key
+ # does some fancy logic and returns an encryption key
+ end
+ end
+```
+
+
+##### Procs
+
+Here is an example of passing a proc/lambda object as the `:key` option as well:
+
+```ruby
+ class User
+ attr_encrypted :email, key: proc { |user| user.key }
+ end
+```
+
+
+### Default options
+
+The following are the default options used by `attr_encrypted`:
+
+```ruby
+ prefix: 'encrypted_',
+ suffix: '',
+ if: true,
+ unless: false,
+ encode: false,
+ encode_iv: true,
+ encode_salt: true,
+ default_encoding: 'm',
+ marshal: false,
+ marshaler: Marshal,
+ dump_method: 'dump',
+ load_method: 'load',
+ encryptor: Encryptor,
+ encrypt_method: 'encrypt',
+ decrypt_method: 'decrypt',
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm',
+ allow_empty_value: false
+```
+
+All of the aforementioned options are explained in depth below.
+
+Additionally, you can specify default options for all encrypted attributes in your class. Instead of having to define your class like this:
+
+```ruby
+ class User
+ attr_encrypted :email, key: 'This is a key that is 256 bits!!', prefix: '', suffix: '_crypted'
+ attr_encrypted :ssn, key: 'a different secret key', prefix: '', suffix: '_crypted'
+ attr_encrypted :credit_card, key: 'another secret key', prefix: '', suffix: '_crypted'
+ end
+```
+
+You can simply define some default options like so:
+
+```ruby
+ class User
+ attr_encrypted_options.merge!(prefix: '', :suffix => '_crypted')
+ attr_encrypted :email, key: 'This is a key that is 256 bits!!'
+ attr_encrypted :ssn, key: 'a different secret key'
+ attr_encrypted :credit_card, key: 'another secret key'
+ end
+```
+
+This should help keep your classes clean and DRY.
+
+### The `:attribute` option
+
+You can simply pass the name of the encrypted attribute as the `:attribute` option:
+
+```ruby
+ class User
+ attr_encrypted :email, key: 'This is a key that is 256 bits!!', attribute: 'email_encrypted'
+ end
+```
+
+This would generate an attribute named `email_encrypted`
+
+
+### The `:prefix` and `:suffix` options
+
+If you don't like the `encrypted_#{attribute}` naming convention then you can specify your own:
+
+```ruby
+ class User
+ attr_encrypted :email, key: 'This is a key that is 256 bits!!', prefix: 'secret_', suffix: '_crypted'
+ end
+```
+
+This would generate the following attribute: `secret_email_crypted`.
+
+
+### The `:key` option
+
+The `:key` option is used to pass in a data encryption key to be used with whatever encryption class you use. If you're using `Encryptor`, the key must meet minimum length requirements respective to the algorithm that you use; aes-256 requires a 256 bit key, etc. The `:key` option is not required (see custom encryptor below).
+
+
+##### Unique keys for each attribute
+
+You can specify unique keys for each attribute if you'd like:
+
+```ruby
+ class User
+ attr_encrypted :email, key: 'This is a key that is 256 bits!!'
+ attr_encrypted :ssn, key: 'a different secret key'
+ end
+```
+
+It is recommended to use a symbol or a proc for the key and to store information regarding what key was used to encrypt your data. (See below for more details.)
+
+
+### The `:if` and `:unless` options
+
+There may be times that you want to only encrypt when certain conditions are met. For example maybe you're using rails and you don't want to encrypt attributes when you're in development mode. You can specify conditions like this:
+
+```ruby
+ class User < ActiveRecord::Base
+ attr_encrypted :email, key: 'This is a key that is 256 bits!!', unless: Rails.env.development?
+ attr_encrypted :ssn, key: 'This is a key that is 256 bits!!', if: Rails.env.development?
+ end
+```
+
+You can specify both `:if` and `:unless` options.
+
+
+### The `:encryptor`, `:encrypt_method`, and `:decrypt_method` options
+
+The `Encryptor` class is used by default. You may use your own custom encryptor by specifying the `:encryptor`, `:encrypt_method`, and `:decrypt_method` options.
+
+Lets suppose you'd like to use this custom encryptor class:
+
+```ruby
+ class SillyEncryptor
+ def self.silly_encrypt(options)
+ (options[:value] + options[:secret_key]).reverse
+ end
+
+ def self.silly_decrypt(options)
+ options[:value].reverse.gsub(/#{options[:secret_key]}$/, '')
+ end
+ end
+```
+
+Simply set up your class like so:
+
+```ruby
+ class User
+ attr_encrypted :email, secret_key: 'This is a key that is 256 bits!!', encryptor: SillyEncryptor, encrypt_method: :silly_encrypt, decrypt_method: :silly_decrypt
+ end
+```
+
+Any options that you pass to `attr_encrypted` will be passed to the encryptor class along with the `:value` option which contains the string to encrypt/decrypt. Notice that the above example uses `:secret_key` instead of `:key`. See [encryptor](https://github.com/attr-encrypted/encryptor) for more info regarding the default encryptor class.
+
+
+### The `:mode` option
+
+The mode options allows you to specify in what mode your data will be encrypted. There are currently three modes: `:per_attribute_iv`, `:per_attribute_iv_and_salt`, and `:single_iv_and_salt`.
+
+__NOTE: `:per_attribute_iv_and_salt` and `:single_iv_and_salt` modes are deprecated and will be removed in the next major release.__
+
+
+### The `:algorithm` option
+
+The default `Encryptor` class uses the standard ruby OpenSSL library. Its default algorithm is `aes-256-gcm`. You can modify this by passing the `:algorithm` option to the `attr_encrypted` call like so:
+
+```ruby
+ class User
+ attr_encrypted :email, key: 'This is a key that is 256 bits!!', algorithm: 'aes-256-cbc'
+ end
+```
+
+To view a list of all cipher algorithms that are supported on your platform, run the following code in your favorite Ruby REPL:
+
+```ruby
+ require 'openssl'
+ puts OpenSSL::Cipher.ciphers
+```
+See [Encryptor](https://github.com/attr-encrypted/encryptor#algorithms) for more information.
+
+
+### The `:encode`, `:encode_iv`, `:encode_salt`, and `:default_encoding` options
+
+You're probably going to be storing your encrypted attributes somehow (e.g. filesystem, database, etc). You can simply pass the `:encode` option to automatically encode/decode when encrypting/decrypting. The default behavior assumes that you're using a string column type and will base64 encode your cipher text. If you choose to use the binary column type then encoding is not required, but be sure to pass in `false` with the `:encode` option.
+
+```ruby
+ class User
+ attr_encrypted :email, key: 'some secret key', encode: true, encode_iv: true, encode_salt: true
+ end
+```
+
+The default encoding is `m` (base64). You can change this by setting `encode: 'some encoding'`. See [`Array#pack`](http://ruby-doc.org/core-2.3.0/Array.html#method-i-pack) for more encoding options.
+
+
+### The `:marshal`, `:dump_method`, and `:load_method` options
+
+You may want to encrypt objects other than strings (e.g. hashes, arrays, etc). If this is the case, simply pass the `:marshal` option to automatically marshal when encrypting/decrypting.
+
+```ruby
+ class User
+ attr_encrypted :credentials, key: 'some secret key', marshal: true
+ end
+```
+
+You may also optionally specify `:marshaler`, `:dump_method`, and `:load_method` if you want to use something other than the default `Marshal` object.
+
+### The `:allow_empty_value` option
+
+You may want to encrypt empty strings or nil so as to not reveal which records are populated and which records are not.
+
+```ruby
+ class User
+ attr_encrypted :credentials, key: 'some secret key', marshal: true, allow_empty_value: true
+ end
+```
+
+
+## ORMs
+
+### ActiveRecord
+
+If you're using this gem with `ActiveRecord`, you get a few extra features:
+
+#### Default options
+
+The `:encode` option is set to true by default.
+
+#### Dynamic `find_by_` and `scoped_by_` methods
+
+Let's say you'd like to encrypt your user's email addresses, but you also need a way for them to login. Simply set up your class like so:
+
+```ruby
+ class User < ActiveRecord::Base
+ attr_encrypted :email, key: 'This is a key that is 256 bits!!'
+ attr_encrypted :password, key: 'some other secret key'
+ end
+```
+
+You can now lookup and login users like so:
+
+```ruby
+ User.find_by_email_and_password('test@example.com', 'testing')
+```
+
+The call to `find_by_email_and_password` is intercepted and modified to `find_by_encrypted_email_and_encrypted_password('ENCRYPTED EMAIL', 'ENCRYPTED PASSWORD')`. The dynamic scope methods like `scoped_by_email_and_password` work the same way.
+
+NOTE: This only works if all records are encrypted with the same encryption key (per attribute).
+
+__NOTE: This feature is deprecated and will be removed in the next major release.__
+
+
+### DataMapper and Sequel
+
+#### Default options
+
+The `:encode` option is set to true by default.
+
+
+## Deprecations
+
+attr_encrypted v2.0.0 now depends on encryptor v2.0.0. As part of both major releases many insecure defaults and behaviors have been deprecated. The new default behavior is as follows:
+
+* Default `:mode` is now `:per_attribute_iv`, the default `:mode` in attr_encrypted v1.x was `:single_iv_and_salt`.
+* Default `:algorithm` is now 'aes-256-gcm', the default `:algorithm` in attr_encrypted v1.x was 'aes-256-cbc'.
+* The encryption key provided must be of appropriate length respective to the algorithm used. Previously, encryptor did not verify minimum key length.
+* The dynamic finders available in ActiveRecord will only work with `:single_iv_and_salt` mode. It is strongly advised that you do not use this mode. If you can search the encrypted data, it wasn't encrypted securely. This functionality will be deprecated in the next major release.
+* `:per_attribute_iv_and_salt` and `:single_iv_and_salt` modes are deprecated and will be removed in the next major release.
+
+Backwards compatibility is supported by providing a special option that is passed to encryptor, namely, `:insecure_mode`:
+
+```ruby
+ class User
+ attr_encrypted :email, key: 'a secret key', algorithm: 'aes-256-cbc', mode: :single_iv_and_salt, insecure_mode: true
+ end
+```
+
+The `:insecure_mode` option will allow encryptor to ignore the new security requirements. It is strongly advised that if you use this older insecure behavior that you migrate to the newer more secure behavior.
+
+
+## Upgrading from attr_encrypted v1.x to v3.x
+
+Modify your gemfile to include the new version of attr_encrypted:
+
+```ruby
+ gem attr_encrypted, "~> 3.0.0"
+```
+
+The update attr_encrypted:
+
+```bash
+ bundle update attr_encrypted
+```
+
+Then modify your models using attr\_encrypted to account for the changes in default options. Specifically, pass in the `:mode` and `:algorithm` options that you were using if you had not previously done so. If your key is insufficient length relative to the algorithm that you use, you should also pass in `insecure_mode: true`; this will prevent Encryptor from raising an exception regarding insufficient key length. Please see the Deprecations sections for more details including an example of how to specify your model with default options from attr_encrypted v1.x.
+
+## Upgrading from attr_encrypted v2.x to v3.x
+
+A bug was discovered in Encryptor v2.0.0 that inccorectly set the IV when using an AES-\*-GCM algorithm. Unfornately fixing this major security issue results in the inability to decrypt records encrypted using an AES-*-GCM algorithm from Encryptor v2.0.0. Please see [Upgrading to Encryptor v3.0.0](https://github.com/attr-encrypted/encryptor#upgrading-from-v200-to-v300) for more info.
+
+It is strongly advised that you re-encrypt your data encrypted with Encryptor v2.0.0. However, you'll have to take special care to re-encrypt. To decrypt data encrypted with Encryptor v2.0.0 using an AES-\*-GCM algorithm you can use the `:v2_gcm_iv` option.
+
+It is recommended that you implement a strategy to insure that you do not mix the encryption implementations of Encryptor. One way to do this is to re-encrypt everything while your application is offline.Another way is to add a column that keeps track of what implementation was used. The path that you choose will depend on your situtation. Below is an example of how you might go about re-encrypting your data.
+
+```ruby
+ class User
+ attr_encrypted :ssn, key: :encryption_key, v2_gcm_iv: is_decrypting?(:ssn)
+
+ def is_decrypting?(attribute)
+ encrypted_attributes[attribute][:operation] == :decrypting
+ end
+ end
+
+ User.all.each do |user|
+ old_ssn = user.ssn
+ user.ssn= old_ssn
+ user.save
+ end
+```
+
+## Things to consider before using attr_encrypted
+
+#### Searching, joining, etc
+While choosing to encrypt at the attribute level is the most secure solution, it is not without drawbacks. Namely, you cannot search the encrypted data, and because you can't search it, you can't index it either. You also can't use joins on the encrypted data. Data that is securely encrypted is effectively noise. So any operations that rely on the data not being noise will not work. If you need to do any of the aforementioned operations, please consider using database and file system encryption along with transport encryption as it moves through your stack.
+
+#### Data leaks
+Please also consider where your data leaks. If you're using attr_encrypted with Rails, it's highly likely that this data will enter your app as a request parameter. You'll want to be sure that you're filtering your request params from you logs or else your data is sitting in the clear in your logs. [Parameter Filtering in Rails](http://apidock.com/rails/ActionDispatch/Http/FilterParameters) Please also consider other possible leak points.
+
+#### Storage requirements
+When storing your encrypted data, please consider the length requirements of the db column that you're storing the cipher text in. Older versions of Mysql attempt to 'help' you by truncating strings that are too large for the column. When this happens, you will not be able to decrypt your data. [MySQL Strict Trans](http://www.davidpashley.com/2009/02/15/silently-truncated/)
+
+#### Metadata regarding your crypto implementation
+It is advisable to also store metadata regarding the circumstances of your encrypted data. Namely, you should store information about the key used to encrypt your data, as well as the algorithm. Having this metadata with every record will make key rotation and migrating to a new algorithm signficantly easier. It will allow you to continue to decrypt old data using the information provided in the metadata and new data can be encrypted using your new key and algorithm of choice.
+
+#### Enforcing the IV as a nonce
+On a related note, most algorithms require that your IV be unique for every record and key combination. You can enforce this using composite unique indexes on your IV and encryption key name/id column. [RFC 5084](https://tools.ietf.org/html/rfc5084#section-1.5)
+
+#### Unique key per record
+Lastly, while the `:per_attribute_iv_and_salt` mode is more secure than `:per_attribute_iv` mode because it uses a unique key per record, it uses a PBKDF function which introduces a huge performance hit (175x slower by my benchmarks). There are other ways of deriving a unique key per record that would be much faster.
+
+## Note on Patches/Pull Requests
+
+* Fork the project.
+* Make your feature addition or bug fix.
+* Add tests for it. This is important so I don't break it in a
+ future version unintentionally.
+* Commit, do not mess with rakefile, version, changelog, or history.
+* Send me a pull request. Bonus points for topic branches.
diff --git a/vendor/gems/attr_encrypted/Rakefile b/vendor/gems/attr_encrypted/Rakefile
new file mode 100644
index 00000000000..3dbc96ceb14
--- /dev/null
+++ b/vendor/gems/attr_encrypted/Rakefile
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'rake/testtask'
+require 'rdoc/task'
+require "bundler/gem_tasks"
+
+desc 'Test the attr_encrypted gem.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.warning = false
+ t.verbose = true
+end
+
+desc 'Generate documentation for the attr_encrypted gem.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'attr_encrypted'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README*')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
+desc 'Default: run unit tests.'
+task :default => :test
diff --git a/vendor/gems/attr_encrypted/attr_encrypted.gemspec b/vendor/gems/attr_encrypted/attr_encrypted.gemspec
new file mode 100644
index 00000000000..b6a39bddd2c
--- /dev/null
+++ b/vendor/gems/attr_encrypted/attr_encrypted.gemspec
@@ -0,0 +1,52 @@
+# -*- encoding: utf-8 -*-
+
+lib = File.expand_path('../lib/', __FILE__)
+$:.unshift lib unless $:.include?(lib)
+
+require 'attr_encrypted/version'
+require 'date'
+
+Gem::Specification.new do |s|
+ s.name = 'attr_encrypted'
+ s.version = AttrEncrypted::Version.string
+ s.date = Date.today
+
+ s.summary = 'GitLab fork of attr_encrypted'
+ s.description = "Generates attr_accessors that encrypt and decrypt attributes transparently.\n\n
+Forked from https://github.com/attr-encrypted/attr_encrypted."
+
+ s.authors = ['Sean Huber', 'S. Brent Faulkner', 'William Monk', 'Stephen Aghaulor']
+ s.email = ['seah@shuber.io', 'sbfaulkner@gmail.com', 'billy.monk@gmail.com', 'saghaulor@gmail.com']
+ s.homepage = 'https://gitlab.com/gitlab-org/ruby/gems/attr_encrypted'
+ s.license = 'MIT'
+
+ s.rdoc_options = ['--line-numbers', '--inline-source', '--main', 'README.rdoc']
+
+ s.require_paths = ['lib']
+
+ s.files = Dir['lib/**/*.rb']
+ s.test_files = Dir['test/**/*']
+
+ s.required_ruby_version = '>= 2.7.0'
+
+ s.add_dependency('encryptor', ['~> 3.0.0'])
+
+ activerecord_version = "~> 6.1"
+ s.add_development_dependency('activerecord', activerecord_version)
+ s.add_development_dependency('actionpack', activerecord_version)
+ s.add_development_dependency('datamapper')
+ s.add_development_dependency('rake')
+ s.add_development_dependency('minitest')
+ s.add_development_dependency('sequel')
+ s.add_development_dependency('sqlite3')
+ s.add_development_dependency('dm-sqlite-adapter')
+ s.add_development_dependency('simplecov')
+ s.add_development_dependency('simplecov-rcov')
+ s.add_development_dependency("codeclimate-test-reporter", '<= 0.6.0')
+
+ s.post_install_message = "\n\n\nWARNING: Several insecure default options and features were deprecated in attr_encrypted v2.0.0.\n
+Additionally, there was a bug in Encryptor v2.0.0 that insecurely encrypted data when using an AES-*-GCM algorithm.\n
+This bug was fixed but introduced breaking changes between v2.x and v3.x.\n
+Please see the README for more information regarding upgrading to attr_encrypted v3.0.0.\n\n\n"
+
+end
diff --git a/vendor/gems/attr_encrypted/lib/attr_encrypted.rb b/vendor/gems/attr_encrypted/lib/attr_encrypted.rb
new file mode 100644
index 00000000000..88e5f65e3c8
--- /dev/null
+++ b/vendor/gems/attr_encrypted/lib/attr_encrypted.rb
@@ -0,0 +1,473 @@
+# frozen_string_literal: true
+
+require 'encryptor'
+
+# Adds attr_accessors that encrypt and decrypt an object's attributes
+module AttrEncrypted
+ autoload :Version, 'attr_encrypted/version'
+
+ def self.extended(base) # :nodoc:
+ base.class_eval do
+ include InstanceMethods
+ attr_writer :attr_encrypted_options
+ @attr_encrypted_options, @encrypted_attributes = {}, {}
+ end
+ end
+
+ # Generates attr_accessors that encrypt and decrypt attributes transparently
+ #
+ # Options (any other options you specify are passed to the Encryptor's encrypt and decrypt methods)
+ #
+ # attribute: The name of the referenced encrypted attribute. For example
+ # <tt>attr_accessor :email, attribute: :ee</tt> would generate an
+ # attribute named 'ee' to store the encrypted email. This is useful when defining
+ # one attribute to encrypt at a time or when the :prefix and :suffix options
+ # aren't enough.
+ # Defaults to nil.
+ #
+ # prefix: A prefix used to generate the name of the referenced encrypted attributes.
+ # For example <tt>attr_accessor :email, prefix: 'crypted_'</tt> would
+ # generate attributes named 'crypted_email' to store the encrypted
+ # email and password.
+ # Defaults to 'encrypted_'.
+ #
+ # suffix: A suffix used to generate the name of the referenced encrypted attributes.
+ # For example <tt>attr_accessor :email, prefix: '', suffix: '_encrypted'</tt>
+ # would generate attributes named 'email_encrypted' to store the
+ # encrypted email.
+ # Defaults to ''.
+ #
+ # key: The encryption key. This option may not be required if
+ # you're using a custom encryptor. If you pass a symbol
+ # representing an instance method then the :key option
+ # will be replaced with the result of the method before
+ # being passed to the encryptor. Objects that respond
+ # to :call are evaluated as well (including procs).
+ # Any other key types will be passed directly to the encryptor.
+ # Defaults to nil.
+ #
+ # encode: If set to true, attributes will be encoded as well as
+ # encrypted. This is useful if you're planning on storing
+ # the encrypted attributes in a database. The default
+ # encoding is 'm' (base64), however this can be overwritten
+ # by setting the :encode option to some other encoding
+ # string instead of just 'true'. See
+ # http://www.ruby-doc.org/core/classes/Array.html#M002245
+ # for more encoding directives.
+ # Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.
+ #
+ # encode_iv: Defaults to true.
+
+ # encode_salt: Defaults to true.
+ #
+ # default_encoding: Defaults to 'm' (base64).
+ #
+ # marshal: If set to true, attributes will be marshaled as well
+ # as encrypted. This is useful if you're planning on
+ # encrypting something other than a string.
+ # Defaults to false.
+ #
+ # marshaler: The object to use for marshaling.
+ # Defaults to Marshal.
+ #
+ # dump_method: The dump method name to call on the <tt>:marshaler</tt> object to.
+ # Defaults to 'dump'.
+ #
+ # load_method: The load method name to call on the <tt>:marshaler</tt> object.
+ # Defaults to 'load'.
+ #
+ # encryptor: The object to use for encrypting.
+ # Defaults to Encryptor.
+ #
+ # encrypt_method: The encrypt method name to call on the <tt>:encryptor</tt> object.
+ # Defaults to 'encrypt'.
+ #
+ # decrypt_method: The decrypt method name to call on the <tt>:encryptor</tt> object.
+ # Defaults to 'decrypt'.
+ #
+ # if: Attributes are only encrypted if this option evaluates
+ # to true. If you pass a symbol representing an instance
+ # method then the result of the method will be evaluated.
+ # Any objects that respond to <tt>:call</tt> are evaluated as well.
+ # Defaults to true.
+ #
+ # unless: Attributes are only encrypted if this option evaluates
+ # to false. If you pass a symbol representing an instance
+ # method then the result of the method will be evaluated.
+ # Any objects that respond to <tt>:call</tt> are evaluated as well.
+ # Defaults to false.
+ #
+ # mode: Selects encryption mode for attribute: choose <tt>:single_iv_and_salt</tt> for compatibility
+ # with the old attr_encrypted API: the IV is derived from the encryption key by the underlying Encryptor class; salt is not used.
+ # The <tt>:per_attribute_iv_and_salt</tt> mode uses a per-attribute IV and salt. The salt is used to derive a unique key per attribute.
+ # A <tt>:per_attribute_iv</default> mode derives a unique IV per attribute; salt is not used.
+ # Defaults to <tt>:per_attribute_iv</tt>.
+ #
+ # allow_empty_value: Attributes which have nil or empty string values will not be encrypted unless this option
+ # has a truthy value.
+ #
+ # You can specify your own default options
+ #
+ # class User
+ # # Now all attributes will be encoded and marshaled by default
+ # attr_encrypted_options.merge!(encode: true, marshal: true, some_other_option: true)
+ # attr_encrypted :configuration, key: 'my secret key'
+ # end
+ #
+ #
+ # Example
+ #
+ # class User
+ # attr_encrypted :email, key: 'some secret key'
+ # attr_encrypted :configuration, key: 'some other secret key', marshal: true
+ # end
+ #
+ # @user = User.new
+ # @user.encrypted_email # nil
+ # @user.email? # false
+ # @user.email = 'test@example.com'
+ # @user.email? # true
+ # @user.encrypted_email # returns the encrypted version of 'test@example.com'
+ #
+ # @user.configuration = { time_zone: 'UTC' }
+ # @user.encrypted_configuration # returns the encrypted version of configuration
+ #
+ # See README for more examples
+ def attr_encrypted(*attributes)
+ options = attributes.last.is_a?(Hash) ? attributes.pop : {}
+ options = attr_encrypted_default_options.dup.merge!(attr_encrypted_options).merge!(options)
+
+ options[:encode] = options[:default_encoding] if options[:encode] == true
+ options[:encode_iv] = options[:default_encoding] if options[:encode_iv] == true
+ options[:encode_salt] = options[:default_encoding] if options[:encode_salt] == true
+
+ attributes.each do |attribute|
+ encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym
+
+ instance_methods_as_symbols = attribute_instance_methods_as_symbols
+
+ if attribute_instance_methods_as_symbols_available?
+ attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
+ attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
+
+ iv_name = "#{encrypted_attribute_name}_iv".to_sym
+ attr_reader iv_name unless instance_methods_as_symbols.include?(iv_name)
+ attr_writer iv_name unless instance_methods_as_symbols.include?(:"#{iv_name}=")
+
+ salt_name = "#{encrypted_attribute_name}_salt".to_sym
+ attr_reader salt_name unless instance_methods_as_symbols.include?(salt_name)
+ attr_writer salt_name unless instance_methods_as_symbols.include?(:"#{salt_name}=")
+ end
+
+ define_method(attribute) do
+ instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
+ end
+
+ define_method("#{attribute}=") do |value|
+ send("#{encrypted_attribute_name}=", encrypt(attribute, value))
+ instance_variable_set("@#{attribute}", value)
+ end
+
+ define_method("#{attribute}?") do
+ value = send(attribute)
+ value.respond_to?(:empty?) ? !value.empty? : !!value
+ end
+
+ encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name)
+ end
+ end
+
+ alias_method :attr_encryptor, :attr_encrypted
+
+ # Default options to use with calls to <tt>attr_encrypted</tt>
+ #
+ # It will inherit existing options from its superclass
+ def attr_encrypted_options
+ @attr_encrypted_options ||= superclass.attr_encrypted_options.dup
+ end
+
+ def attr_encrypted_default_options
+ {
+ prefix: 'encrypted_',
+ suffix: '',
+ if: true,
+ unless: false,
+ encode: false,
+ encode_iv: true,
+ encode_salt: true,
+ default_encoding: 'm',
+ marshal: false,
+ marshaler: Marshal,
+ dump_method: 'dump',
+ load_method: 'load',
+ encryptor: Encryptor,
+ encrypt_method: 'encrypt',
+ decrypt_method: 'decrypt',
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm',
+ allow_empty_value: false,
+ }
+ end
+
+ private :attr_encrypted_default_options
+
+ # Checks if an attribute is configured with <tt>attr_encrypted</tt>
+ #
+ # Example
+ #
+ # class User
+ # attr_accessor :name
+ # attr_encrypted :email
+ # end
+ #
+ # User.attr_encrypted?(:name) # false
+ # User.attr_encrypted?(:email) # true
+ def attr_encrypted?(attribute)
+ encrypted_attributes.has_key?(attribute.to_sym)
+ end
+
+ # Decrypts a value for the attribute specified
+ #
+ # Example
+ #
+ # class User
+ # attr_encrypted :email
+ # end
+ #
+ # email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
+ def decrypt(attribute, encrypted_value, options = {})
+ options = encrypted_attributes[attribute.to_sym].merge(options)
+ if options[:if] && !options[:unless] && not_empty?(encrypted_value)
+ encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
+ value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value))
+ if options[:marshal]
+ value = options[:marshaler].send(options[:load_method], value)
+ elsif defined?(Encoding)
+ encoding = Encoding.default_internal || Encoding.default_external
+ value = value.force_encoding(encoding.name)
+ end
+ value
+ else
+ encrypted_value
+ end
+ end
+
+ # Encrypts a value for the attribute specified
+ #
+ # Example
+ #
+ # class User
+ # attr_encrypted :email
+ # end
+ #
+ # encrypted_email = User.encrypt(:email, 'test@example.com')
+ def encrypt(attribute, value, options = {})
+ options = encrypted_attributes[attribute.to_sym].merge(options)
+ if options[:if] && !options[:unless] && (options[:allow_empty_value] || not_empty?(value))
+ value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
+ encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(value: value))
+ encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
+ encrypted_value
+ else
+ value
+ end
+ end
+
+ def not_empty?(value)
+ !value.nil? && !(value.is_a?(String) && value.empty?)
+ end
+
+ # Contains a hash of encrypted attributes with virtual attribute names as keys
+ # and their corresponding options as values
+ #
+ # Example
+ #
+ # class User
+ # attr_encrypted :email, key: 'my secret key'
+ # end
+ #
+ # User.encrypted_attributes # { email: { attribute: 'encrypted_email', key: 'my secret key' } }
+ def encrypted_attributes
+ @encrypted_attributes ||= superclass.encrypted_attributes.dup
+ end
+
+ # Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method
+ # if attribute was configured with attr_encrypted
+ #
+ # Example
+ #
+ # class User
+ # attr_encrypted :email, key: 'my secret key'
+ # end
+ #
+ # User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
+ def method_missing(method, *arguments, &block)
+ if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3)
+ send($1, $3, *arguments)
+ else
+ super
+ end
+ end
+
+ module InstanceMethods
+ # Decrypts a value for the attribute specified using options evaluated in the current object's scope
+ #
+ # Example
+ #
+ # class User
+ # attr_accessor :secret_key
+ # attr_encrypted :email, key: :secret_key
+ #
+ # def initialize(secret_key)
+ # self.secret_key = secret_key
+ # end
+ # end
+ #
+ # @user = User.new('some-secret-key')
+ # @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
+ def decrypt(attribute, encrypted_value)
+ encrypted_attributes[attribute.to_sym][:operation] = :decrypting
+ encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(encrypted_value)
+ self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
+ end
+
+ # Encrypts a value for the attribute specified using options evaluated in the current object's scope
+ #
+ # Example
+ #
+ # class User
+ # attr_accessor :secret_key
+ # attr_encrypted :email, key: :secret_key
+ #
+ # def initialize(secret_key)
+ # self.secret_key = secret_key
+ # end
+ # end
+ #
+ # @user = User.new('some-secret-key')
+ # @user.encrypt(:email, 'test@example.com')
+ def encrypt(attribute, value)
+ encrypted_attributes[attribute.to_sym][:operation] = :encrypting
+ encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(value)
+ self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
+ end
+
+ # Copies the class level hash of encrypted attributes with virtual attribute names as keys
+ # and their corresponding options as values to the instance
+ #
+ def encrypted_attributes
+ @encrypted_attributes ||= begin
+ duplicated= {}
+ self.class.encrypted_attributes.map { |key, value| duplicated[key] = value.dup }
+ duplicated
+ end
+ end
+
+ protected
+
+ # Returns attr_encrypted options evaluated in the current object's scope for the attribute specified
+ def evaluated_attr_encrypted_options_for(attribute)
+ evaluated_options = Hash.new
+ attributes = encrypted_attributes[attribute.to_sym]
+ attribute_option_value = attributes[:attribute]
+
+ [:if, :unless, :value_present, :allow_empty_value].each do |option|
+ evaluated_options[option] = evaluate_attr_encrypted_option(attributes[option])
+ end
+
+ evaluated_options[:attribute] = attribute_option_value
+
+ evaluated_options.tap do |options|
+ if options[:if] && !options[:unless] && options[:value_present] || options[:allow_empty_value]
+ (attributes.keys - evaluated_options.keys).each do |option|
+ options[option] = evaluate_attr_encrypted_option(attributes[option])
+ end
+
+ unless options[:mode] == :single_iv_and_salt
+ load_iv_for_attribute(attribute, options)
+ end
+
+ if options[:mode] == :per_attribute_iv_and_salt
+ load_salt_for_attribute(attribute, options)
+ end
+ end
+ end
+ end
+
+ # Evaluates symbol (method reference) or proc (responds to call) options
+ #
+ # If the option is not a symbol or proc then the original option is returned
+ def evaluate_attr_encrypted_option(option)
+ if option.is_a?(Symbol) && respond_to?(option, true)
+ send(option)
+ elsif option.respond_to?(:call)
+ option.call(self)
+ else
+ option
+ end
+ end
+
+ def load_iv_for_attribute(attribute, options)
+ encrypted_attribute_name = options[:attribute]
+ encode_iv = options[:encode_iv]
+ iv = options[:iv] || send("#{encrypted_attribute_name}_iv")
+ if options[:operation] == :encrypting
+ begin
+ iv = generate_iv(options[:algorithm])
+ iv = [iv].pack(encode_iv) if encode_iv
+ send("#{encrypted_attribute_name}_iv=", iv)
+ rescue RuntimeError
+ end
+ end
+ if iv && !iv.empty?
+ iv = iv.unpack(encode_iv).first if encode_iv
+ options[:iv] = iv
+ end
+ end
+
+ def generate_iv(algorithm)
+ algo = OpenSSL::Cipher.new(algorithm)
+ algo.encrypt
+ algo.random_iv
+ end
+
+ def load_salt_for_attribute(attribute, options)
+ encrypted_attribute_name = options[:attribute]
+ encode_salt = options[:encode_salt]
+ salt = options[:salt] || send("#{encrypted_attribute_name}_salt")
+ if options[:operation] == :encrypting
+ salt = SecureRandom.random_bytes
+ salt = prefix_and_encode_salt(salt, encode_salt) if encode_salt
+ send("#{encrypted_attribute_name}_salt=", salt)
+ end
+ if salt && !salt.empty?
+ salt = decode_salt_if_encoded(salt, encode_salt) if encode_salt
+ options[:salt] = salt
+ end
+ end
+
+ def prefix_and_encode_salt(salt, encoding)
+ prefix = '_'
+ prefix + [salt].pack(encoding)
+ end
+
+ def decode_salt_if_encoded(salt, encoding)
+ prefix = '_'
+ salt.slice(0).eql?(prefix) ? salt.slice(1..-1).unpack(encoding).first : salt
+ end
+ end
+
+ protected
+
+ def attribute_instance_methods_as_symbols
+ instance_methods.collect { |method| method.to_sym }
+ end
+
+ def attribute_instance_methods_as_symbols_available?
+ true
+ end
+
+end
+
+
+Dir[File.join(File.dirname(__FILE__), 'attr_encrypted', 'adapters', '*.rb')].each { |adapter| require adapter }
diff --git a/vendor/gems/attr_encrypted/lib/attr_encrypted/adapters/active_record.rb b/vendor/gems/attr_encrypted/lib/attr_encrypted/adapters/active_record.rb
new file mode 100644
index 00000000000..ec8d0208e92
--- /dev/null
+++ b/vendor/gems/attr_encrypted/lib/attr_encrypted/adapters/active_record.rb
@@ -0,0 +1,146 @@
+# frozen_string_literal: true
+
+if defined?(ActiveRecord::Base)
+ module AttrEncrypted
+ module Adapters
+ module ActiveRecord
+ def self.extended(base) # :nodoc:
+ base.class_eval do
+
+ # https://github.com/attr-encrypted/attr_encrypted/issues/68
+ alias_method :reload_without_attr_encrypted, :reload
+ def reload(*args, &block)
+ result = reload_without_attr_encrypted(*args, &block)
+ self.class.encrypted_attributes.keys.each do |attribute_name|
+ instance_variable_set("@#{attribute_name}", nil)
+ end
+ result
+ end
+
+ attr_encrypted_options[:encode] = true
+
+ class << self
+ alias_method :method_missing_without_attr_encrypted, :method_missing
+ alias_method :method_missing, :method_missing_with_attr_encrypted
+ end
+
+ def perform_attribute_assignment(method, new_attributes, *args)
+ return if new_attributes.blank?
+
+ send method, new_attributes.reject { |k, _| self.class.encrypted_attributes.key?(k.to_sym) }, *args
+ send method, new_attributes.reject { |k, _| !self.class.encrypted_attributes.key?(k.to_sym) }, *args
+ end
+ private :perform_attribute_assignment
+
+ if ::ActiveRecord::VERSION::STRING > "3.1"
+ alias_method :assign_attributes_without_attr_encrypted, :assign_attributes
+ def assign_attributes(*args)
+ perform_attribute_assignment :assign_attributes_without_attr_encrypted, *args
+ end
+ end
+
+ alias_method :attributes_without_attr_encrypted=, :attributes=
+ def attributes=(*args)
+ perform_attribute_assignment :attributes_without_attr_encrypted=, *args
+ end
+ end
+ end
+
+ protected
+
+ # <tt>attr_encrypted</tt> method
+ def attr_encrypted(*attrs)
+ super
+ options = attrs.extract_options!
+ attr = attrs.pop
+ attribute attr if ::ActiveRecord::VERSION::STRING >= "5.1.0"
+ options.merge! encrypted_attributes[attr]
+
+ define_method("#{attr}_was") do
+ attribute_was(attr)
+ end
+
+ if ::ActiveRecord::VERSION::STRING >= "4.1"
+ define_method("#{attr}_changed?") do |options = {}|
+ attribute_changed?(attr, **options)
+ end
+ else
+ define_method("#{attr}_changed?") do
+ attribute_changed?(attr)
+ end
+ end
+
+ define_method("#{attr}_change") do
+ attribute_change(attr)
+ end
+
+ define_method("#{attr}_with_dirtiness=") do |value|
+ attribute_will_change!(attr) if value != __send__(attr)
+ __send__("#{attr}_without_dirtiness=", value)
+ end
+
+ alias_method "#{attr}_without_dirtiness=", "#{attr}="
+ alias_method "#{attr}=", "#{attr}_with_dirtiness="
+
+ alias_method "#{attr}_before_type_cast", attr
+ end
+
+ def attribute_instance_methods_as_symbols
+ # We add accessor methods of the db columns to the list of instance
+ # methods returned to let ActiveRecord define the accessor methods
+ # for the db columns
+
+ if connected? && table_exists?
+ columns_hash.keys.inject(super) {|instance_methods, column_name| instance_methods.concat [column_name.to_sym, :"#{column_name}="]}
+ else
+ super
+ end
+ end
+
+ # Prevent attr_encrypted from defining virtual accessors for encryption
+ # data when the code and schema are out of sync. See this issue for more
+ # details: https://github.com/attr-encrypted/attr_encrypted/issues/332
+ def attribute_instance_methods_as_symbols_available?
+ false
+ end
+
+ # Allows you to use dynamic methods like <tt>find_by_email</tt> or <tt>scoped_by_email</tt> for
+ # encrypted attributes
+ #
+ # NOTE: This only works when the <tt>:key</tt> option is specified as a string (see the README)
+ #
+ # This is useful for encrypting fields like email addresses. Your user's email addresses
+ # are encrypted in the database, but you can still look up a user by email for logging in
+ #
+ # Example
+ #
+ # class User < ActiveRecord::Base
+ # attr_encrypted :email, key: 'secret key'
+ # end
+ #
+ # User.find_by_email_and_password('test@example.com', 'testing')
+ # # results in a call to
+ # User.find_by_encrypted_email_and_password('the_encrypted_version_of_test@example.com', 'testing')
+ def method_missing_with_attr_encrypted(method, *args, &block)
+ if match = /^(find|scoped)_(all_by|by)_([_a-zA-Z]\w*)$/.match(method.to_s)
+ attribute_names = match.captures.last.split('_and_')
+ attribute_names.each_with_index do |attribute, index|
+ if attr_encrypted?(attribute) && encrypted_attributes[attribute.to_sym][:mode] == :single_iv_and_salt
+ args[index] = send("encrypt_#{attribute}", args[index])
+ warn "DEPRECATION WARNING: This feature will be removed in the next major release."
+ attribute_names[index] = encrypted_attributes[attribute.to_sym][:attribute]
+ end
+ end
+ method = "#{match.captures[0]}_#{match.captures[1]}_#{attribute_names.join('_and_')}".to_sym
+ end
+ method_missing_without_attr_encrypted(method, *args, &block)
+ end
+ end
+ end
+ end
+
+ ActiveSupport.on_load(:active_record) do
+ extend AttrEncrypted
+ extend AttrEncrypted::Adapters::ActiveRecord
+ end
+end
diff --git a/vendor/gems/attr_encrypted/lib/attr_encrypted/adapters/data_mapper.rb b/vendor/gems/attr_encrypted/lib/attr_encrypted/adapters/data_mapper.rb
new file mode 100644
index 00000000000..03fb5caa457
--- /dev/null
+++ b/vendor/gems/attr_encrypted/lib/attr_encrypted/adapters/data_mapper.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+if defined?(DataMapper)
+ module AttrEncrypted
+ module Adapters
+ module DataMapper
+ def self.extended(base) # :nodoc:
+ class << base
+ alias_method :included_without_attr_encrypted, :included
+ alias_method :included, :included_with_attr_encrypted
+ end
+ end
+
+ def included_with_attr_encrypted(base)
+ included_without_attr_encrypted(base)
+ base.extend AttrEncrypted
+ base.attr_encrypted_options[:encode] = true
+ end
+ end
+ end
+ end
+
+ DataMapper::Resource.extend AttrEncrypted::Adapters::DataMapper
+end
diff --git a/vendor/gems/attr_encrypted/lib/attr_encrypted/adapters/sequel.rb b/vendor/gems/attr_encrypted/lib/attr_encrypted/adapters/sequel.rb
new file mode 100644
index 00000000000..ff14b9a0c8b
--- /dev/null
+++ b/vendor/gems/attr_encrypted/lib/attr_encrypted/adapters/sequel.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+if defined?(Sequel)
+ module AttrEncrypted
+ module Adapters
+ module Sequel
+ def self.extended(base) # :nodoc:
+ base.attr_encrypted_options[:encode] = true
+ end
+ end
+ end
+ end
+
+ Sequel::Model.extend AttrEncrypted
+ Sequel::Model.extend AttrEncrypted::Adapters::Sequel
+end
diff --git a/vendor/gems/attr_encrypted/lib/attr_encrypted/version.rb b/vendor/gems/attr_encrypted/lib/attr_encrypted/version.rb
new file mode 100644
index 00000000000..d97f1532249
--- /dev/null
+++ b/vendor/gems/attr_encrypted/lib/attr_encrypted/version.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module AttrEncrypted
+ # Contains information about this gem's version
+ module Version
+ MAJOR = 3
+ MINOR = 2
+ PATCH = 4
+
+ # Returns a version string by joining <tt>MAJOR</tt>, <tt>MINOR</tt>, and <tt>PATCH</tt> with <tt>'.'</tt>
+ #
+ # Example
+ #
+ # Version.string # '1.0.2'
+ def self.string
+ [MAJOR, MINOR, PATCH].join('.')
+ end
+ end
+end
diff --git a/vendor/gems/attr_encrypted/test/active_record_test.rb b/vendor/gems/attr_encrypted/test/active_record_test.rb
new file mode 100644
index 00000000000..133546b3ceb
--- /dev/null
+++ b/vendor/gems/attr_encrypted/test/active_record_test.rb
@@ -0,0 +1,346 @@
+# frozen_string_literal: true
+
+require_relative 'test_helper'
+
+ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
+
+def create_tables
+ ActiveRecord::Schema.define(version: 1) do
+ self.verbose = false
+ create_table :people do |t|
+ t.string :encrypted_email
+ t.string :password
+ t.string :encrypted_credentials
+ t.binary :salt
+ t.binary :key_iv
+ t.string :encrypted_email_salt
+ t.string :encrypted_credentials_salt
+ t.string :encrypted_email_iv
+ t.string :encrypted_credentials_iv
+ end
+ create_table :accounts do |t|
+ t.string :encrypted_password
+ t.string :encrypted_password_iv
+ t.string :encrypted_password_salt
+ t.string :key
+ end
+ create_table :users do |t|
+ t.string :login
+ t.string :encrypted_password
+ t.string :encrypted_password_iv
+ t.boolean :is_admin
+ end
+ create_table :prime_ministers do |t|
+ t.string :encrypted_name
+ t.string :encrypted_name_iv
+ end
+ create_table :addresses do |t|
+ t.binary :encrypted_street
+ t.binary :encrypted_street_iv
+ t.binary :encrypted_zipcode
+ t.string :mode
+ end
+ end
+end
+
+ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
+
+if ::ActiveRecord::VERSION::STRING > "4.0"
+ module Rack
+ module Test
+ class UploadedFile; end
+ end
+ end
+
+ require 'action_controller/metal/strong_parameters'
+end
+
+class Person < ActiveRecord::Base
+ self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
+ attr_encrypted :email, key: SECRET_KEY
+ attr_encrypted :credentials, key: Proc.new { |user| Encryptor.encrypt(value: user.salt, key: SECRET_KEY, iv: user.key_iv) }, marshal: true
+
+ after_initialize :initialize_salt_and_credentials
+
+ protected
+
+ def initialize_salt_and_credentials
+ self.key_iv ||= SecureRandom.random_bytes(12)
+ self.salt ||= Digest::SHA256.hexdigest((Time.now.to_i * rand(1000)).to_s)[0..15]
+ self.credentials ||= { username: 'example', password: 'test' }
+ end
+end
+
+class PersonWithValidation < Person
+ validates_presence_of :email
+end
+
+class PersonWithProcMode < Person
+ attr_encrypted :email, key: SECRET_KEY, mode: Proc.new { :per_attribute_iv_and_salt }
+ attr_encrypted :credentials, key: SECRET_KEY, mode: Proc.new { :single_iv_and_salt }, insecure_mode: true
+end
+
+class Account < ActiveRecord::Base
+ ACCOUNT_ENCRYPTION_KEY = SecureRandom.urlsafe_base64(24)
+ attr_encrypted :password, key: :password_encryption_key
+
+ def encrypting?(attr)
+ encrypted_attributes[attr][:operation] == :encrypting
+ end
+
+ def password_encryption_key
+ if encrypting?(:password)
+ self.key = ACCOUNT_ENCRYPTION_KEY
+ else
+ self.key
+ end
+ end
+end
+
+class PersonWithSerialization < ActiveRecord::Base
+ self.table_name = 'people'
+ attr_encrypted :email, key: SECRET_KEY
+ serialize :password
+end
+
+class UserWithProtectedAttribute < ActiveRecord::Base
+ self.table_name = 'users'
+ attr_encrypted :password, key: SECRET_KEY
+ attr_protected :is_admin if ::ActiveRecord::VERSION::STRING < "4.0"
+end
+
+class PersonUsingAlias < ActiveRecord::Base
+ self.table_name = 'people'
+ attr_encryptor :email, key: SECRET_KEY
+end
+
+class PrimeMinister < ActiveRecord::Base
+ attr_encrypted :name, marshal: true, key: SECRET_KEY
+end
+
+class Address < ActiveRecord::Base
+ self.attr_encrypted_options[:marshal] = false
+ self.attr_encrypted_options[:encode] = false
+ attr_encrypted :street, encode_iv: false, key: SECRET_KEY
+ attr_encrypted :zipcode, key: SECRET_KEY, mode: Proc.new { |address| address.mode.to_sym }, insecure_mode: true
+end
+
+class ActiveRecordTest < Minitest::Test
+
+ def setup
+ drop_all_tables
+ create_tables
+ end
+
+ def test_should_encrypt_email
+ @person = Person.create(email: 'test@example.com')
+ refute_nil @person.encrypted_email
+ refute_equal @person.email, @person.encrypted_email
+ assert_equal @person.email, Person.first.email
+ end
+
+ def test_should_marshal_and_encrypt_credentials
+ @person = Person.create
+ refute_nil @person.encrypted_credentials
+ refute_equal @person.credentials, @person.encrypted_credentials
+ assert_equal @person.credentials, Person.first.credentials
+ end
+
+ def test_should_encode_by_default
+ assert Person.attr_encrypted_options[:encode]
+ end
+
+ def test_should_validate_presence_of_email
+ @person = PersonWithValidation.new
+ assert !@person.valid?
+ assert !@person.errors[:email].empty? || @person.errors.on(:email)
+ end
+
+ def test_should_encrypt_decrypt_with_iv
+ @person = Person.create(email: 'test@example.com')
+ @person2 = Person.find(@person.id)
+ refute_nil @person2.encrypted_email_iv
+ assert_equal 'test@example.com', @person2.email
+ end
+
+ def test_should_ensure_attributes_can_be_deserialized
+ @person = PersonWithSerialization.new(email: 'test@example.com', password: %w(an array of strings))
+ @person.save
+ assert_equal @person.password, %w(an array of strings)
+ end
+
+ def test_should_create_an_account_regardless_of_arguments_order
+ Account.create!(key: SECRET_KEY, password: "password")
+ Account.create!(password: "password" , key: SECRET_KEY)
+ end
+
+ def test_should_set_attributes_regardless_of_arguments_order
+ # minitest does not implement `assert_nothing_raised` https://github.com/seattlerb/minitest/issues/112
+ Account.new.attributes = { password: "password", key: SECRET_KEY }
+ end
+
+ def test_should_create_changed_predicate
+ person = Person.create!(email: 'test@example.com')
+ refute person.email_changed?
+ person.email = 'test@example.com'
+ refute person.email_changed?
+ person.email = nil
+ assert person.email_changed?
+ person.email = 'test2@example.com'
+ assert person.email_changed?
+ end
+
+ # PENDING - this test is failing because attr_encrypted does not adhere to the
+ # interface contract for ActiveModel::Dirty as of ActiveRecord 6.1:
+ # https://devdocs.io/rails~6.1/activemodel/dirty
+ def pending_test_should_create_was_predicate
+ original_email = 'test@example.com'
+ person = Person.create!(email: original_email)
+ assert_equal original_email, person.email_was
+ person.email = 'test2@example.com'
+ assert_equal original_email, person.email_was
+ old_pm_name = "Winston Churchill"
+ pm = PrimeMinister.create!(name: old_pm_name)
+ assert_equal old_pm_name, pm.name_was
+ old_zipcode = "90210"
+ address = Address.create!(zipcode: old_zipcode, mode: "single_iv_and_salt")
+ assert_equal old_zipcode, address.zipcode_was
+ end
+
+ # PENDING - this test is failing because attr_encrypted does not adhere to the
+ # interface contract for ActiveModel::Dirty as of ActiveRecord 6.1:
+ # https://devdocs.io/rails~6.1/activemodel/dirty
+ def pending_test_attribute_was_works_when_options_for_old_encrypted_value_are_different_than_options_for_new_encrypted_value
+ pw = 'password'
+ crypto_key = SecureRandom.urlsafe_base64(24)
+ old_iv = SecureRandom.random_bytes(12)
+ account = Account.create
+ encrypted_value = Encryptor.encrypt(value: pw, iv: old_iv, key: crypto_key)
+ Account.where(id: account.id).update_all(key: crypto_key, encrypted_password_iv: [old_iv].pack('m'), encrypted_password: [encrypted_value].pack('m'))
+ account = Account.find(account.id)
+ assert_equal pw, account.password
+ account.password = pw.reverse
+ assert_equal pw, account.password_was
+ account.save
+ account.reload
+ assert_equal Account::ACCOUNT_ENCRYPTION_KEY, account.key
+ assert_equal pw.reverse, account.password
+ end
+
+ if ::ActiveRecord::VERSION::STRING > "4.0"
+ def test_should_assign_attributes
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
+ @user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true).permit(:login)
+ assert_equal 'modified', @user.login
+ end
+
+ def test_should_not_assign_protected_attributes
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
+ @user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true).permit(:login)
+ assert !@user.is_admin?
+ end
+
+ def test_should_raise_exception_if_not_permitted
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
+ assert_raises ActiveModel::ForbiddenAttributesError do
+ @user.attributes = ActionController::Parameters.new(login: 'modified', is_admin: true)
+ end
+ end
+
+ def test_should_raise_exception_on_init_if_not_permitted
+ assert_raises ActiveModel::ForbiddenAttributesError do
+ @user = UserWithProtectedAttribute.new ActionController::Parameters.new(login: 'modified', is_admin: true)
+ end
+ end
+ else
+ def test_should_assign_attributes
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
+ @user.attributes = { login: 'modified', is_admin: true }
+ assert_equal 'modified', @user.login
+ end
+
+ def test_should_not_assign_protected_attributes
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
+ @user.attributes = { login: 'modified', is_admin: true }
+ assert !@user.is_admin?
+ end
+
+ def test_should_assign_protected_attributes
+ @user = UserWithProtectedAttribute.new(login: 'login', is_admin: false)
+ if ::ActiveRecord::VERSION::STRING > "3.1"
+ @user.send(:assign_attributes, { login: 'modified', is_admin: true }, without_protection: true)
+ else
+ @user.send(:attributes=, { login: 'modified', is_admin: true }, false)
+ end
+ assert @user.is_admin?
+ end
+ end
+
+ def test_should_allow_assignment_of_nil_attributes
+ @person = Person.new
+ assert_nil(@person.attributes = nil)
+ end
+
+ def test_should_allow_proc_based_mode
+ @person = PersonWithProcMode.create(email: 'test@example.com', credentials: 'password123')
+
+ # Email is :per_attribute_iv_and_salt
+ assert_equal @person.class.encrypted_attributes[:email][:mode].class, Proc
+ assert_equal @person.class.encrypted_attributes[:email][:mode].call, :per_attribute_iv_and_salt
+ refute_nil @person.encrypted_email_salt
+ refute_nil @person.encrypted_email_iv
+
+ # Credentials is :single_iv_and_salt
+ assert_equal @person.class.encrypted_attributes[:credentials][:mode].class, Proc
+ assert_equal @person.class.encrypted_attributes[:credentials][:mode].call, :single_iv_and_salt
+ assert_nil @person.encrypted_credentials_salt
+ assert_nil @person.encrypted_credentials_iv
+ end
+
+ if ::ActiveRecord::VERSION::STRING > "3.1"
+ def test_should_allow_assign_attributes_with_nil
+ @person = Person.new
+ assert_nil(@person.assign_attributes nil)
+ end
+ end
+
+ def test_that_alias_encrypts_column
+ user = PersonUsingAlias.new
+ user.email = 'test@example.com'
+ user.save
+
+ refute_nil user.encrypted_email
+ refute_equal user.email, user.encrypted_email
+ assert_equal user.email, PersonUsingAlias.first.email
+ end
+
+ # See https://github.com/attr-encrypted/attr_encrypted/issues/68
+ def test_should_invalidate_virtual_attributes_on_reload
+ old_pm_name = 'Winston Churchill'
+ new_pm_name = 'Neville Chamberlain'
+ pm = PrimeMinister.create!(name: old_pm_name)
+ assert_equal old_pm_name, pm.name
+ pm.name = new_pm_name
+ assert_equal new_pm_name, pm.name
+
+ result = pm.reload
+ assert_equal pm, result
+ assert_equal old_pm_name, pm.name
+ end
+
+ def test_should_save_encrypted_data_as_binary
+ street = '123 Elm'
+ address = Address.create!(street: street)
+ refute_equal address.encrypted_street, street
+ assert_equal Address.first.street, street
+ end
+
+ def test_should_evaluate_proc_based_mode
+ street = '123 Elm'
+ zipcode = '12345'
+ address = Address.create(street: street, zipcode: zipcode, mode: :single_iv_and_salt)
+ address.reload
+ refute_equal address.encrypted_zipcode, zipcode
+ assert_equal address.zipcode, zipcode
+ end
+end
diff --git a/vendor/gems/attr_encrypted/test/attr_encrypted_test.rb b/vendor/gems/attr_encrypted/test/attr_encrypted_test.rb
new file mode 100644
index 00000000000..84cb130aa50
--- /dev/null
+++ b/vendor/gems/attr_encrypted/test/attr_encrypted_test.rb
@@ -0,0 +1,490 @@
+# frozen_string_literal: true
+
+# encoding: UTF-8
+require_relative 'test_helper'
+
+class SillyEncryptor
+ def self.silly_encrypt(options)
+ (options[:value] + options[:some_arg]).reverse
+ end
+
+ def self.silly_decrypt(options)
+ options[:value].reverse.gsub(/#{options[:some_arg]}$/, '')
+ end
+end
+
+class User
+ extend AttrEncrypted
+ self.attr_encrypted_options[:key] = Proc.new { |user| SECRET_KEY } # default key
+ self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
+
+ attr_encrypted :email, :without_encoding, :key => SECRET_KEY
+ attr_encrypted :password, :prefix => 'crypted_', :suffix => '_test'
+ attr_encrypted :ssn, :key => :secret_key, :attribute => 'ssn_encrypted'
+ attr_encrypted :credit_card, :encryptor => SillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt, :some_arg => 'test'
+ attr_encrypted :with_encoding, :key => SECRET_KEY, :encode => true
+ attr_encrypted :with_custom_encoding, :key => SECRET_KEY, :encode => 'm'
+ attr_encrypted :with_marshaling, :key => SECRET_KEY, :marshal => true
+ attr_encrypted :with_true_if, :key => SECRET_KEY, :if => true, mode: :per_attribute_iv_and_salt
+ attr_encrypted :with_false_if, :key => SECRET_KEY, :if => false, mode: :per_attribute_iv_and_salt
+ attr_encrypted :with_true_unless, :key => SECRET_KEY, :unless => true, mode: :per_attribute_iv_and_salt
+ attr_encrypted :with_false_unless, :key => SECRET_KEY, :unless => false, mode: :per_attribute_iv_and_salt
+ attr_encrypted :with_if_changed, :key => SECRET_KEY, :if => :should_encrypt
+ attr_encrypted :with_allow_empty_value, key: SECRET_KEY, allow_empty_value: true, marshal: true
+
+ attr_encryptor :aliased, :key => SECRET_KEY
+
+ attr_accessor :salt
+ attr_accessor :should_encrypt
+
+ def initialize(email: nil)
+ self.email = email
+ self.salt = Time.now.to_i.to_s
+ self.should_encrypt = true
+ end
+
+ private
+ def secret_key
+ SECRET_KEY
+ end
+end
+
+class Admin < User
+ attr_encrypted :testing
+end
+
+class SomeOtherClass
+ extend AttrEncrypted
+ def self.call(object)
+ object.class
+ end
+end
+
+class YetAnotherClass
+ extend AttrEncrypted
+ self.attr_encrypted_options[:encode_iv] = false
+
+ attr_encrypted :email, :key => SECRET_KEY
+ attr_encrypted :phone_number, :key => SECRET_KEY, mode: Proc.new { |thing| thing.mode }, encode_iv: Proc.new { |thing| thing.encode_iv }, encode_salt: Proc.new { |thing| thing.encode_salt }
+
+ def initialize(email: nil, encode_iv: 'm', encode_salt: 'm', mode: :per_attribute_iv_and_salt)
+ self.email = email
+ @encode_iv = encode_iv
+ @encode_salt = encode_salt
+ @mode = mode
+ end
+
+ attr_reader :encode_iv, :encode_salt, :mode
+end
+
+class AttrEncryptedTest < Minitest::Test
+ def setup
+ @iv = SecureRandom.random_bytes(12)
+ end
+
+ def test_should_store_email_in_encrypted_attributes
+ assert User.encrypted_attributes.include?(:email)
+ end
+
+ def test_should_not_store_salt_in_encrypted_attributes
+ refute User.encrypted_attributes.include?(:salt)
+ end
+
+ def test_attr_encrypted_should_return_true_for_email
+ assert User.attr_encrypted?('email')
+ end
+
+ def test_attr_encrypted_should_not_use_the_same_attribute_name_for_two_attributes_in_the_same_line
+ refute_equal User.encrypted_attributes[:email][:attribute], User.encrypted_attributes[:without_encoding][:attribute]
+ end
+
+ def test_attr_encrypted_should_return_false_for_salt
+ assert !User.attr_encrypted?('salt')
+ end
+
+ def test_should_generate_an_encrypted_attribute
+ assert User.new.respond_to?(:encrypted_email)
+ end
+
+ def test_should_generate_an_encrypted_attribute_with_a_prefix_and_suffix
+ assert User.new.respond_to?(:crypted_password_test)
+ end
+
+ def test_should_generate_an_encrypted_attribute_with_the_attribute_option
+ assert User.new.respond_to?(:ssn_encrypted)
+ end
+
+ def test_should_not_encrypt_nil_value
+ assert_nil User.encrypt_email(nil, iv: @iv)
+ end
+
+ def test_should_not_encrypt_empty_string_by_default
+ assert_equal '', User.encrypt_email('', iv: @iv)
+ end
+
+ def test_should_encrypt_email
+ refute_nil User.encrypt_email('test@example.com', iv: @iv)
+ refute_equal 'test@example.com', User.encrypt_email('test@example.com', iv: @iv)
+ end
+
+ def test_should_encrypt_email_when_modifying_the_attr_writer
+ @user = User.new
+ assert_nil @user.encrypted_email
+ @user.email = 'test@example.com'
+ refute_nil @user.encrypted_email
+ iv = @user.encrypted_email_iv.unpack('m').first
+ salt = @user.encrypted_email_salt[1..-1].unpack('m').first
+ assert_equal User.encrypt_email('test@example.com', iv: iv, salt: salt), @user.encrypted_email
+ end
+
+ def test_should_not_decrypt_nil_value
+ assert_nil User.decrypt_email(nil, iv: @iv)
+ end
+
+ def test_should_not_decrypt_empty_string
+ assert_equal '', User.decrypt_email('', iv: @iv)
+ end
+
+ def test_should_decrypt_email
+ encrypted_email = User.encrypt_email('test@example.com', iv: @iv)
+ refute_equal 'test@test.com', encrypted_email
+ assert_equal 'test@example.com', User.decrypt_email(encrypted_email, iv: @iv)
+ end
+
+ def test_should_decrypt_email_when_reading
+ @user = User.new
+ assert_nil @user.email
+ options = @user.encrypted_attributes[:email]
+ iv = @user.send(:generate_iv, options[:algorithm])
+ encoded_iv = [iv].pack(options[:encode_iv])
+ salt = SecureRandom.random_bytes
+ encoded_salt = @user.send(:prefix_and_encode_salt, salt, options[:encode_salt])
+ @user.encrypted_email = User.encrypt_email('test@example.com', iv: iv, salt: salt)
+ @user.encrypted_email_iv = encoded_iv
+ @user.encrypted_email_salt = encoded_salt
+ assert_equal 'test@example.com', @user.email
+ end
+
+ def test_should_encrypt_with_encoding
+ assert_equal User.encrypt_with_encoding('test', iv: @iv), [User.encrypt_without_encoding('test', iv: @iv)].pack('m')
+ end
+
+ def test_should_decrypt_with_encoding
+ encrypted = User.encrypt_with_encoding('test', iv: @iv)
+ assert_equal 'test', User.decrypt_with_encoding(encrypted, iv: @iv)
+ assert_equal User.decrypt_with_encoding(encrypted, iv: @iv), User.decrypt_without_encoding(encrypted.unpack('m').first, iv: @iv)
+ end
+
+ def test_should_encrypt_with_custom_encoding
+ assert_equal User.encrypt_with_encoding('test', iv: @iv), [User.encrypt_without_encoding('test', iv: @iv)].pack('m')
+ end
+
+ def test_should_decrypt_with_custom_encoding
+ encrypted = User.encrypt_with_encoding('test', iv: @iv)
+ assert_equal 'test', User.decrypt_with_encoding(encrypted, iv: @iv)
+ assert_equal User.decrypt_with_encoding(encrypted, iv: @iv), User.decrypt_without_encoding(encrypted.unpack('m').first, iv: @iv)
+ end
+
+ def test_should_encrypt_with_marshaling
+ @user = User.new
+ @user.with_marshaling = [1, 2, 3]
+ refute_nil @user.encrypted_with_marshaling
+ end
+
+ def test_should_use_custom_encryptor_and_crypt_method_names_and_arguments
+ assert_equal SillyEncryptor.silly_encrypt(:value => 'testing', :some_arg => 'test'), User.encrypt_credit_card('testing')
+ end
+
+ def test_should_evaluate_a_key_passed_as_a_symbol
+ @user = User.new
+ assert_nil @user.ssn_encrypted
+ @user.ssn = 'testing'
+ refute_nil @user.ssn_encrypted
+ encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.ssn_encrypted_iv.unpack("m").first, :salt => @user.ssn_encrypted_salt.unpack("m").first )
+ assert_equal encrypted, @user.ssn_encrypted
+ end
+
+ def test_should_evaluate_a_key_passed_as_a_proc
+ @user = User.new
+ assert_nil @user.crypted_password_test
+ @user.password = 'testing'
+ refute_nil @user.crypted_password_test
+ encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.crypted_password_test_iv.unpack("m").first, :salt => @user.crypted_password_test_salt.unpack("m").first)
+ assert_equal encrypted, @user.crypted_password_test
+ end
+
+ def test_should_use_options_found_in_the_attr_encrypted_options_attribute
+ @user = User.new
+ assert_nil @user.crypted_password_test
+ @user.password = 'testing'
+ refute_nil @user.crypted_password_test
+ encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.crypted_password_test_iv.unpack("m").first, :salt => @user.crypted_password_test_salt.unpack("m").first)
+ assert_equal encrypted, @user.crypted_password_test
+ end
+
+ def test_should_inherit_encrypted_attributes
+ assert_equal [User.encrypted_attributes.keys, :testing].flatten.collect { |key| key.to_s }.sort, Admin.encrypted_attributes.keys.collect { |key| key.to_s }.sort
+ end
+
+ def test_should_inherit_attr_encrypted_options
+ assert !User.attr_encrypted_options.empty?
+ assert_equal User.attr_encrypted_options, Admin.attr_encrypted_options
+ end
+
+ def test_should_not_inherit_unrelated_attributes
+ assert SomeOtherClass.attr_encrypted_options.empty?
+ assert SomeOtherClass.encrypted_attributes.empty?
+ end
+
+ def test_should_evaluate_a_symbol_option
+ assert_equal SomeOtherClass, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, :class)
+ end
+
+ def test_should_evaluate_a_proc_option
+ assert_equal SomeOtherClass, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, proc { |object| object.class })
+ end
+
+ def test_should_evaluate_a_lambda_option
+ assert_equal SomeOtherClass, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, lambda { |object| object.class })
+ end
+
+ def test_should_evaluate_a_method_option
+ assert_equal SomeOtherClass, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, SomeOtherClass.method(:call))
+ end
+
+ def test_should_return_a_string_option
+ class_string = 'SomeOtherClass'
+ assert_equal class_string, SomeOtherClass.new.send(:evaluate_attr_encrypted_option, class_string)
+ end
+
+ def test_should_encrypt_with_true_if
+ @user = User.new
+ assert_nil @user.encrypted_with_true_if
+ @user.with_true_if = 'testing'
+ refute_nil @user.encrypted_with_true_if
+ encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.encrypted_with_true_if_iv.unpack("m").first, :salt => @user.encrypted_with_true_if_salt.unpack("m").first)
+ assert_equal encrypted, @user.encrypted_with_true_if
+ end
+
+ def test_should_not_encrypt_with_false_if
+ @user = User.new
+ assert_nil @user.encrypted_with_false_if
+ @user.with_false_if = 'testing'
+ refute_nil @user.encrypted_with_false_if
+ assert_equal 'testing', @user.encrypted_with_false_if
+ end
+
+ def test_should_encrypt_with_false_unless
+ @user = User.new
+ assert_nil @user.encrypted_with_false_unless
+ @user.with_false_unless = 'testing'
+ refute_nil @user.encrypted_with_false_unless
+ encrypted = Encryptor.encrypt(:value => 'testing', :key => SECRET_KEY, :iv => @user.encrypted_with_false_unless_iv.unpack("m").first, :salt => @user.encrypted_with_false_unless_salt.unpack("m").first)
+ assert_equal encrypted, @user.encrypted_with_false_unless
+ end
+
+ def test_should_not_encrypt_with_true_unless
+ @user = User.new
+ assert_nil @user.encrypted_with_true_unless
+ @user.with_true_unless = 'testing'
+ refute_nil @user.encrypted_with_true_unless
+ assert_equal 'testing', @user.encrypted_with_true_unless
+ end
+
+ def test_should_encrypt_empty_with_truthy_allow_empty_value_option
+ @user = User.new
+ assert_nil @user.encrypted_with_allow_empty_value
+ @user.with_allow_empty_value = ''
+ refute_nil @user.encrypted_with_allow_empty_value
+ assert_equal '', @user.with_allow_empty_value
+ @user = User.new
+ @user.with_allow_empty_value = nil
+ refute_nil @user.encrypted_with_allow_empty_value
+ assert_nil @user.with_allow_empty_value
+ end
+
+ def test_should_work_with_aliased_attr_encryptor
+ assert User.encrypted_attributes.include?(:aliased)
+ end
+
+ def test_should_always_reset_options
+ @user = User.new
+ @user.with_if_changed = "encrypt_stuff"
+
+ @user = User.new
+ @user.should_encrypt = false
+ @user.with_if_changed = "not_encrypted_stuff"
+ assert_equal "not_encrypted_stuff", @user.with_if_changed
+ assert_equal "not_encrypted_stuff", @user.encrypted_with_if_changed
+ end
+
+ def test_should_cast_values_as_strings_before_encrypting
+ string_encrypted_email = User.encrypt_email('3', iv: @iv)
+ assert_equal string_encrypted_email, User.encrypt_email(3, iv: @iv)
+ assert_equal '3', User.decrypt_email(string_encrypted_email, iv: @iv)
+ end
+
+ def test_should_create_query_accessor
+ @user = User.new
+ assert !@user.email?
+ @user.email = ''
+ assert !@user.email?
+ @user.email = 'test@example.com'
+ assert @user.email?
+ end
+
+ def test_should_vary_iv_per_attribute
+ @user = User.new
+ @user.email = 'email@example.com'
+ @user.password = 'p455w0rd'
+ refute_equal @user.encrypted_email_iv, @user.crypted_password_test_iv
+ end
+
+ def test_should_generate_iv_per_attribute_by_default
+ thing = YetAnotherClass.new(email: 'thing@thing.com')
+ refute_nil thing.encrypted_email_iv
+ end
+
+ def test_should_vary_iv_per_instance
+ @user1 = User.new
+ @user1.email = 'email@example.com'
+ @user2 = User.new
+ @user2.email = 'email@example.com'
+ refute_equal @user1.encrypted_email_iv, @user2.encrypted_email_iv
+ refute_equal @user1.encrypted_email, @user2.encrypted_email
+ end
+
+ def test_should_vary_salt_per_attribute
+ @user = User.new
+ @user.email = 'email@example.com'
+ @user.password = 'p455w0rd'
+ refute_equal @user.encrypted_email_salt, @user.crypted_password_test_salt
+ end
+
+ def test_should_vary_salt_per_instance
+ @user1 = User.new
+ @user1.email = 'email@example.com'
+ @user2 = User.new
+ @user2.email = 'email@example.com'
+ refute_equal @user1.encrypted_email_salt, @user2.encrypted_email_salt
+ end
+
+ def test_should_not_generate_salt_per_attribute_by_default
+ thing = YetAnotherClass.new(email: 'thing@thing.com')
+ assert_nil thing.encrypted_email_salt
+ end
+
+ def test_should_decrypt_second_record
+ @user1 = User.new
+ @user1.email = 'test@example.com'
+
+ @user2 = User.new
+ @user2.email = 'test@example.com'
+
+ assert_equal 'test@example.com', @user1.decrypt(:email, @user1.encrypted_email)
+ end
+
+ def test_should_specify_the_default_algorithm
+ assert YetAnotherClass.encrypted_attributes[:email][:algorithm]
+ assert_equal YetAnotherClass.encrypted_attributes[:email][:algorithm], 'aes-256-gcm'
+ end
+
+ def test_should_not_encode_iv_when_encode_iv_is_false
+ email = 'thing@thing.com'
+ thing = YetAnotherClass.new(email: email)
+ refute thing.encrypted_email_iv =~ base64_encoding_regex
+ assert_equal thing.email, email
+ end
+
+ def test_should_base64_encode_iv_by_default
+ phone_number = '555-555-5555'
+ thing = YetAnotherClass.new
+ thing.phone_number = phone_number
+ assert thing.encrypted_phone_number_iv =~ base64_encoding_regex
+ assert_equal thing.phone_number, phone_number
+ end
+
+ def test_should_generate_unique_iv_for_every_encrypt_operation
+ user = User.new
+ user.email = 'initial_value@test.com'
+ original_iv = user.encrypted_email_iv
+ user.email = 'revised_value@test.com'
+ refute_equal original_iv, user.encrypted_email_iv
+ end
+
+ def test_should_not_generate_iv_for_attribute_when_if_option_is_false
+ user = User.new
+ user.with_false_if = 'derp'
+ assert_nil user.encrypted_with_false_if_iv
+ end
+
+ def test_should_generate_iv_for_attribute_when_if_option_is_true
+ user = User.new
+ user.with_true_if = 'derp'
+ refute_nil user.encrypted_with_true_if_iv
+
+ user.with_true_if = Object.new
+ refute_nil user.encrypted_with_true_if_iv
+ end
+
+ def test_should_not_generate_salt_for_attribute_when_if_option_is_false
+ user = User.new
+ user.with_false_if = 'derp'
+ assert_nil user.encrypted_with_false_if_salt
+ end
+
+ def test_should_generate_salt_for_attribute_when_if_option_is_true
+ user = User.new
+ user.with_true_if = 'derp'
+ refute_nil user.encrypted_with_true_if_salt
+ end
+
+ def test_should_generate_iv_for_attribute_when_unless_option_is_false
+ user = User.new
+ user.with_false_unless = 'derp'
+ refute_nil user.encrypted_with_false_unless_iv
+ end
+
+ def test_should_not_generate_iv_for_attribute_when_unless_option_is_true
+ user = User.new
+ user.with_true_unless = 'derp'
+ assert_nil user.encrypted_with_true_unless_iv
+ end
+
+ def test_should_generate_salt_for_attribute_when_unless_option_is_false
+ user = User.new
+ user.with_false_unless = 'derp'
+ refute_nil user.encrypted_with_false_unless_salt
+ end
+
+ def test_should_not_generate_salt_for_attribute_when_unless_option_is_true
+ user = User.new
+ user.with_true_unless = 'derp'
+ assert_nil user.encrypted_with_true_unless_salt
+ end
+
+ def test_should_not_by_default_generate_iv_when_attribute_is_empty
+ user = User.new
+ user.with_true_if = nil
+ assert_nil user.encrypted_with_true_if_iv
+ end
+
+ def test_encrypted_attributes_state_is_not_shared
+ user = User.new
+ user.ssn = '123456789'
+
+ another_user = User.new
+
+ assert_equal :encrypting, user.encrypted_attributes[:ssn][:operation]
+ assert_nil another_user.encrypted_attributes[:ssn][:operation]
+ end
+
+ def test_should_not_by_default_generate_key_when_attribute_is_empty
+ user = User.new
+ calls = 0
+ user.stub(:secret_key, lambda { calls += 1; SECRET_KEY }) do
+ user.ssn
+ end
+ assert_equal 0, calls
+ end
+end
diff --git a/vendor/gems/attr_encrypted/test/compatibility_test.rb b/vendor/gems/attr_encrypted/test/compatibility_test.rb
new file mode 100644
index 00000000000..7585895e16e
--- /dev/null
+++ b/vendor/gems/attr_encrypted/test/compatibility_test.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+# -*- encoding: utf-8 -*-
+require_relative 'test_helper'
+
+# Test to ensure that existing representations in database do not break on
+# migrating to new versions of this gem. This ensures that future versions of
+# this gem will retain backwards compatibility with data generated by earlier
+# versions.
+class CompatibilityTest < Minitest::Test
+ class NonmarshallingPet < ActiveRecord::Base
+ PET_NICKNAME_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-nickname-salt')
+ PET_NICKNAME_KEY = 'my-really-really-secret-pet-nickname-key'
+ PET_BIRTHDATE_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-birthdate-salt')
+ PET_BIRTHDATE_KEY = 'my-really-really-secret-pet-birthdate-key'
+
+ self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
+ self.attr_encrypted_options[:algorithm] = 'aes-256-cbc'
+ self.attr_encrypted_options[:insecure_mode] = true
+
+ attr_encrypted :nickname,
+ :key => proc { Encryptor.encrypt(:value => PET_NICKNAME_SALT, :key => PET_NICKNAME_KEY, insecure_mode: true, algorithm: 'aes-256-cbc') }
+ attr_encrypted :birthdate,
+ :key => proc { Encryptor.encrypt(:value => PET_BIRTHDATE_SALT, :key => PET_BIRTHDATE_KEY, insecure_mode: true, algorithm: 'aes-256-cbc') }
+ end
+
+ class MarshallingPet < ActiveRecord::Base
+ PET_NICKNAME_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-nickname-salt')
+ PET_NICKNAME_KEY = 'my-really-really-secret-pet-nickname-key'
+ PET_BIRTHDATE_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-birthdate-salt')
+ PET_BIRTHDATE_KEY = 'my-really-really-secret-pet-birthdate-key'
+
+ self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
+ self.attr_encrypted_options[:algorithm] = 'aes-256-cbc'
+ self.attr_encrypted_options[:insecure_mode] = true
+
+ attr_encrypted :nickname,
+ :key => proc { Encryptor.encrypt(:value => PET_NICKNAME_SALT, :key => PET_NICKNAME_KEY, insecure_mode: true, algorithm: 'aes-256-cbc') },
+ :marshal => true
+ attr_encrypted :birthdate,
+ :key => proc { Encryptor.encrypt(:value => PET_BIRTHDATE_SALT, :key => PET_BIRTHDATE_KEY, insecure_mode: true, algorithm: 'aes-256-cbc') },
+ :marshal => true
+ end
+
+ def setup
+ drop_all_tables
+ create_tables
+ end
+
+ def test_nonmarshalling_backwards_compatibility
+ pet = NonmarshallingPet.create!(
+ :name => 'Fido',
+ :encrypted_nickname => 'E4lJTxFG/EfkfPg5MpnriQ==',
+ :encrypted_nickname_iv => 'z4Q8deE4h7f6S8NNZcbPNg==',
+ :encrypted_nickname_salt => 'adcd833001a873db',
+ :encrypted_birthdate => '6uKEAiFVdJw+N5El+U6Gow==',
+ :encrypted_birthdate_iv => 'zxtc1XPssL4s2HwA69nORQ==',
+ :encrypted_birthdate_salt => '4f879270045eaad7'
+ )
+
+ assert_equal 'Fido', pet.name
+ assert_equal 'Fido the Dog', pet.nickname
+ assert_equal '2011-07-09', pet.birthdate
+ end
+
+ def test_marshalling_backwards_compatibility
+ pet = MarshallingPet.create!(
+ :name => 'Fido',
+ :encrypted_nickname => 'EsQScJYkPw80vVGvKWkE37Px99HHpXPFjoEPTNa4rbs=',
+ :encrypted_nickname_iv => 'fNq1OZcGvty4KfcvGTcFSw==',
+ :encrypted_nickname_salt => '733b459b7d34c217',
+ :encrypted_birthdate => '+VUlKQGfNWkOgCwI4hv+3qlGIwh9h6cJ/ranJlaxvU+xxQdL3H3cOzTcI2rkYkdR',
+ :encrypted_birthdate_iv => 'Ka+zF/SwEYZKwVa24lvFfA==',
+ :encrypted_birthdate_salt => 'd5e892d5bbd81566'
+ )
+
+ assert_equal 'Fido', pet.name
+ assert_equal 'Mummy\'s little helper', pet.nickname
+
+ assert_equal Date.new(2011, 7, 9), pet.birthdate
+ end
+
+ private
+
+ def create_tables
+ ActiveRecord::Schema.define(:version => 1) do
+ create_table :nonmarshalling_pets do |t|
+ t.string :name
+ t.string :encrypted_nickname
+ t.string :encrypted_nickname_iv
+ t.string :encrypted_nickname_salt
+ t.string :encrypted_birthdate
+ t.string :encrypted_birthdate_iv
+ t.string :encrypted_birthdate_salt
+ end
+ create_table :marshalling_pets do |t|
+ t.string :name
+ t.string :encrypted_nickname
+ t.string :encrypted_nickname_iv
+ t.string :encrypted_nickname_salt
+ t.string :encrypted_birthdate
+ t.string :encrypted_birthdate_iv
+ t.string :encrypted_birthdate_salt
+ end
+ end
+ end
+end
+
+ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
diff --git a/vendor/gems/attr_encrypted/test/data_mapper_test.rb b/vendor/gems/attr_encrypted/test/data_mapper_test.rb
new file mode 100644
index 00000000000..3fb284a6ca4
--- /dev/null
+++ b/vendor/gems/attr_encrypted/test/data_mapper_test.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require_relative 'test_helper'
+
+DataMapper.setup(:default, 'sqlite3::memory:')
+
+class Client
+ include DataMapper::Resource
+
+ property :id, Serial
+ property :encrypted_email, String
+ property :encrypted_email_iv, String
+ property :encrypted_email_salt, String
+
+ property :encrypted_credentials, Text
+ property :encrypted_credentials_iv, Text
+ property :encrypted_credentials_salt, Text
+
+ self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
+
+ attr_encrypted :email, :key => SECRET_KEY
+ attr_encrypted :credentials, :key => SECRET_KEY, :marshal => true
+
+ def initialize(attrs = {})
+ super attrs
+ self.credentials ||= { :username => 'example', :password => 'test' }
+ end
+end
+
+DataMapper.auto_migrate!
+
+class DataMapperTest < Minitest::Test
+
+ def setup
+ Client.all.each(&:destroy)
+ end
+
+ def test_should_encrypt_email
+ @client = Client.new :email => 'test@example.com'
+ assert @client.save
+ refute_nil @client.encrypted_email
+ refute_equal @client.email, @client.encrypted_email
+ assert_equal @client.email, Client.first.email
+ end
+
+ def test_should_marshal_and_encrypt_credentials
+ @client = Client.new
+ assert @client.save
+ refute_nil @client.encrypted_credentials
+ refute_equal @client.credentials, @client.encrypted_credentials
+ assert_equal @client.credentials, Client.first.credentials
+ assert Client.first.credentials.is_a?(Hash)
+ end
+
+ def test_should_encode_by_default
+ assert Client.attr_encrypted_options[:encode]
+ end
+
+end
diff --git a/vendor/gems/attr_encrypted/test/legacy_active_record_test.rb b/vendor/gems/attr_encrypted/test/legacy_active_record_test.rb
new file mode 100644
index 00000000000..802318570fa
--- /dev/null
+++ b/vendor/gems/attr_encrypted/test/legacy_active_record_test.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+# -*- encoding: utf-8 -*-
+require_relative 'test_helper'
+
+ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
+
+def create_people_table
+ ActiveRecord::Schema.define(:version => 1) do
+ create_table :legacy_people do |t|
+ t.string :encrypted_email
+ t.string :password
+ t.string :encrypted_credentials
+ t.string :salt
+ end
+ end
+end
+
+# The table needs to exist before defining the class
+create_people_table
+
+ActiveRecord::MissingAttributeError = ActiveModel::MissingAttributeError unless defined?(ActiveRecord::MissingAttributeError)
+
+class LegacyPerson < ActiveRecord::Base
+ self.attr_encrypted_options[:insecure_mode] = true
+ self.attr_encrypted_options[:algorithm] = 'aes-256-cbc'
+ self.attr_encrypted_options[:mode] = :single_iv_and_salt
+
+ attr_encrypted :email, :key => 'a secret key'
+ attr_encrypted :credentials, :key => Proc.new { |user| Encryptor.encrypt(:value => user.salt, :key => 'some private key', insecure_mode: true, algorithm: 'aes-256-cbc') }, :marshal => true
+
+ ActiveSupport::Deprecation.silenced = true
+ def after_initialize; end
+ ActiveSupport::Deprecation.silenced = false
+
+ after_initialize :initialize_salt_and_credentials
+
+ protected
+
+ def initialize_salt_and_credentials
+ self.salt ||= Digest::SHA256.hexdigest((Time.now.to_i * rand(5)).to_s)
+ self.credentials ||= { :username => 'example', :password => 'test' }
+ rescue ActiveRecord::MissingAttributeError
+ end
+end
+
+class LegacyPersonWithValidation < LegacyPerson
+ validates_presence_of :email
+ validates_uniqueness_of :encrypted_email
+end
+
+class LegacyActiveRecordTest < Minitest::Test
+
+ def setup
+ drop_all_tables
+ create_people_table
+ end
+
+ def test_should_decrypt_with_correct_encoding
+ if defined?(Encoding)
+ @person = LegacyPerson.create :email => 'test@example.com'
+ assert_equal 'UTF-8', LegacyPerson.first.email.encoding.name
+ end
+ end
+
+ def test_should_encrypt_email
+ @person = LegacyPerson.create :email => 'test@example.com'
+ refute_nil @person.encrypted_email
+ refute_equal @person.email, @person.encrypted_email
+ assert_equal @person.email, LegacyPerson.first.email
+ end
+
+ def test_should_marshal_and_encrypt_credentials
+ @person = LegacyPerson.create
+ refute_nil @person.encrypted_credentials
+ refute_equal @person.credentials, @person.encrypted_credentials
+ assert_equal @person.credentials, LegacyPerson.first.credentials
+ end
+
+ def test_should_find_by_email
+ @person = LegacyPerson.create(:email => 'test@example.com')
+ assert_equal @person, LegacyPerson.find_by_email('test@example.com')
+ end
+
+ def test_should_find_by_email_and_password
+ LegacyPerson.create(:email => 'test@example.com', :password => 'invalid')
+ @person = LegacyPerson.create(:email => 'test@example.com', :password => 'test')
+ assert_equal @person, LegacyPerson.find_by_email_and_password('test@example.com', 'test')
+ end
+
+ def test_should_scope_by_email
+ @person = LegacyPerson.create(:email => 'test@example.com')
+ assert_equal @person, LegacyPerson.scoped_by_email('test@example.com').first rescue NoMethodError
+ end
+
+ def test_should_scope_by_email_and_password
+ LegacyPerson.create(:email => 'test@example.com', :password => 'invalid')
+ @person = LegacyPerson.create(:email => 'test@example.com', :password => 'test')
+ assert_equal @person, LegacyPerson.scoped_by_email_and_password('test@example.com', 'test').first rescue NoMethodError
+ end
+
+ def test_should_encode_by_default
+ assert LegacyPerson.attr_encrypted_options[:encode]
+ end
+
+ def test_should_validate_presence_of_email
+ @person = LegacyPersonWithValidation.new
+ assert !@person.valid?
+ assert !@person.errors[:email].empty? || @person.errors.on(:email)
+ end
+
+ def test_should_validate_uniqueness_of_email
+ @person = LegacyPersonWithValidation.new :email => 'test@example.com'
+ assert @person.save
+ @person2 = LegacyPersonWithValidation.new :email => @person.email
+ assert !@person2.valid?
+ assert !@person2.errors[:encrypted_email].empty? || @person2.errors.on(:encrypted_email)
+ end
+
+end
diff --git a/vendor/gems/attr_encrypted/test/legacy_attr_encrypted_test.rb b/vendor/gems/attr_encrypted/test/legacy_attr_encrypted_test.rb
new file mode 100644
index 00000000000..875086d2351
--- /dev/null
+++ b/vendor/gems/attr_encrypted/test/legacy_attr_encrypted_test.rb
@@ -0,0 +1,300 @@
+# frozen_string_literal: true
+
+# -*- encoding: utf-8 -*-
+require_relative 'test_helper'
+
+class LegacySillyEncryptor
+ def self.silly_encrypt(options)
+ (options[:value] + options[:some_arg]).reverse
+ end
+
+ def self.silly_decrypt(options)
+ options[:value].reverse.gsub(/#{options[:some_arg]}$/, '')
+ end
+end
+
+class LegacyUser
+ extend AttrEncrypted
+ self.attr_encrypted_options[:key] = Proc.new { |user| user.class.to_s } # default key
+ self.attr_encrypted_options[:insecure_mode] = true
+ self.attr_encrypted_options[:algorithm] = 'aes-256-cbc'
+ self.attr_encrypted_options[:mode] = :single_iv_and_salt
+
+ attr_encrypted :email, :without_encoding, :key => 'secret key'
+ attr_encrypted :password, :prefix => 'crypted_', :suffix => '_test'
+ attr_encrypted :ssn, :key => :salt, :attribute => 'ssn_encrypted'
+ attr_encrypted :credit_card, :encryptor => LegacySillyEncryptor, :encrypt_method => :silly_encrypt, :decrypt_method => :silly_decrypt, :some_arg => 'test'
+ attr_encrypted :with_encoding, :key => 'secret key', :encode => true
+ attr_encrypted :with_custom_encoding, :key => 'secret key', :encode => 'm'
+ attr_encrypted :with_marshaling, :key => 'secret key', :marshal => true
+ attr_encrypted :with_true_if, :key => 'secret key', :if => true
+ attr_encrypted :with_false_if, :key => 'secret key', :if => false
+ attr_encrypted :with_true_unless, :key => 'secret key', :unless => true
+ attr_encrypted :with_false_unless, :key => 'secret key', :unless => false
+ attr_encrypted :with_if_changed, :key => 'secret key', :if => :should_encrypt
+
+ attr_encryptor :aliased, :key => 'secret_key'
+
+ attr_accessor :salt
+ attr_accessor :should_encrypt
+
+ def initialize
+ self.salt = Time.now.to_i.to_s
+ self.should_encrypt = true
+ end
+end
+
+class LegacyAdmin < LegacyUser
+ attr_encrypted :testing
+end
+
+class LegacySomeOtherClass
+ extend AttrEncrypted
+ def self.call(object)
+ object.class
+ end
+end
+
+class LegacyAttrEncryptedTest < Minitest::Test
+
+ def test_should_store_email_in_encrypted_attributes
+ assert LegacyUser.encrypted_attributes.include?(:email)
+ end
+
+ def test_should_not_store_salt_in_encrypted_attributes
+ assert !LegacyUser.encrypted_attributes.include?(:salt)
+ end
+
+ def test_attr_encrypted_should_return_true_for_email
+ assert LegacyUser.attr_encrypted?('email')
+ end
+
+ def test_attr_encrypted_should_not_use_the_same_attribute_name_for_two_attributes_in_the_same_line
+ refute_equal LegacyUser.encrypted_attributes[:email][:attribute], LegacyUser.encrypted_attributes[:without_encoding][:attribute]
+ end
+
+ def test_attr_encrypted_should_return_false_for_salt
+ assert !LegacyUser.attr_encrypted?('salt')
+ end
+
+ def test_should_generate_an_encrypted_attribute
+ assert LegacyUser.new.respond_to?(:encrypted_email)
+ end
+
+ def test_should_generate_an_encrypted_attribute_with_a_prefix_and_suffix
+ assert LegacyUser.new.respond_to?(:crypted_password_test)
+ end
+
+ def test_should_generate_an_encrypted_attribute_with_the_attribute_option
+ assert LegacyUser.new.respond_to?(:ssn_encrypted)
+ end
+
+ def test_should_not_encrypt_nil_value
+ assert_nil LegacyUser.encrypt_email(nil)
+ end
+
+ def test_should_not_encrypt_empty_string
+ assert_equal '', LegacyUser.encrypt_email('')
+ end
+
+ def test_should_encrypt_email
+ refute_nil LegacyUser.encrypt_email('test@example.com')
+ refute_equal 'test@example.com', LegacyUser.encrypt_email('test@example.com')
+ end
+
+ def test_should_encrypt_email_when_modifying_the_attr_writer
+ @user = LegacyUser.new
+ assert_nil @user.encrypted_email
+ @user.email = 'test@example.com'
+ refute_nil @user.encrypted_email
+ assert_equal LegacyUser.encrypt_email('test@example.com'), @user.encrypted_email
+ end
+
+ def test_should_not_decrypt_nil_value
+ assert_nil LegacyUser.decrypt_email(nil)
+ end
+
+ def test_should_not_decrypt_empty_string
+ assert_equal '', LegacyUser.decrypt_email('')
+ end
+
+ def test_should_decrypt_email
+ encrypted_email = LegacyUser.encrypt_email('test@example.com')
+ refute_equal 'test@test.com', encrypted_email
+ assert_equal 'test@example.com', LegacyUser.decrypt_email(encrypted_email)
+ end
+
+ def test_should_decrypt_email_when_reading
+ @user = LegacyUser.new
+ assert_nil @user.email
+ @user.encrypted_email = LegacyUser.encrypt_email('test@example.com')
+ assert_equal 'test@example.com', @user.email
+ end
+
+ def test_should_encrypt_with_encoding
+ assert_equal LegacyUser.encrypt_with_encoding('test'), [LegacyUser.encrypt_without_encoding('test')].pack('m')
+ end
+
+ def test_should_decrypt_with_encoding
+ encrypted = LegacyUser.encrypt_with_encoding('test')
+ assert_equal 'test', LegacyUser.decrypt_with_encoding(encrypted)
+ assert_equal LegacyUser.decrypt_with_encoding(encrypted), LegacyUser.decrypt_without_encoding(encrypted.unpack('m').first)
+ end
+
+ def test_should_decrypt_utf8_with_encoding
+ encrypted = LegacyUser.encrypt_with_encoding("test\xC2\xA0utf-8\xC2\xA0text")
+ assert_equal "test\xC2\xA0utf-8\xC2\xA0text", LegacyUser.decrypt_with_encoding(encrypted)
+ assert_equal LegacyUser.decrypt_with_encoding(encrypted), LegacyUser.decrypt_without_encoding(encrypted.unpack('m').first)
+ end
+
+ def test_should_encrypt_with_custom_encoding
+ assert_equal LegacyUser.encrypt_with_custom_encoding('test'), [LegacyUser.encrypt_without_encoding('test')].pack('m')
+ end
+
+ def test_should_decrypt_with_custom_encoding
+ encrypted = LegacyUser.encrypt_with_custom_encoding('test')
+ assert_equal 'test', LegacyUser.decrypt_with_custom_encoding(encrypted)
+ assert_equal LegacyUser.decrypt_with_custom_encoding(encrypted), LegacyUser.decrypt_without_encoding(encrypted.unpack('m').first)
+ end
+
+ def test_should_encrypt_with_marshaling
+ @user = LegacyUser.new
+ @user.with_marshaling = [1, 2, 3]
+ refute_nil @user.encrypted_with_marshaling
+ assert_equal LegacyUser.encrypt_with_marshaling([1, 2, 3]), @user.encrypted_with_marshaling
+ end
+
+ def test_should_decrypt_with_marshaling
+ encrypted = LegacyUser.encrypt_with_marshaling([1, 2, 3])
+ @user = LegacyUser.new
+ assert_nil @user.with_marshaling
+ @user.encrypted_with_marshaling = encrypted
+ assert_equal [1, 2, 3], @user.with_marshaling
+ end
+
+ def test_should_use_custom_encryptor_and_crypt_method_names_and_arguments
+ assert_equal LegacySillyEncryptor.silly_encrypt(:value => 'testing', :some_arg => 'test'), LegacyUser.encrypt_credit_card('testing')
+ end
+
+ def test_should_evaluate_a_key_passed_as_a_symbol
+ @user = LegacyUser.new
+ assert_nil @user.ssn_encrypted
+ @user.ssn = 'testing'
+ refute_nil @user.ssn_encrypted
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => @user.salt, insecure_mode: true, algorithm: 'aes-256-cbc'), @user.ssn_encrypted
+ end
+
+ def test_should_evaluate_a_key_passed_as_a_proc
+ @user = LegacyUser.new
+ assert_nil @user.crypted_password_test
+ @user.password = 'testing'
+ refute_nil @user.crypted_password_test
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'LegacyUser', insecure_mode: true, algorithm: 'aes-256-cbc'), @user.crypted_password_test
+ end
+
+ def test_should_use_options_found_in_the_attr_encrypted_options_attribute
+ @user = LegacyUser.new
+ assert_nil @user.crypted_password_test
+ @user.password = 'testing'
+ refute_nil @user.crypted_password_test
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'LegacyUser', insecure_mode: true, algorithm: 'aes-256-cbc'), @user.crypted_password_test
+ end
+
+ def test_should_inherit_encrypted_attributes
+ assert_equal [LegacyUser.encrypted_attributes.keys, :testing].flatten.collect { |key| key.to_s }.sort, LegacyAdmin.encrypted_attributes.keys.collect { |key| key.to_s }.sort
+ end
+
+ def test_should_inherit_attr_encrypted_options
+ assert !LegacyUser.attr_encrypted_options.empty?
+ assert_equal LegacyUser.attr_encrypted_options, LegacyAdmin.attr_encrypted_options
+ end
+
+ def test_should_not_inherit_unrelated_attributes
+ assert LegacySomeOtherClass.attr_encrypted_options.empty?
+ assert LegacySomeOtherClass.encrypted_attributes.empty?
+ end
+
+ def test_should_evaluate_a_symbol_option
+ assert_equal LegacySomeOtherClass, LegacySomeOtherClass.new.send(:evaluate_attr_encrypted_option, :class)
+ end
+
+ def test_should_evaluate_a_proc_option
+ assert_equal LegacySomeOtherClass, LegacySomeOtherClass.new.send(:evaluate_attr_encrypted_option, proc { |object| object.class })
+ end
+
+ def test_should_evaluate_a_lambda_option
+ assert_equal LegacySomeOtherClass, LegacySomeOtherClass.new.send(:evaluate_attr_encrypted_option, lambda { |object| object.class })
+ end
+
+ def test_should_evaluate_a_method_option
+ assert_equal LegacySomeOtherClass, LegacySomeOtherClass.new.send(:evaluate_attr_encrypted_option, LegacySomeOtherClass.method(:call))
+ end
+
+ def test_should_return_a_string_option
+ class_string = 'LegacySomeOtherClass'
+ assert_equal class_string, LegacySomeOtherClass.new.send(:evaluate_attr_encrypted_option, class_string)
+ end
+
+ def test_should_encrypt_with_true_if
+ @user = LegacyUser.new
+ assert_nil @user.encrypted_with_true_if
+ @user.with_true_if = 'testing'
+ refute_nil @user.encrypted_with_true_if
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'secret key', insecure_mode: true, algorithm: 'aes-256-cbc'), @user.encrypted_with_true_if
+ end
+
+ def test_should_not_encrypt_with_false_if
+ @user = LegacyUser.new
+ assert_nil @user.encrypted_with_false_if
+ @user.with_false_if = 'testing'
+ refute_nil @user.encrypted_with_false_if
+ assert_equal 'testing', @user.encrypted_with_false_if
+ end
+
+ def test_should_encrypt_with_false_unless
+ @user = LegacyUser.new
+ assert_nil @user.encrypted_with_false_unless
+ @user.with_false_unless = 'testing'
+ refute_nil @user.encrypted_with_false_unless
+ assert_equal Encryptor.encrypt(:value => 'testing', :key => 'secret key', insecure_mode: true, algorithm: 'aes-256-cbc'), @user.encrypted_with_false_unless
+ end
+
+ def test_should_not_encrypt_with_true_unless
+ @user = LegacyUser.new
+ assert_nil @user.encrypted_with_true_unless
+ @user.with_true_unless = 'testing'
+ refute_nil @user.encrypted_with_true_unless
+ assert_equal 'testing', @user.encrypted_with_true_unless
+ end
+
+ def test_should_work_with_aliased_attr_encryptor
+ assert LegacyUser.encrypted_attributes.include?(:aliased)
+ end
+
+ def test_should_always_reset_options
+ @user = LegacyUser.new
+ @user.with_if_changed = "encrypt_stuff"
+
+ @user = LegacyUser.new
+ @user.should_encrypt = false
+ @user.with_if_changed = "not_encrypted_stuff"
+ assert_equal "not_encrypted_stuff", @user.with_if_changed
+ assert_equal "not_encrypted_stuff", @user.encrypted_with_if_changed
+ end
+
+ def test_should_cast_values_as_strings_before_encrypting
+ string_encrypted_email = LegacyUser.encrypt_email('3')
+ assert_equal string_encrypted_email, LegacyUser.encrypt_email(3)
+ assert_equal '3', LegacyUser.decrypt_email(string_encrypted_email)
+ end
+
+ def test_should_create_query_accessor
+ @user = LegacyUser.new
+ assert !@user.email?
+ @user.email = ''
+ assert !@user.email?
+ @user.email = 'test@example.com'
+ assert @user.email?
+ end
+
+end
diff --git a/vendor/gems/attr_encrypted/test/legacy_compatibility_test.rb b/vendor/gems/attr_encrypted/test/legacy_compatibility_test.rb
new file mode 100644
index 00000000000..68985816f94
--- /dev/null
+++ b/vendor/gems/attr_encrypted/test/legacy_compatibility_test.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+# -*- encoding: utf-8 -*-
+require_relative 'test_helper'
+
+# Test to ensure that existing representations in database do not break on
+# migrating to new versions of this gem. This ensures that future versions of
+# this gem will retain backwards compatibility with data generated by earlier
+# versions.
+class LegacyCompatibilityTest < Minitest::Test
+ class LegacyNonmarshallingPet < ActiveRecord::Base
+ PET_NICKNAME_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-nickname-salt')
+ PET_NICKNAME_KEY = 'my-really-really-secret-pet-nickname-key'
+ PET_BIRTHDATE_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-birthdate-salt')
+ PET_BIRTHDATE_KEY = 'my-really-really-secret-pet-birthdate-key'
+
+ self.attr_encrypted_options[:insecure_mode] = true
+ self.attr_encrypted_options[:algorithm] = 'aes-256-cbc'
+ self.attr_encrypted_options[:mode] = :single_iv_and_salt
+
+ attr_encrypted :nickname,
+ :key => proc { Encryptor.encrypt(:value => PET_NICKNAME_SALT, :key => PET_NICKNAME_KEY, insecure_mode: true, algorithm: 'aes-256-cbc') }
+ attr_encrypted :birthdate,
+ :key => proc { Encryptor.encrypt(:value => PET_BIRTHDATE_SALT, :key => PET_BIRTHDATE_KEY, insecure_mode: true, algorithm: 'aes-256-cbc') }
+ end
+
+ class LegacyMarshallingPet < ActiveRecord::Base
+ PET_NICKNAME_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-nickname-salt')
+ PET_NICKNAME_KEY = 'my-really-really-secret-pet-nickname-key'
+ PET_BIRTHDATE_SALT = Digest::SHA256.hexdigest('my-really-really-secret-pet-birthdate-salt')
+ PET_BIRTHDATE_KEY = 'my-really-really-secret-pet-birthdate-key'
+
+ self.attr_encrypted_options[:insecure_mode] = true
+ self.attr_encrypted_options[:algorithm] = 'aes-256-cbc'
+ self.attr_encrypted_options[:mode] = :single_iv_and_salt
+
+ attr_encrypted :nickname,
+ :key => proc { Encryptor.encrypt(:value => PET_NICKNAME_SALT, :key => PET_NICKNAME_KEY, insecure_mode: true, algorithm: 'aes-256-cbc') },
+ :marshal => true
+ attr_encrypted :birthdate,
+ :key => proc { Encryptor.encrypt(:value => PET_BIRTHDATE_SALT, :key => PET_BIRTHDATE_KEY, insecure_mode: true, algorithm: 'aes-256-cbc') },
+ :marshal => true
+ end
+
+ def setup
+ drop_all_tables
+ create_tables
+ end
+
+ def test_nonmarshalling_backwards_compatibility
+ pet = LegacyNonmarshallingPet.create!(
+ :name => 'Fido',
+ :encrypted_nickname => 'uSUB6KGzta87yxesyVc3DA==',
+ :encrypted_birthdate => 'I3d691B2PtFXLx15kO067g=='
+ )
+
+ assert_equal 'Fido', pet.name
+ assert_equal 'Fido the Dog', pet.nickname
+ assert_equal '2011-07-09', pet.birthdate
+ end
+
+ def test_marshalling_backwards_compatibility
+ pet = LegacyMarshallingPet.create!(
+ :name => 'Fido',
+ :encrypted_nickname => '7RwoT64in4H+fGVBPYtRcN0K4RtriIy1EP4nDojUa8g=',
+ :encrypted_birthdate => 'bSp9sJhXQSp2QlNZHiujtcK4lRVBE8HQhn1y7moQ63bGJR20hvRSZ73ePAmm+wc5'
+ )
+
+ assert_equal 'Fido', pet.name
+ assert_equal 'Mummy\'s little helper', pet.nickname
+
+ assert_equal Date.new(2011, 7, 9), pet.birthdate
+ end
+
+ private
+
+ def create_tables
+ ActiveRecord::Schema.define(:version => 1) do
+ create_table :legacy_nonmarshalling_pets do |t|
+ t.string :name
+ t.string :encrypted_nickname
+ t.string :encrypted_birthdate
+ t.string :salt
+ end
+ create_table :legacy_marshalling_pets do |t|
+ t.string :name
+ t.string :encrypted_nickname
+ t.string :encrypted_birthdate
+ t.string :salt
+ end
+ end
+ end
+end
+
+ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
diff --git a/vendor/gems/attr_encrypted/test/legacy_data_mapper_test.rb b/vendor/gems/attr_encrypted/test/legacy_data_mapper_test.rb
new file mode 100644
index 00000000000..03916dd9728
--- /dev/null
+++ b/vendor/gems/attr_encrypted/test/legacy_data_mapper_test.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require_relative 'test_helper'
+
+DataMapper.setup(:default, 'sqlite3::memory:')
+
+class LegacyClient
+ include DataMapper::Resource
+ self.attr_encrypted_options[:insecure_mode] = true
+ self.attr_encrypted_options[:algorithm] = 'aes-256-cbc'
+ self.attr_encrypted_options[:mode] = :single_iv_and_salt
+
+ property :id, Serial
+ property :encrypted_email, String
+ property :encrypted_credentials, Text
+ property :salt, String
+
+ attr_encrypted :email, :key => 'a secret key', mode: :single_iv_and_salt
+ attr_encrypted :credentials, :key => Proc.new { |client| Encryptor.encrypt(:value => client.salt, :key => 'some private key', insecure_mode: true, algorithm: 'aes-256-cbc') }, :marshal => true, mode: :single_iv_and_salt
+
+ def initialize(attrs = {})
+ super attrs
+ self.salt ||= Digest::SHA1.hexdigest((Time.now.to_i * rand(5)).to_s)
+ self.credentials ||= { :username => 'example', :password => 'test' }
+ end
+end
+
+DataMapper.auto_migrate!
+
+class LegacyDataMapperTest < Minitest::Test
+
+ def setup
+ LegacyClient.all.each(&:destroy)
+ end
+
+ def test_should_encrypt_email
+ @client = LegacyClient.new :email => 'test@example.com'
+ assert @client.save
+ refute_nil @client.encrypted_email
+ refute_equal @client.email, @client.encrypted_email
+ assert_equal @client.email, LegacyClient.first.email
+ end
+
+ def test_should_marshal_and_encrypt_credentials
+ @client = LegacyClient.new
+ assert @client.save
+ refute_nil @client.encrypted_credentials
+ refute_equal @client.credentials, @client.encrypted_credentials
+ assert_equal @client.credentials, LegacyClient.first.credentials
+ assert LegacyClient.first.credentials.is_a?(Hash)
+ end
+
+ def test_should_encode_by_default
+ assert LegacyClient.attr_encrypted_options[:encode]
+ end
+
+end
diff --git a/vendor/gems/attr_encrypted/test/legacy_sequel_test.rb b/vendor/gems/attr_encrypted/test/legacy_sequel_test.rb
new file mode 100644
index 00000000000..fd46301e25e
--- /dev/null
+++ b/vendor/gems/attr_encrypted/test/legacy_sequel_test.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require_relative 'test_helper'
+
+DB.create_table :legacy_humans do
+ primary_key :id
+ column :encrypted_email, :string
+ column :password, :string
+ column :encrypted_credentials, :string
+ column :salt, :string
+end
+
+class LegacyHuman < Sequel::Model(:legacy_humans)
+ self.attr_encrypted_options[:insecure_mode] = true
+ self.attr_encrypted_options[:algorithm] = 'aes-256-cbc'
+ self.attr_encrypted_options[:mode] = :single_iv_and_salt
+
+ attr_encrypted :email, :key => 'a secret key', mode: :single_iv_and_salt
+ attr_encrypted :credentials, :key => Proc.new { |human| Encryptor.encrypt(:value => human.salt, :key => 'some private key', insecure_mode: true, algorithm: 'aes-256-cbc') }, :marshal => true, mode: :single_iv_and_salt
+
+ def after_initialize(attrs = {})
+ self.salt ||= Digest::SHA1.hexdigest((Time.now.to_i * rand(5)).to_s)
+ self.credentials ||= { :username => 'example', :password => 'test' }
+ end
+end
+
+class LegacySequelTest < Minitest::Test
+
+ def setup
+ LegacyHuman.all.each(&:destroy)
+ end
+
+ def test_should_encrypt_email
+ @human = LegacyHuman.new :email => 'test@example.com'
+ assert @human.save
+ refute_nil @human.encrypted_email
+ refute_equal @human.email, @human.encrypted_email
+ assert_equal @human.email, LegacyHuman.first.email
+ end
+
+ def test_should_marshal_and_encrypt_credentials
+ @human = LegacyHuman.new
+ assert @human.save
+ refute_nil @human.encrypted_credentials
+ refute_equal @human.credentials, @human.encrypted_credentials
+ assert_equal @human.credentials, LegacyHuman.first.credentials
+ assert LegacyHuman.first.credentials.is_a?(Hash)
+ end
+
+ def test_should_encode_by_default
+ assert LegacyHuman.attr_encrypted_options[:encode]
+ end
+
+end
diff --git a/vendor/gems/attr_encrypted/test/run.sh b/vendor/gems/attr_encrypted/test/run.sh
new file mode 100755
index 00000000000..7e9b777eebf
--- /dev/null
+++ b/vendor/gems/attr_encrypted/test/run.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env sh -e
+
+for RUBY in 1.9.3 2.0.0 2.1 2.2
+do
+ for RAILS in 2.3.8 3.0.0 3.1.0 3.2.0 4.0.0 4.1.0 4.2.0
+ do
+ if [[ $RUBY -gt 1.9.3 && $RAILS -lt 4.0.0 ]]; then
+ continue
+ fi
+ RBENV_VERSION=$RUBY ACTIVERECORD=$RAILS bundle && bundle exec rake
+ done
+done
diff --git a/vendor/gems/attr_encrypted/test/sequel_test.rb b/vendor/gems/attr_encrypted/test/sequel_test.rb
new file mode 100644
index 00000000000..1df520319b1
--- /dev/null
+++ b/vendor/gems/attr_encrypted/test/sequel_test.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require_relative 'test_helper'
+
+DB.create_table :humans do
+ primary_key :id
+ column :encrypted_email, :string
+ column :encrypted_email_salt, String
+ column :encrypted_email_iv, :string
+ column :password, :string
+ column :encrypted_credentials, :string
+ column :encrypted_credentials_iv, :string
+ column :encrypted_credentials_salt, String
+end
+
+class Human < Sequel::Model(:humans)
+ self.attr_encrypted_options[:mode] = :per_attribute_iv_and_salt
+
+ attr_encrypted :email, :key => SECRET_KEY
+ attr_encrypted :credentials, :key => SECRET_KEY, :marshal => true
+
+ def after_initialize(attrs = {})
+ self.credentials ||= { :username => 'example', :password => 'test' }
+ end
+end
+
+class SequelTest < Minitest::Test
+
+ def setup
+ Human.all.each(&:destroy)
+ end
+
+ def test_should_encrypt_email
+ @human = Human.new :email => 'test@example.com'
+ assert @human.save
+ refute_nil @human.encrypted_email
+ refute_equal @human.email, @human.encrypted_email
+ assert_equal @human.email, Human.first.email
+ end
+
+ def test_should_marshal_and_encrypt_credentials
+
+ @human = Human.new :credentials => { :username => 'example', :password => 'test' }
+ assert @human.save
+ refute_nil @human.encrypted_credentials
+ refute_equal @human.credentials, @human.encrypted_credentials
+ assert_equal @human.credentials, Human.first.credentials
+ assert Human.first.credentials.is_a?(Hash)
+ end
+
+ def test_should_encode_by_default
+ assert Human.attr_encrypted_options[:encode]
+ end
+
+end
diff --git a/vendor/gems/attr_encrypted/test/test_helper.rb b/vendor/gems/attr_encrypted/test/test_helper.rb
new file mode 100644
index 00000000000..091e81bb9d7
--- /dev/null
+++ b/vendor/gems/attr_encrypted/test/test_helper.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'simplecov'
+require 'simplecov-rcov'
+require "codeclimate-test-reporter"
+
+SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
+ [
+ SimpleCov::Formatter::HTMLFormatter,
+ SimpleCov::Formatter::RcovFormatter,
+ CodeClimate::TestReporter::Formatter
+ ]
+)
+
+# Disabling for now since there are issues with Ruby 3 support.
+# See https://gitlab.com/gitlab-org/ruby/gems/attr_encrypted/-/merge_requests/1
+#
+# SimpleCov.start do
+# add_filter 'test'
+# end
+
+CodeClimate::TestReporter.start
+
+require 'minitest/autorun'
+
+# Rails 4.0.x pins to an old minitest
+unless defined?(MiniTest::Test)
+ MiniTest::Test = MiniTest::Unit::TestCase
+end
+
+require 'active_record'
+require 'data_mapper'
+require 'digest/sha2'
+require 'sequel'
+ActiveSupport::Deprecation.behavior = :raise
+
+$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+$:.unshift(File.dirname(__FILE__))
+require 'attr_encrypted'
+
+DB = if defined?(RUBY_ENGINE) && RUBY_ENGINE.to_sym == :jruby
+ Sequel.jdbc('jdbc:sqlite::memory:')
+else
+ Sequel.sqlite
+end
+
+# The :after_initialize hook was removed in Sequel 4.0
+# and had been deprecated for a while before that:
+# http://sequel.rubyforge.org/rdoc-plugins/classes/Sequel/Plugins/AfterInitialize.html
+# This plugin re-enables it.
+Sequel::Model.plugin :after_initialize
+
+SECRET_KEY = SecureRandom.random_bytes(32)
+
+def base64_encoding_regex
+ /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$/
+end
+
+def drop_all_tables
+ connection = ActiveRecord::Base.connection
+ tables = (ActiveRecord::VERSION::MAJOR >= 5 ? connection.data_sources : connection.tables)
+ tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
+end
diff --git a/yarn.lock b/yarn.lock
index 5657313ca9a..adeedd86d03 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3802,10 +3802,10 @@ core-js-pure@^3.0.0:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
-core-js@^3.25.4:
- version "3.25.4"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.4.tgz#76f6bc330a79aafbaf77e9645293351ea5d09b5b"
- integrity sha512-JDLxg61lFPFYQ7U0HKoyKwVUV63VbbVTb/K73Yf+k4Mf4ZBZxCjfyrWZjTk1ZM7ZrgFSqhSIOmuzYAxG2f/reQ==
+core-js@^3.25.5:
+ version "3.25.5"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.5.tgz#e86f651a2ca8a0237a5f064c2fe56cef89646e27"
+ integrity sha512-nbm6eZSjm+ZuBQxCUPQKQCoUEfFOXjUZ8dTTyikyKaWrTYmAVbykQfwsKE5dBK88u3QCkCrzsx/PPlKfhsvgpw==
core-util-is@~1.0.0:
version "1.0.3"