summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-30 22:09:22 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-30 22:09:22 +0000
commitd0aaab0fe909d9a7a921c1f8241fb6f320d1befe (patch)
treec19919906007988a281a3d1a7083978260b2d1d9
parent1fe6e7297ad09105d97782d74da4f084191114b4 (diff)
downloadgitlab-ce-d0aaab0fe909d9a7a921c1f8241fb6f320d1befe.tar.gz
Add latest changes from gitlab-org/security/gitlab@13-2-stable-ee
-rw-r--r--app/controllers/admin/users_controller.rb21
-rw-r--r--app/controllers/projects/raw_controller.rb1
-rw-r--r--app/controllers/registrations_controller.rb10
-rw-r--r--app/helpers/safe_params_helper.rb2
-rw-r--r--app/models/member.rb1
-rw-r--r--app/workers/all_queues.yml8
-rw-r--r--app/workers/remove_unaccepted_member_invites_worker.rb33
-rw-r--r--changelogs/unreleased/222349-purge_unaccepted_member_invitations.yml5
-rw-r--r--changelogs/unreleased/security-44-stored-xss-via-svg-file-preview.yml5
-rw-r--r--changelogs/unreleased/security-ensure-prerequisites-are-met-before-account-deletion.yml5
-rw-r--r--changelogs/unreleased/security-fix-safe-params-helper.yml4
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--db/migrate/20200916120837_add_index_to_members_for_unaccepted_invitations.rb20
-rw-r--r--db/schema_migrations/202009161208371
-rw-r--r--db/structure.sql3
-rw-r--r--doc/user/admin_area/custom_project_templates.md3
-rw-r--r--doc/user/group/custom_project_templates.md5
-rw-r--r--doc/user/project/members/index.md3
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/controllers/admin/users_controller_spec.rb37
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb5
-rw-r--r--spec/controllers/registrations_controller_spec.rb18
-rw-r--r--spec/factories/projects.rb8
-rw-r--r--spec/requests/api/files_spec.rb15
-rw-r--r--spec/support/shared_examples/cached_response_shared_examples.rb12
-rw-r--r--spec/workers/remove_unaccepted_member_invites_worker_spec.rb76
-rwxr-xr-x[-rw-r--r--]vendor/gitignore/C++.gitignore0
-rwxr-xr-x[-rw-r--r--]vendor/gitignore/Java.gitignore0
28 files changed, 292 insertions, 18 deletions
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index fc0acd8f99a..9cb1d16fbd8 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -5,6 +5,7 @@ class Admin::UsersController < Admin::ApplicationController
before_action :user, except: [:index, :new, :create]
before_action :check_impersonation_availability, only: :impersonate
+ before_action :ensure_destroy_prerequisites_met, only: [:destroy]
def index
@users = User.filter_items(params[:filter]).order_name_asc
@@ -168,7 +169,7 @@ class Admin::UsersController < Admin::ApplicationController
end
def destroy
- user.delete_async(deleted_by: current_user, params: params.permit(:hard_delete))
+ user.delete_async(deleted_by: current_user, params: destroy_params)
respond_to do |format|
format.html { redirect_to admin_users_path, status: :found, notice: _("The user is being deleted.") }
@@ -197,6 +198,24 @@ class Admin::UsersController < Admin::ApplicationController
user == current_user
end
+ def destroy_params
+ params.permit(:hard_delete)
+ end
+
+ def ensure_destroy_prerequisites_met
+ return if hard_delete?
+
+ if user.solo_owned_groups.present?
+ message = s_('AdminUsers|You must transfer ownership or delete the groups owned by this user before you can delete their account')
+
+ redirect_to admin_user_path(user), status: :see_other, alert: message
+ end
+ end
+
+ def hard_delete?
+ destroy_params[:hard_delete]
+ end
+
def user
@user ||= find_routable!(User, params[:id])
end
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 69a3898af55..29f1e4bfd44 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -12,6 +12,7 @@ class Projects::RawController < Projects::ApplicationController
before_action :authorize_download_code!
before_action :show_rate_limit, only: [:show], unless: :external_storage_request?
before_action :assign_ref_vars
+ before_action :no_cache_headers, only: [:show]
before_action :redirect_to_external_storage, only: :show, if: :static_objects_external_storage_enabled?
def show
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index b1c1fe3ba74..f054be50e20 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -10,7 +10,7 @@ class RegistrationsController < Devise::RegistrationsController
skip_before_action :required_signup_info, :check_two_factor_requirement, only: [:welcome, :update_registration]
prepend_before_action :check_captcha, only: :create
- before_action :whitelist_query_limiting, only: [:destroy]
+ before_action :whitelist_query_limiting, :ensure_destroy_prerequisites_met, only: [:destroy]
before_action :ensure_terms_accepted,
if: -> { action_name == 'create' && Gitlab::CurrentSettings.current_application_settings.enforce_terms? }
before_action :load_recaptcha, only: :new
@@ -125,6 +125,14 @@ class RegistrationsController < Devise::RegistrationsController
private
+ def ensure_destroy_prerequisites_met
+ if current_user.solo_owned_groups.present?
+ redirect_to profile_account_path,
+ status: :see_other,
+ alert: s_('Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account')
+ end
+ end
+
def user_created_message(confirmed: false)
"User Created: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip} confirmed:#{confirmed}"
end
diff --git a/app/helpers/safe_params_helper.rb b/app/helpers/safe_params_helper.rb
index 72bf1377b02..e9f0d82bb27 100644
--- a/app/helpers/safe_params_helper.rb
+++ b/app/helpers/safe_params_helper.rb
@@ -5,7 +5,7 @@ module SafeParamsHelper
# Use this helper when generating links with `params.merge(...)`
def safe_params
if params.respond_to?(:permit!)
- params.except(:host, :port, :protocol).permit!
+ params.except(*ActionDispatch::Routing::RouteSet::RESERVED_OPTIONS).permit!
else
params
end
diff --git a/app/models/member.rb b/app/models/member.rb
index b221c44c748..34360fc62f7 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -5,6 +5,7 @@ class Member < ApplicationRecord
include AfterCommitQueue
include Sortable
include Importable
+ include CreatedAtFilterable
include Expirable
include Gitlab::Access
include Presentable
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 5148772c881..59a949fadef 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -291,6 +291,14 @@
:weight: 1
:idempotent:
:tags: []
+- :name: cronjob:remove_unaccepted_member_invites
+ :feature_category: :authentication_and_authorization
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: cronjob:remove_unreferenced_lfs_objects
:feature_category: :git_lfs
:has_external_dependencies:
diff --git a/app/workers/remove_unaccepted_member_invites_worker.rb b/app/workers/remove_unaccepted_member_invites_worker.rb
new file mode 100644
index 00000000000..4b75b43791e
--- /dev/null
+++ b/app/workers/remove_unaccepted_member_invites_worker.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class RemoveUnacceptedMemberInvitesWorker # rubocop:disable Scalability/IdempotentWorker
+ include ApplicationWorker
+ include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+
+ feature_category :authentication_and_authorization
+ urgency :low
+ idempotent!
+
+ EXPIRATION_THRESHOLD = 90.days
+ BATCH_SIZE = 10_000
+
+ def perform
+ # We need to check for user_id IS NULL because we have accepted invitations
+ # in the database where we did not clear the invite_token. We do not
+ # want to accidentally delete those members.
+ loop do
+ # rubocop: disable CodeReuse/ActiveRecord
+ inner_query = Member
+ .select(:id)
+ .invite
+ .created_before(EXPIRATION_THRESHOLD.ago)
+ .where(user_id: nil)
+ .limit(BATCH_SIZE)
+
+ records_deleted = Member.where(id: inner_query).delete_all
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ break if records_deleted == 0
+ end
+ end
+end
diff --git a/changelogs/unreleased/222349-purge_unaccepted_member_invitations.yml b/changelogs/unreleased/222349-purge_unaccepted_member_invitations.yml
new file mode 100644
index 00000000000..988ebe9f0c8
--- /dev/null
+++ b/changelogs/unreleased/222349-purge_unaccepted_member_invitations.yml
@@ -0,0 +1,5 @@
+---
+title: Purge unaccepted member invitations older than 90 days
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-44-stored-xss-via-svg-file-preview.yml b/changelogs/unreleased/security-44-stored-xss-via-svg-file-preview.yml
new file mode 100644
index 00000000000..89a1eedb753
--- /dev/null
+++ b/changelogs/unreleased/security-44-stored-xss-via-svg-file-preview.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent SVG XSS via Web IDE
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-ensure-prerequisites-are-met-before-account-deletion.yml b/changelogs/unreleased/security-ensure-prerequisites-are-met-before-account-deletion.yml
new file mode 100644
index 00000000000..4b8f1c64ec7
--- /dev/null
+++ b/changelogs/unreleased/security-ensure-prerequisites-are-met-before-account-deletion.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure user has no solo owned groups before triggering account deletion
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-safe-params-helper.yml b/changelogs/unreleased/security-fix-safe-params-helper.yml
new file mode 100644
index 00000000000..ac7d2b60ff2
--- /dev/null
+++ b/changelogs/unreleased/security-fix-safe-params-helper.yml
@@ -0,0 +1,4 @@
+---
+title: Security fix safe params helper
+author:
+type: security
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index b7432c4cbe6..1bba665c918 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -440,6 +440,9 @@ Settings.cron_jobs['remove_expired_members_worker']['job_class'] = 'RemoveExpire
Settings.cron_jobs['remove_expired_group_links_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['remove_expired_group_links_worker']['cron'] ||= '10 0 * * *'
Settings.cron_jobs['remove_expired_group_links_worker']['job_class'] = 'RemoveExpiredGroupLinksWorker'
+Settings.cron_jobs['remove_unaccepted_member_invites_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['remove_unaccepted_member_invites_worker']['cron'] ||= '10 15 * * *'
+Settings.cron_jobs['remove_unaccepted_member_invites_worker']['job_class'] = 'RemoveUnacceptedMemberInvitesWorker'
Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '0 */6 * * *'
Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker'
diff --git a/db/migrate/20200916120837_add_index_to_members_for_unaccepted_invitations.rb b/db/migrate/20200916120837_add_index_to_members_for_unaccepted_invitations.rb
new file mode 100644
index 00000000000..3b0e4b99eb5
--- /dev/null
+++ b/db/migrate/20200916120837_add_index_to_members_for_unaccepted_invitations.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class AddIndexToMembersForUnacceptedInvitations < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ INDEX_NAME = 'idx_members_created_at_user_id_invite_token'
+ INDEX_SCOPE = 'invite_token IS NOT NULL AND user_id IS NULL'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(:members, :created_at, where: INDEX_SCOPE, name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index(:members, :created_at, where: INDEX_SCOPE, name: INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20200916120837 b/db/schema_migrations/20200916120837
new file mode 100644
index 00000000000..41ce56533b2
--- /dev/null
+++ b/db/schema_migrations/20200916120837
@@ -0,0 +1 @@
+ff246eb2761c4504b67b7d7b197990a671626038e50f1b82d6b3e4739a1ec3d4 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 179be83d2cc..ecc4ec7b955 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -18581,6 +18581,8 @@ CREATE INDEX idx_jira_connect_subscriptions_on_installation_id ON public.jira_co
CREATE UNIQUE INDEX idx_jira_connect_subscriptions_on_installation_id_namespace_id ON public.jira_connect_subscriptions USING btree (jira_connect_installation_id, namespace_id);
+CREATE INDEX idx_members_created_at_user_id_invite_token ON public.members USING btree (created_at) WHERE ((invite_token IS NOT NULL) AND (user_id IS NULL));
+
CREATE INDEX idx_merge_requests_on_id_and_merge_jid ON public.merge_requests USING btree (id, merge_jid) WHERE ((merge_jid IS NOT NULL) AND (state_id = 4));
CREATE INDEX idx_merge_requests_on_source_project_and_branch_state_opened ON public.merge_requests USING btree (source_project_id, source_branch) WHERE (state_id = 1);
@@ -23872,5 +23874,6 @@ COPY "schema_migrations" (version) FROM STDIN;
20200728182311
20200831204646
20200831222347
+20200916120837
\.
diff --git a/doc/user/admin_area/custom_project_templates.md b/doc/user/admin_area/custom_project_templates.md
index d194ca42215..8d044b94fbd 100644
--- a/doc/user/admin_area/custom_project_templates.md
+++ b/doc/user/admin_area/custom_project_templates.md
@@ -15,7 +15,8 @@ templates are sourced.
Every project directly under the group namespace will be
available to the user if they have access to them. For example:
-- Public project in the group will be available to every logged in user.
+- Public projects, in the group will be available to every signed-in user, if all enabled [project features](../project/settings/index.md#sharing-and-permissions)
+ are set to **Everyone With Access**.
- Private projects will be available only if the user is a member of the project.
Repository and database information that are copied over to each new project are
diff --git a/doc/user/group/custom_project_templates.md b/doc/user/group/custom_project_templates.md
index ebeacda24c6..7c02d80d08d 100644
--- a/doc/user/group/custom_project_templates.md
+++ b/doc/user/group/custom_project_templates.md
@@ -20,9 +20,8 @@ GitLab administrators can
[set project templates for an entire GitLab instance](../admin_area/custom_project_templates.md).
Within this section, you can configure the group where all the custom project
-templates are sourced. Every project directly under the group namespace will be
-available to the user if they have access to them. For example, every public
-project in the group will be available to every logged in user.
+templates are sourced. Every project _template_ directly under the group namespace is
+available to every signed-in user, if all enabled [project features](../project/settings/index.md#sharing-and-permissions) are set to **Everyone With Access**.
However, private projects will be available only if the user is a member of the project.
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
index 3eb411aef18..d8579c4cd8e 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -93,6 +93,9 @@ invitation, change their access level, or even delete them.
Once the user accepts the invitation, they will be prompted to create a new
GitLab account using the same e-mail address the invitation was sent to.
+Note: **Note:**
+Unaccepted invites are automatically deleted after 90 days.
+
## Project membership and requesting access
Project owners can :
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 95a57c3d55a..60fd73edda4 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1942,6 +1942,9 @@ msgstr ""
msgid "AdminUsers|You cannot remove your own admin rights."
msgstr ""
+msgid "AdminUsers|You must transfer ownership or delete the groups owned by this user before you can delete their account"
+msgstr ""
+
msgid "Administration"
msgstr ""
@@ -17980,6 +17983,9 @@ msgstr ""
msgid "Profiles|You don't have access to delete this user."
msgstr ""
+msgid "Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account"
+msgstr ""
+
msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
msgstr ""
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 08a1d7c9fa9..9e7f24ff7e3 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe Admin::UsersController do
end
end
- describe 'DELETE #user with projects', :sidekiq_might_not_need_inline do
+ describe 'DELETE #destroy', :sidekiq_might_not_need_inline do
let(:project) { create(:project, namespace: user.namespace) }
let!(:issue) { create(:issue, author: user) }
@@ -59,6 +59,41 @@ RSpec.describe Admin::UsersController do
expect(User.exists?(user.id)).to be_falsy
expect(Issue.exists?(issue.id)).to be_falsy
end
+
+ context 'prerequisites for account deletion' do
+ context 'solo-owned groups' do
+ let(:group) { create(:group) }
+
+ context 'if the user is the sole owner of at least one group' do
+ before do
+ create(:group_member, :owner, group: group, user: user)
+ end
+
+ context 'soft-delete' do
+ it 'fails' do
+ delete :destroy, params: { id: user.username }
+
+ message = s_('AdminUsers|You must transfer ownership or delete the groups owned by this user before you can delete their account')
+
+ expect(flash[:alert]).to eq(message)
+ expect(response).to have_gitlab_http_status(:see_other)
+ expect(response).to redirect_to admin_user_path(user)
+ expect(User.exists?(user.id)).to be_truthy
+ end
+ end
+
+ context 'hard-delete' do
+ it 'succeeds' do
+ delete :destroy, params: { id: user.username, hard_delete: true }
+
+ expect(response).to redirect_to(admin_users_path)
+ expect(flash[:notice]).to eq(_('The user is being deleted.'))
+ expect(User.exists?(user.id)).to be_falsy
+ end
+ end
+ end
+ end
+ end
end
describe 'PUT #activate' do
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 5f10343eb76..b3921164c81 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -33,6 +33,11 @@ RSpec.describe Projects::RawController do
it_behaves_like 'project cache control headers'
it_behaves_like 'content disposition headers'
+ it_behaves_like 'uncached response' do
+ before do
+ subject
+ end
+ end
end
context 'image header' do
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index 66caa58666f..c1afc75a6fd 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -384,6 +384,24 @@ RSpec.describe RegistrationsController do
expect_success
end
end
+
+ context 'prerequisites for account deletion' do
+ context 'solo-owned groups' do
+ let(:group) { create(:group) }
+
+ context 'if the user is the sole owner of at least one group' do
+ before do
+ create(:group_member, :owner, group: group, user: user)
+ end
+
+ it 'fails' do
+ delete :destroy, params: { password: '12345678' }
+
+ expect_failure(s_('Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account'))
+ end
+ end
+ end
+ end
end
describe '#update_registration' do
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index e4b53186ea8..216991c56c5 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -31,6 +31,7 @@ FactoryBot.define do
pages_access_level do
visibility_level == Gitlab::VisibilityLevel::PUBLIC ? ProjectFeature::ENABLED : ProjectFeature::PRIVATE
end
+ metrics_dashboard_access_level { ProjectFeature::PRIVATE }
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
@@ -54,7 +55,9 @@ FactoryBot.define do
issues_access_level: evaluator.issues_access_level,
forking_access_level: evaluator.forking_access_level,
merge_requests_access_level: merge_requests_access_level,
- repository_access_level: evaluator.repository_access_level
+ repository_access_level: evaluator.repository_access_level,
+ pages_access_level: evaluator.pages_access_level,
+ metrics_dashboard_access_level: evaluator.metrics_dashboard_access_level
}
if ActiveRecord::Migrator.current_version >= PAGES_ACCESS_LEVEL_SCHEMA_VERSION
@@ -300,6 +303,9 @@ FactoryBot.define do
trait(:pages_enabled) { pages_access_level { ProjectFeature::ENABLED } }
trait(:pages_disabled) { pages_access_level { ProjectFeature::DISABLED } }
trait(:pages_private) { pages_access_level { ProjectFeature::PRIVATE } }
+ trait(:metrics_dashboard_enabled) { metrics_dashboard_access_level { ProjectFeature::ENABLED } }
+ trait(:metrics_dashboard_disabled) { metrics_dashboard_access_level { ProjectFeature::DISABLED } }
+ trait(:metrics_dashboard_private) { metrics_dashboard_access_level { ProjectFeature::PRIVATE } }
trait :auto_devops do
association :auto_devops, factory: :project_auto_devops
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index b50f63ed67c..91877776190 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -502,16 +502,13 @@ RSpec.describe API::Files do
expect(response).to have_gitlab_http_status(:ok)
end
- it 'sets no-cache headers' do
- url = route('.gitignore') + "/raw"
- expect(Gitlab::Workhorse).to receive(:send_git_blob)
-
- get api(url, current_user), params: params
+ it_behaves_like 'uncached response' do
+ before do
+ url = route('.gitignore') + "/raw"
+ expect(Gitlab::Workhorse).to receive(:send_git_blob)
- expect(response.headers["Cache-Control"]).to include("no-store")
- expect(response.headers["Cache-Control"]).to include("no-cache")
- expect(response.headers["Pragma"]).to eq("no-cache")
- expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT")
+ get api(url, current_user), params: params
+ end
end
context 'when mandatory params are not given' do
diff --git a/spec/support/shared_examples/cached_response_shared_examples.rb b/spec/support/shared_examples/cached_response_shared_examples.rb
new file mode 100644
index 00000000000..34e5f741b4e
--- /dev/null
+++ b/spec/support/shared_examples/cached_response_shared_examples.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+#
+# Negates lib/gitlab/no_cache_headers.rb
+#
+
+RSpec.shared_examples 'cached response' do
+ it 'defines a cached header response' do
+ expect(response.headers["Cache-Control"]).not_to include("no-store", "no-cache")
+ expect(response.headers["Pragma"]).not_to eq("no-cache")
+ expect(response.headers["Expires"]).not_to eq("Fri, 01 Jan 1990 00:00:00 GMT")
+ end
+end
diff --git a/spec/workers/remove_unaccepted_member_invites_worker_spec.rb b/spec/workers/remove_unaccepted_member_invites_worker_spec.rb
new file mode 100644
index 00000000000..96d7cf535ed
--- /dev/null
+++ b/spec/workers/remove_unaccepted_member_invites_worker_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe RemoveUnacceptedMemberInvitesWorker do
+ let(:worker) { described_class.new }
+
+ describe '#perform' do
+ context 'unaccepted members' do
+ before do
+ stub_const("#{described_class}::EXPIRATION_THRESHOLD", 1.day)
+ end
+
+ it 'removes unaccepted members', :aggregate_failures do
+ unaccepted_group_invitee = create(
+ :group_member, invite_token: 't0ken',
+ invite_email: 'group_invitee@example.com',
+ user: nil,
+ created_at: Time.current - 5.days)
+ unaccepted_project_invitee = create(
+ :project_member, invite_token: 't0ken',
+ invite_email: 'project_invitee@example.com',
+ user: nil,
+ created_at: Time.current - 5.days)
+
+ expect { worker.perform }.to change { Member.count }.by(-2)
+
+ expect(Member.where(id: unaccepted_project_invitee.id)).not_to exist
+ expect(Member.where(id: unaccepted_group_invitee.id)).not_to exist
+ end
+ end
+
+ context 'invited members still within expiration threshold' do
+ it 'leaves invited members', :aggregate_failures do
+ group_invitee = create(
+ :group_member, invite_token: 't0ken',
+ invite_email: 'group_invitee@example.com',
+ user: nil)
+ project_invitee = create(
+ :project_member, invite_token: 't0ken',
+ invite_email: 'project_invitee@example.com',
+ user: nil)
+
+ expect { worker.perform }.not_to change { Member.count }
+
+ expect(Member.where(id: group_invitee.id)).to exist
+ expect(Member.where(id: project_invitee.id)).to exist
+ end
+ end
+
+ context 'accepted members' do
+ before do
+ stub_const("#{described_class}::EXPIRATION_THRESHOLD", 1.day)
+ end
+
+ it 'leaves accepted members', :aggregate_failures do
+ user = create(:user)
+ accepted_group_invitee = create(
+ :group_member, invite_token: 't0ken',
+ invite_email: 'group_invitee@example.com',
+ user: user,
+ created_at: Time.current - 5.days)
+ accepted_project_invitee = create(
+ :project_member, invite_token: nil,
+ invite_email: 'project_invitee@example.com',
+ user: user,
+ created_at: Time.current - 5.days)
+
+ expect { worker.perform }.not_to change { Member.count }
+
+ expect(Member.where(id: accepted_group_invitee.id)).to exist
+ expect(Member.where(id: accepted_project_invitee.id)).to exist
+ end
+ end
+ end
+end
diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore
index 259148fa18f..259148fa18f 100644..100755
--- a/vendor/gitignore/C++.gitignore
+++ b/vendor/gitignore/C++.gitignore
diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore
index a1c2a238a96..a1c2a238a96 100644..100755
--- a/vendor/gitignore/Java.gitignore
+++ b/vendor/gitignore/Java.gitignore