Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-05 06:13:32 +00:00
parent 3d38e524f1
commit 791054a0a5
29 changed files with 584 additions and 459 deletions

View File

@ -209,7 +209,6 @@ export default {
/>
<sidebar-labels-widget
class="block labels"
data-testid="sidebar-labels"
:iid="activeBoardItem.iid"
:full-path="projectPathForActiveIssue"
:allow-label-remove="allowLabelEdit"

View File

@ -16,6 +16,7 @@ export default class CreateLabelDropdown {
this.$colorPreview = $('.js-dropdown-label-color-preview', this.$el);
this.$addList = $('.js-add-list', this.$el);
this.$newLabelError = $('.js-label-error', this.$el);
this.$newLabelErrorContent = $('.gl-alert-content', this.$newLabelError);
this.$newLabelCreateButton = $('.js-new-label-btn', this.$el);
this.$colorSuggestions = $('.suggest-colors-dropdown a', this.$el);
@ -119,7 +120,8 @@ export default class CreateLabelDropdown {
.join('<br/>');
}
this.$newLabelError.html(errors).show();
this.$newLabelErrorContent.html(errors);
this.$newLabelError.show();
} else {
const addNewList = this.$addList.is(':checked');
this.$dropdownBack.trigger('click');

View File

@ -1,5 +1,12 @@
<script>
import { GlTooltipDirective, GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
import {
GlAlert,
GlTooltipDirective,
GlButton,
GlFormInput,
GlLink,
GlLoadingIcon,
} from '@gitlab/ui';
import produce from 'immer';
import createFlash from '~/flash';
import { __ } from '~/locale';
@ -11,6 +18,7 @@ const errorMessage = __('Error creating label.');
export default {
components: {
GlAlert,
GlButton,
GlFormInput,
GlLink,
@ -42,6 +50,7 @@ export default {
labelTitle: '',
selectedColor: '',
labelCreateInProgress: false,
error: undefined,
};
},
computed: {
@ -111,13 +120,14 @@ export default {
) => this.updateLabelsInCache(store, label),
});
if (labelCreate.errors.length) {
createFlash({ message: errorMessage });
[this.error] = labelCreate.errors;
} else {
this.$emit('hideCreateView');
}
} catch {
createFlash({ message: errorMessage });
}
this.labelCreateInProgress = false;
this.$emit('hideCreateView');
},
},
};
@ -126,6 +136,9 @@ export default {
<template>
<div class="labels-select-contents-create js-labels-create">
<div class="dropdown-input">
<gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mb-3">
{{ error }}
</gl-alert>
<gl-form-input
v-model.trim="labelTitle"
:placeholder="__('Name new label')"

View File

@ -289,6 +289,7 @@ export default {
'is-standalone': isDropdownVariantStandalone(variant),
'is-embedded': isDropdownVariantEmbedded(variant),
}"
data-testid="sidebar-labels"
data-qa-selector="labels_block"
>
<template v-if="isDropdownVariantSidebar(variant)">

View File

@ -68,13 +68,6 @@
color: $white;
}
.dropdown-labels-error {
padding: 5px 10px;
margin-bottom: 10px;
background-color: $red-500;
color: $white;
}
.manage-labels-list {
padding: 0;
margin-bottom: 0;

View File

@ -158,7 +158,7 @@ class Admin::UsersController < Admin::ApplicationController
end
def confirm
if update_user { |user| user.confirm }
if update_user { |user| user.force_confirm }
redirect_back_or_admin_user(notice: _("Successfully confirmed"))
else
redirect_back_or_admin_user(alert: _("Error occurred. User was not confirmed"))

View File

@ -33,17 +33,15 @@ class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy
end
def blob
return blob_via_workhorse if Feature.enabled?(:dependency_proxy_workhorse, group, default_enabled: :yaml)
blob = @group.dependency_proxy_blobs.find_by_file_name(blob_file_name)
result = DependencyProxy::FindOrCreateBlobService
.new(group, image, token, params[:sha]).execute
if result[:status] == :success
event_name = tracking_event_name(object_type: :blob, from_cache: result[:from_cache])
if blob.present?
event_name = tracking_event_name(object_type: :blob, from_cache: true)
track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)
send_upload(result[:blob].file)
send_upload(blob.file)
else
head result[:http_status]
send_dependency(token_header, DependencyProxy::Registry.blob_url(image, params[:sha]), blob_file_name)
end
end
@ -99,19 +97,6 @@ class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy
private
def blob_via_workhorse
blob = @group.dependency_proxy_blobs.find_by_file_name(blob_file_name)
if blob.present?
event_name = tracking_event_name(object_type: :blob, from_cache: true)
track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)
send_upload(blob.file)
else
send_dependency(token_header, DependencyProxy::Registry.blob_url(image, params[:sha]), blob_file_name)
end
end
def send_manifest(manifest, from_cache:)
response.headers[DependencyProxy::Manifest::DIGEST_HEADER] = manifest.digest
response.headers['Content-Length'] = manifest.size

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
module ForcedEmailConfirmation
extend ActiveSupport::Concern
included do
attr_accessor :skip_confirmation_period_expiry_check
end
def force_confirm(args = {})
self.skip_confirmation_period_expiry_check = true
confirm(args)
ensure
self.skip_confirmation_period_expiry_check = nil
end
protected
# Override, from Devise::Models::Confirmable
# Link: https://github.com/heartcombo/devise/blob/main/lib/devise/models/confirmable.rb
def confirmation_period_expired?
return false if skip_confirmation_period_expiry_check
super
end
end

View File

@ -19,6 +19,7 @@ class Email < ApplicationRecord
# This module adds async behaviour to Devise emails
# and should be added after Devise modules are initialized.
include AsyncDeviseEmail
include ForcedEmailConfirmation
self.reconfirmable = false # currently email can't be changed, no need to reconfirm

View File

@ -81,6 +81,7 @@ class User < ApplicationRecord
# This module adds async behaviour to Devise emails
# and should be added after Devise modules are initialized.
include AsyncDeviseEmail
include ForcedEmailConfirmation
MINIMUM_INACTIVE_DAYS = 90
@ -1974,18 +1975,22 @@ class User < ApplicationRecord
ci_job_token_scope.present?
end
# override from Devise::Confirmable
# override from Devise::Models::Confirmable
#
# Add the primary email to user.emails (or confirm it if it was already
# present) when the primary email is confirmed.
def confirm(*args)
saved = super(*args)
def confirm(args = {})
saved = super(args)
return false unless saved
email_to_confirm = self.emails.find_by(email: self.email)
if email_to_confirm.present?
email_to_confirm.confirm(*args)
if skip_confirmation_period_expiry_check
email_to_confirm.force_confirm(args)
else
email_to_confirm.confirm(args)
end
else
add_primary_email_to_emails!
end

View File

@ -1,38 +0,0 @@
# frozen_string_literal: true
module DependencyProxy
class DownloadBlobService < DependencyProxy::BaseService
def initialize(image, blob_sha, token)
@image = image
@blob_sha = blob_sha
@token = token
@temp_file = Tempfile.new
end
def execute
File.open(@temp_file.path, "wb") do |file|
Gitlab::HTTP.get(blob_url, headers: auth_headers, stream_body: true) do |fragment|
if [301, 302, 307].include?(fragment.code)
# do nothing
elsif fragment.code == 200
file.write(fragment)
else
raise DownloadError.new('Non-success response code on downloading blob fragment', fragment.code)
end
end
end
success(file: @temp_file)
rescue DownloadError => exception
error(exception.message, exception.http_status)
rescue Timeout::Error => exception
error(exception.message, 599)
end
private
def blob_url
registry.blob_url(@image, @blob_sha)
end
end
end

View File

@ -1,48 +0,0 @@
# frozen_string_literal: true
module DependencyProxy
class FindOrCreateBlobService < DependencyProxy::BaseService
def initialize(group, image, token, blob_sha)
@group = group
@image = image
@token = token
@blob_sha = blob_sha
end
def execute
from_cache = true
file_name = @blob_sha.sub('sha256:', '') + '.gz'
blob = @group.dependency_proxy_blobs.active.find_or_build(file_name)
unless blob.persisted?
from_cache = false
result = DependencyProxy::DownloadBlobService
.new(@image, @blob_sha, @token).execute
if result[:status] == :error
log_failure(result)
return error('Failed to download the blob', result[:http_status])
end
blob.file = result[:file]
blob.size = result[:file].size
blob.save!
end
blob.read! if from_cache
success(blob: blob, from_cache: from_cache)
end
private
def log_failure(result)
log_error(
"Dependency proxy: Failed to download the blob." \
"Blob sha: #{@blob_sha}." \
"Error message: #{result[:message][0, 100]}" \
"HTTP status: #{result[:http_status]}"
)
end
end
end

View File

@ -41,20 +41,51 @@ module Projects
remote_mirror.update_start!
# LFS objects must be sent first, or the push has dangling pointers
send_lfs_objects!(remote_mirror)
lfs_status = send_lfs_objects!(remote_mirror)
response = remote_mirror.update_repository
failed, failure_message = failure_status(lfs_status, response, remote_mirror)
if response.divergent_refs.any?
message = "Some refs have diverged and have not been updated on the remote:"
message += "\n\n#{response.divergent_refs.join("\n")}"
remote_mirror.mark_as_failed!(message)
# When the issue https://gitlab.com/gitlab-org/gitlab/-/issues/349262 is closed,
# we can move this block within failure_status.
if failed
remote_mirror.mark_as_failed!(failure_message)
else
remote_mirror.update_finish!
end
end
def failure_status(lfs_status, response, remote_mirror)
message = ''
failed = false
lfs_sync_failed = false
if lfs_status&.dig(:status) == :error
lfs_sync_failed = true
message += "Error synchronizing LFS files:"
message += "\n\n#{lfs_status[:message]}\n\n"
failed = Feature.enabled?(:remote_mirror_fail_on_lfs, project, default_enabled: :yaml)
end
if response.divergent_refs.any?
message += "Some refs have diverged and have not been updated on the remote:"
message += "\n\n#{response.divergent_refs.join("\n")}"
failed = true
end
if message.present?
Gitlab::AppJsonLogger.info(message: "Error synching remote mirror",
project_id: project.id,
project_path: project.full_path,
remote_mirror_id: remote_mirror.id,
lfs_sync_failed: lfs_sync_failed,
divergent_ref_list: response.divergent_refs)
end
[failed, message]
end
def send_lfs_objects!(remote_mirror)
return unless project.lfs_enabled?

View File

@ -6,7 +6,10 @@
.dropdown-page-two.dropdown-new-label
= dropdown_title(create_label_title(subject), options: { back: true, close: show_close })
= dropdown_content do
.dropdown-labels-error.js-label-error
.js-label-error.gl-alert.gl-alert-danger.gl-mb-3
.gl-alert-container
= sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
.gl-alert-content
%input#new_label_name.default-dropdown-input{ type: "text", placeholder: _('Name new label') }
.suggest-colors.suggest-colors-dropdown
= render_suggested_colors

View File

@ -1,8 +1,8 @@
---
name: dependency_proxy_workhorse
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68157
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339639
milestone: '14.3'
name: remote_mirror_fail_on_lfs
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77339
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349262
milestone: '14.7'
type: development
group: group::source code
default_enabled: true
default_enabled: false

View File

@ -421,16 +421,37 @@ RSpec.describe Admin::UsersController do
end
describe 'PUT confirm/:id' do
let(:user) { create(:user, confirmed_at: nil) }
shared_examples_for 'confirms the user' do
it 'confirms the user' do
put :confirm, params: { id: user.username }
user.reload
expect(user.confirmed?).to be_truthy
end
end
let(:expired_confirmation_sent_at) { Date.today - User.confirm_within - 7.days }
let(:extant_confirmation_sent_at) { Date.today }
let(:user) do
create(:user, :unconfirmed).tap do |user|
user.update!(confirmation_sent_at: confirmation_sent_at)
end
end
before do
request.env["HTTP_REFERER"] = "/"
end
it 'confirms user' do
put :confirm, params: { id: user.username }
user.reload
expect(user.confirmed?).to be_truthy
context 'when the confirmation period has expired' do
let(:confirmation_sent_at) { expired_confirmation_sent_at }
it_behaves_like 'confirms the user'
end
context 'when the confirmation period has not expired' do
let(:confirmation_sent_at) { extant_confirmation_sent_at }
it_behaves_like 'confirms the user'
end
end

View File

@ -348,74 +348,6 @@ RSpec.describe Groups::DependencyProxyForContainersController do
it_behaves_like 'a successful blob pull'
end
end
context 'when dependency_proxy_workhorse disabled' do
let(:blob_response) { { status: :success, blob: blob, from_cache: false } }
before do
stub_feature_flags(dependency_proxy_workhorse: false)
allow_next_instance_of(DependencyProxy::FindOrCreateBlobService) do |instance|
allow(instance).to receive(:execute).and_return(blob_response)
end
end
context 'remote blob request fails' do
let(:blob_response) do
{
status: :error,
http_status: 400,
message: ''
}
end
before do
group.add_guest(user)
end
it 'proxies status from the remote blob request', :aggregate_failures do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(response.body).to be_empty
end
end
context 'a valid user' do
before do
group.add_guest(user)
end
it_behaves_like 'a successful blob pull'
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob'
context 'with a cache entry' do
let(:blob_response) { { status: :success, blob: blob, from_cache: true } }
it_behaves_like 'returning response status', :success
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache'
end
end
context 'a valid deploy token' do
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
it_behaves_like 'a successful blob pull'
context 'pulling from a subgroup' do
let_it_be_with_reload(:parent_group) { create(:group) }
let_it_be_with_reload(:group) { create(:group, parent: parent_group) }
before do
parent_group.create_dependency_proxy_setting!(enabled: true)
group_deploy_token.update_column(:group_id, parent_group.id)
end
it_behaves_like 'a successful blob pull'
end
end
end
end
it_behaves_like 'not found when disabled'

View File

@ -6,9 +6,11 @@ RSpec.describe 'Project issue boards sidebar', :js do
include BoardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:board) { create(:board, project: project) }
let_it_be(:list) { create(:list, board: board, position: 0) }
let_it_be(:label) { create(:label, project: project, name: 'Label') }
let_it_be(:list) { create(:list, board: board, label: label, position: 0) }
let_it_be(:issue, reload: true) { create(:issue, project: project, relative_position: 1) }

View File

@ -81,28 +81,11 @@ RSpec.describe 'Group Dependency Proxy for containers', :js do
let!(:dependency_proxy_blob) { create(:dependency_proxy_blob, group: group) }
it_behaves_like 'responds with the file'
context 'dependency_proxy_workhorse feature flag disabled' do
before do
stub_feature_flags({ dependency_proxy_workhorse: false })
end
it_behaves_like 'responds with the file'
end
end
end
context 'when the blob must be downloaded' do
it_behaves_like 'responds with the file'
it_behaves_like 'caches the file'
context 'dependency_proxy_workhorse feature flag disabled' do
before do
stub_feature_flags({ dependency_proxy_workhorse: false })
end
it_behaves_like 'responds with the file'
it_behaves_like 'caches the file'
end
end
end

View File

@ -8,10 +8,9 @@ RSpec.describe 'Issue Sidebar' do
let_it_be(:group) { create(:group, :nested) }
let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:user) { create(:user) }
let_it_be(:label) { create(:label, project: project, title: 'bug') }
let_it_be(:issue) { create(:labeled_issue, project: project, labels: [label]) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:label) { create(:label, project: project, name: 'Label') }
let_it_be(:mock_date) { Date.today.at_beginning_of_month + 2.days }
let_it_be(:xss_label) { create(:label, project: project, title: '&lt;script&gt;alert("xss");&lt;&#x2F;script&gt;') }
before do
stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
@ -223,14 +222,6 @@ RSpec.describe 'Issue Sidebar' do
restore_window_size
open_issue_sidebar
end
it 'escapes XSS when viewing issue labels' do
page.within('.block.labels') do
click_on 'Edit'
expect(page).to have_content '<script>alert("xss");</script>'
end
end
end
context 'editing issue milestone', :js do
@ -242,62 +233,7 @@ RSpec.describe 'Issue Sidebar' do
end
context 'editing issue labels', :js do
before do
issue.update!(labels: [label])
page.within('.block.labels') do
click_on 'Edit'
end
end
it 'shows the current set of labels' do
page.within('.issuable-show-labels') do
expect(page).to have_content label.title
end
end
it 'shows option to create a project label' do
page.within('.block.labels') do
expect(page).to have_content 'Create project'
end
end
context 'creating a project label', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27992' do
before do
page.within('.block.labels') do
click_link 'Create project'
end
end
it 'shows dropdown switches to "create label" section' do
page.within('.block.labels') do
expect(page).to have_content 'Create project label'
end
end
it 'adds new label' do
page.within('.block.labels') do
fill_in 'new_label_name', with: 'wontfix'
page.find('.suggest-colors a', match: :first).click
page.find('button', text: 'Create').click
page.within('.dropdown-page-one') do
expect(page).to have_content 'wontfix'
end
end
end
it 'shows error message if label title is taken' do
page.within('.block.labels') do
fill_in 'new_label_name', with: label.title
page.find('.suggest-colors a', match: :first).click
page.find('button', text: 'Create').click
page.within('.dropdown-page-two') do
expect(page).to have_content 'Title has already been taken'
end
end
end
end
it_behaves_like 'labels sidebar widget'
end
context 'interacting with collapsed sidebar', :js do

View File

@ -1,4 +1,4 @@
import { GlLoadingIcon, GlLink } from '@gitlab/ui';
import { GlAlert, GlLoadingIcon, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
@ -9,6 +9,7 @@ import { workspaceLabelsQueries } from '~/sidebar/constants';
import DropdownContentsCreateView from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue';
import createLabelMutation from '~/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql';
import {
mockRegularLabel,
mockSuggestedColors,
createLabelSuccessfulResponse,
workspaceLabelsQueryResponse,
@ -25,8 +26,18 @@ const userRecoverableError = {
errors: ['Houston, we have a problem'],
};
const titleTakenError = {
data: {
labelCreate: {
label: mockRegularLabel,
errors: ['Title has already been taken'],
},
},
};
const createLabelSuccessHandler = jest.fn().mockResolvedValue(createLabelSuccessfulResponse);
const createLabelUserRecoverableErrorHandler = jest.fn().mockResolvedValue(userRecoverableError);
const createLabelDuplicateErrorHandler = jest.fn().mockResolvedValue(titleTakenError);
const createLabelErrorHandler = jest.fn().mockRejectedValue('Houston, we have a problem');
describe('DropdownContentsCreateView', () => {
@ -208,4 +219,17 @@ describe('DropdownContentsCreateView', () => {
expect(createFlash).toHaveBeenCalled();
});
it('displays error in alert if label title is already taken', async () => {
createComponent({ mutationHandler: createLabelDuplicateErrorHandler });
fillLabelAttributes();
await nextTick();
findCreateButton().vm.$emit('click');
await waitForPromises();
expect(wrapper.findComponent(GlAlert).text()).toEqual(
titleTakenError.data.labelCreate.errors[0],
);
});
});

View File

@ -71,4 +71,84 @@ RSpec.describe Email do
end
end
end
describe '#confirm' do
let(:expired_confirmation_sent_at) { Date.today - described_class.confirm_within - 7.days }
let(:extant_confirmation_sent_at) { Date.today }
let(:email) do
create(:email, email: 'test@gitlab.com').tap do |email|
email.update!(confirmation_sent_at: confirmation_sent_at)
end
end
shared_examples_for 'unconfirmed email' do
it 'returns unconfirmed' do
expect(email.confirmed?).to be_falsey
end
end
context 'when the confirmation period has expired' do
let(:confirmation_sent_at) { expired_confirmation_sent_at }
it_behaves_like 'unconfirmed email'
it 'does not confirm the email' do
email.confirm
expect(email.confirmed?).to be_falsey
end
end
context 'when the confirmation period has not expired' do
let(:confirmation_sent_at) { extant_confirmation_sent_at }
it_behaves_like 'unconfirmed email'
it 'confirms the email' do
email.confirm
expect(email.confirmed?).to be_truthy
end
end
end
describe '#force_confirm' do
let(:expired_confirmation_sent_at) { Date.today - described_class.confirm_within - 7.days }
let(:extant_confirmation_sent_at) { Date.today }
let(:email) do
create(:email, email: 'test@gitlab.com').tap do |email|
email.update!(confirmation_sent_at: confirmation_sent_at)
end
end
shared_examples_for 'unconfirmed email' do
it 'returns unconfirmed' do
expect(email.confirmed?).to be_falsey
end
end
shared_examples_for 'confirms the email on force_confirm' do
it 'confirms an email' do
email.force_confirm
expect(email.reload.confirmed?).to be_truthy
end
end
context 'when the confirmation period has expired' do
let(:confirmation_sent_at) { expired_confirmation_sent_at }
it_behaves_like 'unconfirmed email'
it_behaves_like 'confirms the email on force_confirm'
end
context 'when the confirmation period has not expired' do
let(:confirmation_sent_at) { extant_confirmation_sent_at }
it_behaves_like 'unconfirmed email'
it_behaves_like 'confirms the email on force_confirm'
end
end
end

View File

@ -1481,27 +1481,176 @@ RSpec.describe User do
end
describe '#confirm' do
let(:expired_confirmation_sent_at) { Date.today - described_class.confirm_within - 7.days }
let(:extant_confirmation_sent_at) { Date.today }
before do
allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true)
end
let(:user) { create(:user, :unconfirmed, unconfirmed_email: 'test@gitlab.com') }
it 'returns unconfirmed' do
expect(user.confirmed?).to be_falsey
let(:user) do
create(:user, :unconfirmed, unconfirmed_email: 'test@gitlab.com').tap do |user|
user.update!(confirmation_sent_at: confirmation_sent_at)
end
end
it 'confirms a user' do
user.confirm
expect(user.confirmed?).to be_truthy
shared_examples_for 'unconfirmed user' do
it 'returns unconfirmed' do
expect(user.confirmed?).to be_falsey
end
end
it 'adds the confirmed primary email to emails' do
expect(user.emails.confirmed.map(&:email)).not_to include(user.email)
context 'when the confirmation period has expired' do
let(:confirmation_sent_at) { expired_confirmation_sent_at }
user.confirm
it_behaves_like 'unconfirmed user'
expect(user.emails.confirmed.map(&:email)).to include(user.email)
it 'does not confirm the user' do
user.confirm
expect(user.confirmed?).to be_falsey
end
it 'does not add the confirmed primary email to emails' do
user.confirm
expect(user.emails.confirmed.map(&:email)).not_to include(user.email)
end
end
context 'when the confirmation period has not expired' do
let(:confirmation_sent_at) { extant_confirmation_sent_at }
it_behaves_like 'unconfirmed user'
it 'confirms a user' do
user.confirm
expect(user.confirmed?).to be_truthy
end
it 'adds the confirmed primary email to emails' do
expect(user.emails.confirmed.map(&:email)).not_to include(user.email)
user.confirm
expect(user.emails.confirmed.map(&:email)).to include(user.email)
end
context 'when the primary email is already included in user.emails' do
let(:expired_confirmation_sent_at_for_email) { Date.today - Email.confirm_within - 7.days }
let(:extant_confirmation_sent_at_for_email) { Date.today }
let!(:email) do
create(:email, email: user.unconfirmed_email, user: user).tap do |email|
email.update!(confirmation_sent_at: confirmation_sent_at_for_email)
end
end
context 'when the confirmation period of the email record has expired' do
let(:confirmation_sent_at_for_email) { expired_confirmation_sent_at_for_email }
it 'does not confirm the email record' do
user.confirm
expect(email.reload.confirmed?).to be_falsey
end
end
context 'when the confirmation period of the email record has not expired' do
let(:confirmation_sent_at_for_email) { extant_confirmation_sent_at_for_email }
it 'confirms the email record' do
user.confirm
expect(email.reload.confirmed?).to be_truthy
end
end
end
end
end
describe '#force_confirm' do
let(:expired_confirmation_sent_at) { Date.today - described_class.confirm_within - 7.days }
let(:extant_confirmation_sent_at) { Date.today }
let(:user) do
create(:user, :unconfirmed, unconfirmed_email: 'test@gitlab.com').tap do |user|
user.update!(confirmation_sent_at: confirmation_sent_at)
end
end
shared_examples_for 'unconfirmed user' do
it 'returns unconfirmed' do
expect(user.confirmed?).to be_falsey
end
end
shared_examples_for 'confirms the user on force_confirm' do
it 'confirms a user' do
user.force_confirm
expect(user.confirmed?).to be_truthy
end
end
shared_examples_for 'adds the confirmed primary email to emails' do
it 'adds the confirmed primary email to emails' do
expect(user.emails.confirmed.map(&:email)).not_to include(user.email)
user.force_confirm
expect(user.emails.confirmed.map(&:email)).to include(user.email)
end
end
shared_examples_for 'confirms the email record if the primary email was already present in user.emails' do
context 'when the primary email is already included in user.emails' do
let(:expired_confirmation_sent_at_for_email) { Date.today - Email.confirm_within - 7.days }
let(:extant_confirmation_sent_at_for_email) { Date.today }
let!(:email) do
create(:email, email: user.unconfirmed_email, user: user).tap do |email|
email.update!(confirmation_sent_at: confirmation_sent_at_for_email)
end
end
shared_examples_for 'confirms the email record' do
it 'confirms the email record' do
user.force_confirm
expect(email.reload.confirmed?).to be_truthy
end
end
context 'when the confirmation period of the email record has expired' do
let(:confirmation_sent_at_for_email) { expired_confirmation_sent_at_for_email }
it_behaves_like 'confirms the email record'
end
context 'when the confirmation period of the email record has not expired' do
let(:confirmation_sent_at_for_email) { extant_confirmation_sent_at_for_email }
it_behaves_like 'confirms the email record'
end
end
end
context 'when the confirmation period has expired' do
let(:confirmation_sent_at) { expired_confirmation_sent_at }
it_behaves_like 'unconfirmed user'
it_behaves_like 'confirms the user on force_confirm'
it_behaves_like 'adds the confirmed primary email to emails'
it_behaves_like 'confirms the email record if the primary email was already present in user.emails'
end
context 'when the confirmation period has not expired' do
let(:confirmation_sent_at) { extant_confirmation_sent_at }
it_behaves_like 'unconfirmed user'
it_behaves_like 'confirms the user on force_confirm'
it_behaves_like 'adds the confirmed primary email to emails'
it_behaves_like 'confirms the email record if the primary email was already present in user.emails'
end
end

View File

@ -533,16 +533,10 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
context 'getting a blob' do
let_it_be(:blob) { create(:dependency_proxy_blob) }
let_it_be(:other_blob) { create(:dependency_proxy_blob) }
let(:path) { "/v2/#{group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" }
let(:other_path) { "/v2/#{other_group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" }
let(:blob_response) { { status: :success, blob: blob, from_cache: false } }
before do
allow_next_instance_of(DependencyProxy::FindOrCreateBlobService) do |instance|
allow(instance).to receive(:execute).and_return(blob_response)
end
end
let(:path) { "/v2/#{blob.group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" }
let(:other_path) { "/v2/#{other_blob.group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" }
it_behaves_like 'rate-limited token-authenticated requests'
end

View File

@ -1,59 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DependencyProxy::DownloadBlobService do
include DependencyProxyHelpers
let(:image) { 'alpine' }
let(:token) { Digest::SHA256.hexdigest('123') }
let(:blob_sha) { Digest::SHA256.hexdigest('ruby:2.7.0') }
subject(:download_blob) { described_class.new(image, blob_sha, token).execute }
context 'remote request is successful' do
before do
stub_blob_download(image, blob_sha)
end
it { expect(subject[:status]).to eq(:success) }
it { expect(subject[:file]).to be_a(Tempfile) }
it { expect(subject[:file].size).to eq(6) }
it 'streams the download' do
expected_options = { headers: anything, stream_body: true }
expect(Gitlab::HTTP).to receive(:perform_request).with(Net::HTTP::Get, anything, expected_options)
download_blob
end
it 'skips read_total_timeout', :aggregate_failures do
stub_const('GitLab::HTTP::DEFAULT_READ_TOTAL_TIMEOUT', 0)
expect(Gitlab::Metrics::System).not_to receive(:monotonic_time)
expect(download_blob).to include(status: :success)
end
end
context 'remote request is not found' do
before do
stub_blob_download(image, blob_sha, 404)
end
it { expect(subject[:status]).to eq(:error) }
it { expect(subject[:http_status]).to eq(404) }
it { expect(subject[:message]).to eq('Non-success response code on downloading blob fragment') }
end
context 'net timeout exception' do
before do
blob_url = DependencyProxy::Registry.blob_url(image, blob_sha)
stub_full_request(blob_url).to_timeout
end
it { expect(subject[:status]).to eq(:error) }
it { expect(subject[:http_status]).to eq(599) }
it { expect(subject[:message]).to eq('execution expired') }
end
end

View File

@ -1,71 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DependencyProxy::FindOrCreateBlobService do
include DependencyProxyHelpers
let_it_be_with_reload(:blob) { create(:dependency_proxy_blob) }
let(:group) { blob.group }
let(:image) { 'alpine' }
let(:tag) { '3.9' }
let(:token) { Digest::SHA256.hexdigest('123') }
let(:blob_sha) { '40bd001563085fc35165329ea1ff5c5ecbdbbeef' }
subject { described_class.new(group, image, token, blob_sha).execute }
before do
stub_registry_auth(image, token)
end
shared_examples 'downloads the remote blob' do
it 'downloads blob from remote registry if there is no cached one' do
expect(subject[:status]).to eq(:success)
expect(subject[:blob]).to be_a(DependencyProxy::Blob)
expect(subject[:blob]).to be_persisted
expect(subject[:from_cache]).to eq false
end
end
context 'no cache' do
before do
stub_blob_download(image, blob_sha)
end
it_behaves_like 'downloads the remote blob'
end
context 'cached blob' do
let(:blob_sha) { blob.file_name.sub('.gz', '') }
it 'uses cached blob instead of downloading one' do
expect { subject }.to change { blob.reload.read_at }
expect(subject[:status]).to eq(:success)
expect(subject[:blob]).to be_a(DependencyProxy::Blob)
expect(subject[:blob]).to eq(blob)
expect(subject[:from_cache]).to eq true
end
context 'when the cached blob is expired' do
before do
blob.update_column(:status, DependencyProxy::Blob.statuses[:expired])
stub_blob_download(image, blob_sha)
end
it_behaves_like 'downloads the remote blob'
end
end
context 'no such blob exists remotely' do
before do
stub_blob_download(image, blob_sha, 404)
end
it 'returns error message and http status' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to eq('Failed to download the blob')
expect(subject[:http_status]).to eq(404)
end
end
end

View File

@ -131,32 +131,82 @@ RSpec.describe Projects::UpdateRemoteMirrorService do
expect_next_instance_of(Lfs::PushService) do |service|
expect(service).to receive(:execute)
end
expect(Gitlab::AppJsonLogger).not_to receive(:info)
execute!
expect(remote_mirror.update_status).to eq('finished')
expect(remote_mirror.last_error).to be_nil
end
it 'does nothing to an SSH repository' do
remote_mirror.update!(url: 'ssh://example.com')
context 'when LFS objects fail to push' do
before do
expect_next_instance_of(Lfs::PushService) do |service|
expect(service).to receive(:execute).and_return({ status: :error, message: 'unauthorized' })
end
end
expect_any_instance_of(Lfs::PushService).not_to receive(:execute)
context 'when remote_mirror_fail_on_lfs feature flag enabled' do
it 'fails update' do
expect(Gitlab::AppJsonLogger).to receive(:info).with(
hash_including(message: "Error synching remote mirror")).and_call_original
execute!
execute!
expect(remote_mirror.update_status).to eq('failed')
expect(remote_mirror.last_error).to eq("Error synchronizing LFS files:\n\nunauthorized\n\n")
end
end
context 'when remote_mirror_fail_on_lfs feature flag is disabled' do
before do
stub_feature_flags(remote_mirror_fail_on_lfs: false)
end
it 'does not fail update' do
expect(Gitlab::AppJsonLogger).to receive(:info).with(
hash_including(message: "Error synching remote mirror")).and_call_original
execute!
expect(remote_mirror.update_status).to eq('finished')
expect(remote_mirror.last_error).to be_nil
end
end
end
it 'does nothing if LFS is disabled' do
expect(project).to receive(:lfs_enabled?) { false }
context 'with SSH repository' do
let(:ssh_mirror) { create(:remote_mirror, project: project, enabled: true) }
expect_any_instance_of(Lfs::PushService).not_to receive(:execute)
before do
allow(ssh_mirror)
.to receive(:update_repository)
.and_return(double(divergent_refs: []))
end
execute!
end
it 'does nothing to an SSH repository' do
ssh_mirror.update!(url: 'ssh://example.com')
it 'does nothing if non-password auth is specified' do
remote_mirror.update!(auth_method: 'ssh_public_key')
expect_any_instance_of(Lfs::PushService).not_to receive(:execute)
expect_any_instance_of(Lfs::PushService).not_to receive(:execute)
service.execute(ssh_mirror, retries)
end
execute!
it 'does nothing if LFS is disabled' do
expect(project).to receive(:lfs_enabled?) { false }
expect_any_instance_of(Lfs::PushService).not_to receive(:execute)
service.execute(ssh_mirror, retries)
end
it 'does nothing if non-password auth is specified' do
ssh_mirror.update!(auth_method: 'ssh_public_key')
expect_any_instance_of(Lfs::PushService).not_to receive(:execute)
service.execute(ssh_mirror, retries)
end
end
end
end

View File

@ -0,0 +1,107 @@
# frozen_string_literal: true
RSpec.shared_examples 'labels sidebar widget' do
context 'editing labels' do
let_it_be(:development) { create(:group_label, group: group, name: 'Development') }
let_it_be(:stretch) { create(:label, project: project, name: 'Stretch') }
let_it_be(:xss_label) { create(:label, project: project, title: '&lt;script&gt;alert("xss");&lt;&#x2F;script&gt;') }
let(:labels_widget) { find('[data-testid="sidebar-labels"]') }
before do
page.within(labels_widget) do
click_on 'Edit'
end
wait_for_all_requests
end
it 'shows labels list in the dropdown' do
expect(labels_widget.find('.gl-new-dropdown-contents')).to have_selector('li.gl-new-dropdown-item', count: 4)
end
it 'adds a label' do
within(labels_widget) do
adds_label(stretch)
page.within('[data-testid="value-wrapper"]') do
expect(page).to have_content(stretch.name)
end
end
end
it 'removes a label' do
within(labels_widget) do
adds_label(stretch)
page.within('[data-testid="value-wrapper"]') do
expect(page).to have_content(stretch.name)
end
click_on 'Remove label'
wait_for_requests
page.within('[data-testid="value-wrapper"]') do
expect(page).not_to have_content(stretch.name)
end
end
end
it 'escapes XSS when viewing issuable labels' do
page.within(labels_widget) do
expect(page).to have_content '<script>alert("xss");</script>'
end
end
it 'shows option to create a label' do
page.within(labels_widget) do
expect(page).to have_content 'Create'
end
end
context 'creating a label', :js do
before do
page.within(labels_widget) do
page.find('[data-testid="create-label-button"]').click
end
end
it 'shows dropdown switches to "create label" section' do
page.within(labels_widget) do
expect(page.find('[data-testid="dropdown-header"]')).to have_content 'Create'
end
end
it 'creates new label' do
page.within(labels_widget) do
fill_in 'Name new label', with: 'wontfix'
page.find('.suggest-colors a', match: :first).click
page.find('button', text: 'Create').click
wait_for_requests
expect(page).to have_content 'wontfix'
end
end
it 'shows error message if label title is taken' do
page.within(labels_widget) do
fill_in 'Name new label', with: development.title
page.find('.suggest-colors a', match: :first).click
page.find('button', text: 'Create').click
wait_for_requests
page.within('.dropdown-input') do
expect(page.find('.gl-alert')).to have_content 'Title'
end
end
end
end
end
def adds_label(label)
click_button label.name
click_button 'Close'
wait_for_requests
end
end

View File

@ -50,6 +50,10 @@ RSpec.shared_examples 'issue boards sidebar' do
it_behaves_like 'date sidebar widget'
end
context 'editing issue labels', :js do
it_behaves_like 'labels sidebar widget'
end
context 'in notifications subscription' do
it 'displays notifications toggle', :aggregate_failures do
page.within('[data-testid="sidebar-notifications"]') do