Add latest changes from gitlab-org/security/gitlab@14-10-stable-ee

This commit is contained in:
GitLab Bot 2022-06-29 14:14:22 +00:00
parent 9fdc4213b6
commit d547692176
41 changed files with 497 additions and 132 deletions

View File

@ -1,4 +1,4 @@
export const projectKeys = ['name', 'organizationName', 'organizationSlug', 'slug'];
export const projectKeys = ['id', 'name', 'organizationName', 'organizationSlug', 'slug'];
export const transformFrontendSettings = ({
apiHost,
@ -9,6 +9,7 @@ export const transformFrontendSettings = ({
}) => {
const project = selectedProject
? {
sentry_project_id: selectedProject.id,
slug: selectedProject.slug,
name: selectedProject.name,
organization_name: selectedProject.organizationName,

View File

@ -537,7 +537,7 @@ export default class AccessDropdown {
return `
<li>
<a href="#" class="${isActiveClass}">
<strong>${key.title}</strong>
<strong>${escape(key.title)}</strong>
<p>
${sprintf(
__('Owned by %{image_tag}'),

View File

@ -4,6 +4,7 @@ class Projects::ErrorTrackingController < Projects::ErrorTracking::BaseControlle
respond_to :json
before_action :authorize_read_sentry_issue!
before_action :authorize_update_sentry_issue!, only: %i[update]
before_action :set_issue_id, only: :details
before_action only: [:index] do

View File

@ -143,7 +143,7 @@ module Projects
:integrated,
:api_host,
:token,
project: [:slug, :name, :organization_slug, :organization_name]
project: [:slug, :name, :organization_slug, :organization_name, :sentry_project_id]
],
grafana_integration_attributes: [:token, :grafana_url, :enabled],

View File

@ -159,27 +159,6 @@ module IntegrationsHelper
!Gitlab.com?
end
def jira_issue_breadcrumb_link(issue_reference)
link_to '', { class: 'gl-display-flex gl-align-items-center gl-white-space-nowrap' } do
icon = image_tag image_path('illustrations/logos/jira.svg'), width: 15, height: 15, class: 'gl-mr-2'
[icon, html_escape(issue_reference)].join.html_safe
end
end
def zentao_issue_breadcrumb_link(issue)
link_to issue[:web_url], { target: '_blank', rel: 'noopener noreferrer', class: 'gl-display-flex gl-align-items-center gl-white-space-nowrap' } do
icon = image_tag image_path('logos/zentao.svg'), width: 15, height: 15, class: 'gl-mr-2'
[icon, html_escape(issue[:id])].join.html_safe
end
end
def zentao_issues_show_data
{
issues_show_path: project_integrations_zentao_issue_path(@project, params[:id], format: :json),
issues_list_path: project_integrations_zentao_issues_path(@project)
}
end
extend self
private

View File

@ -298,6 +298,7 @@ module ProjectsHelper
setting.organization_slug.blank?
{
sentry_project_id: setting.sentry_project_id,
name: setting.project_name,
organization_name: setting.organization_name,
organization_slug: setting.organization_slug,

View File

@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
VERSION = '0.39.0'
VERSION = '0.39.2'
self.table_name = 'clusters_applications_runners'

View File

@ -125,17 +125,22 @@ module ErrorTracking
def issue_details(opts = {})
with_reactive_cache('issue_details', opts.stringify_keys) do |result|
ensure_issue_belongs_to_project!(result[:issue].project_id)
result
end
end
def issue_latest_event(opts = {})
with_reactive_cache('issue_latest_event', opts.stringify_keys) do |result|
ensure_issue_belongs_to_project!(result[:latest_event].project_id)
result
end
end
def update_issue(opts = {})
issue_to_be_updated = sentry_client.issue_details(issue_id: opts[:issue_id])
ensure_issue_belongs_to_project!(issue_to_be_updated.project_id)
handle_exceptions do
{ updated: sentry_client.update_issue(opts) }
end
@ -177,6 +182,25 @@ module ErrorTracking
private
def ensure_issue_belongs_to_project!(project_id_from_api)
raise 'The Sentry issue appers to be outside of the configured Sentry project' if Integer(project_id_from_api) != ensure_sentry_project_id!
end
def ensure_sentry_project_id!
return sentry_project_id if sentry_project_id.present?
raise("Couldn't find project: #{organization_name} / #{project_name} on Sentry") if sentry_project.nil?
update!(sentry_project_id: sentry_project.id)
sentry_project_id
end
def sentry_project
strong_memoize(:sentry_project) do
sentry_client.projects.find { |project| project.name == project_name && project.organization_name == organization_name }
end
end
def add_gitlab_issue_details(issue)
issue.gitlab_commit = match_gitlab_commit(issue.first_release_version)
issue.gitlab_commit_path = project_commit_path(project, issue.gitlab_commit) if issue.gitlab_commit

View File

@ -297,7 +297,6 @@ class ProjectPolicy < BasePolicy
enable :read_deployment
enable :read_merge_request
enable :read_sentry_issue
enable :update_sentry_issue
enable :read_prometheus
enable :read_metrics_dashboard_annotation
enable :metrics_dashboard
@ -413,6 +412,7 @@ class ProjectPolicy < BasePolicy
enable :admin_feature_flags_user_lists
enable :update_escalation_status
enable :read_secure_files
enable :update_sentry_issue
end
rule { can?(:developer_access) & user_confirmed? }.policy do

View File

@ -16,7 +16,7 @@ class MemberUserEntity < UserEntity
user.blocked?
end
expose :two_factor_enabled do |user|
expose :two_factor_enabled, if: -> (user) { current_user_can_manage_members? || current_user?(user) } do |user|
user.two_factor_enabled?
end
@ -25,6 +25,18 @@ class MemberUserEntity < UserEntity
user.status.emoji
end
end
private
def current_user_can_manage_members?
return false unless options[:source]
Ability.allowed?(options[:current_user], :"admin_#{options[:source].to_ability_name}_member", options[:source])
end
def current_user?(user)
options[:current_user] == user
end
end
MemberUserEntity.prepend_mod_with('MemberUserEntity')

View File

@ -90,7 +90,8 @@ module Projects
api_url: api_url,
enabled: settings[:enabled],
project_name: settings.dig(:project, :name),
organization_name: settings.dig(:project, :organization_name)
organization_name: settings.dig(:project, :organization_name),
sentry_project_id: settings.dig(:project, :sentry_project_id)
}
}
params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddSentryProjectIdToProjectErrorTrackingSettings < Gitlab::Database::Migration[2.0]
def change
add_column :project_error_tracking_settings, :sentry_project_id, :bigint
end
end

View File

@ -0,0 +1 @@
1f03beba0775e2a4eead512819592f590b02b70096cee250dfcdf426440cb5f5

View File

@ -19156,7 +19156,8 @@ CREATE TABLE project_error_tracking_settings (
encrypted_token_iv character varying,
project_name character varying,
organization_name character varying,
integrated boolean DEFAULT true NOT NULL
integrated boolean DEFAULT true NOT NULL,
sentry_project_id bigint
);
CREATE TABLE project_export_jobs (

View File

@ -106,7 +106,7 @@ button and a link to the GitLab issue displays within the error detail section.
## Taking Action on errors
You can take action on Sentry Errors from within the GitLab UI.
You can take action on Sentry Errors from within the GitLab UI. Marking errors ignored or resolved require at least Developer role.
### Ignoring errors

View File

@ -82,8 +82,14 @@ module API
params.delete(:label_id)
params.delete(:name)
label = ::Labels::UpdateService.new(declared_params(include_missing: false)).execute(label)
render_validation_error!(label) unless label.valid?
update_params = declared_params(include_missing: false)
if update_params.present?
authorize! :admin_label, label
label = ::Labels::UpdateService.new(update_params).execute(label)
render_validation_error!(label) unless label.valid?
end
if parent.is_a?(Project) && update_priority
if priority.nil?
@ -97,10 +103,10 @@ module API
end
def delete_label(parent)
authorize! :admin_label, parent
label = find_label(parent, params_id_or_title, include_ancestor_groups: false)
authorize! :admin_label, label
destroy_conditionally!(label)
end

View File

@ -10,20 +10,8 @@ module BulkImports
<<-'GRAPHQL'
query($full_path: ID!) {
project(fullPath: $full_path) {
description
visibility
archived
created_at: createdAt
shared_runners_enabled: sharedRunnersEnabled
container_registry_enabled: containerRegistryEnabled
only_allow_merge_if_pipeline_succeeds: onlyAllowMergeIfPipelineSucceeds
only_allow_merge_if_all_discussions_are_resolved: onlyAllowMergeIfAllDiscussionsAreResolved
request_access_enabled: requestAccessEnabled
printing_merge_request_link_enabled: printingMergeRequestLinkEnabled
remove_source_branch_after_merge: removeSourceBranchAfterMerge
autoclose_referenced_issues: autocloseReferencedIssues
suggestion_commit_message: suggestionCommitMessage
wiki_enabled: wikiEnabled
}
}
GRAPHQL

View File

@ -7,16 +7,18 @@ module BulkImports
PROJECT_IMPORT_TYPE = 'gitlab_project_migration'
def transform(context, data)
project = {}
entity = context.entity
visibility = data.delete('visibility')
data['name'] = entity.destination_name
data['path'] = entity.destination_name.parameterize
data['import_type'] = PROJECT_IMPORT_TYPE
data['visibility_level'] = Gitlab::VisibilityLevel.string_options[visibility] if visibility.present?
data['namespace_id'] = Namespace.find_by_full_path(entity.destination_namespace)&.id if entity.destination_namespace.present?
project[:name] = entity.destination_name
project[:path] = entity.destination_name.parameterize
project[:created_at] = data['created_at']
project[:import_type] = PROJECT_IMPORT_TYPE
project[:visibility_level] = Gitlab::VisibilityLevel.string_options[visibility] if visibility.present?
project[:namespace_id] = Namespace.find_by_full_path(entity.destination_namespace)&.id if entity.destination_namespace.present?
data.transform_keys!(&:to_sym)
project
end
end
end

View File

@ -15,6 +15,7 @@ module ErrorTracking
stack_trace = parse_stack_trace(event)
Gitlab::ErrorTracking::ErrorEvent.new(
project_id: event['projectID'],
issue_id: event['groupID'],
date_received: event['dateReceived'],
stack_trace_entries: stack_trace

View File

@ -7,7 +7,7 @@ module Gitlab
class ErrorEvent
include ActiveModel::Model
attr_accessor :issue_id, :date_received, :stack_trace_entries, :gitlab_project
attr_accessor :issue_id, :date_received, :stack_trace_entries, :gitlab_project, :project_id
def self.declarative_policy_class
'ErrorTracking::BasePolicy'

View File

@ -8,6 +8,8 @@ module Gitlab
DEFAULT_MAX_BYTES = 10.gigabytes.freeze
TIMEOUT_LIMIT = 210.seconds
ServiceError = Class.new(StandardError)
def initialize(archive_path:, max_bytes: self.class.max_bytes)
@archive_path = archive_path
@max_bytes = max_bytes
@ -29,6 +31,8 @@ module Gitlab
pgrp = nil
valid_archive = true
validate_archive_path
Timeout.timeout(TIMEOUT_LIMIT) do
stdin, stdout, stderr, wait_thr = Open3.popen3(command, pgroup: true)
stdin.close
@ -78,15 +82,29 @@ module Gitlab
false
end
def validate_archive_path
Gitlab::Utils.check_path_traversal!(@archive_path)
raise(ServiceError, 'Archive path is not a string') unless @archive_path.is_a?(String)
raise(ServiceError, 'Archive path is a symlink') if File.lstat(@archive_path).symlink?
raise(ServiceError, 'Archive path is not a file') unless File.file?(@archive_path)
end
def command
"gzip -dc #{@archive_path} | wc -c"
end
def log_error(error)
archive_size = begin
File.size(@archive_path)
rescue StandardError
nil
end
Gitlab::Import::Logger.info(
message: error,
import_upload_archive_path: @archive_path,
import_upload_archive_size: File.size(@archive_path)
import_upload_archive_size: archive_size
)
end
end

View File

@ -297,40 +297,54 @@ RSpec.describe Projects::ErrorTrackingController do
put :update, params: issue_params(issue_id: issue_id, status: 'resolved', format: :json)
end
before do
expect(ErrorTracking::IssueUpdateService)
.to receive(:new).with(project, user, permitted_params)
.and_return(issue_update_service)
end
describe 'format json' do
context 'update result is successful' do
context 'when user is a reporter' do
before do
expect(issue_update_service).to receive(:execute)
.and_return(status: :success, updated: true, closed_issue_iid: non_existing_record_iid)
update_issue
project.add_reporter(user)
end
it 'returns a success' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('error_tracking/update_issue')
it 'returns 404 error' do
update_issue
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'update result is erroneous' do
let(:error_message) { 'error message' }
context 'when user has access to update' do
before do
expect(issue_update_service).to receive(:execute)
.and_return(status: :error, message: error_message)
update_issue
expect(ErrorTracking::IssueUpdateService)
.to receive(:new).with(project, user, permitted_params)
.and_return(issue_update_service)
end
it 'returns 400 with message' do
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq(error_message)
context 'when update result is successful' do
before do
expect(issue_update_service).to receive(:execute)
.and_return(status: :success, updated: true, closed_issue_iid: non_existing_record_iid)
update_issue
end
it 'returns a success' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('error_tracking/update_issue')
end
end
context 'update result is erroneous' do
let(:error_message) { 'error message' }
before do
expect(issue_update_service).to receive(:execute)
.and_return(status: :error, message: error_message)
update_issue
end
it 'returns 400 with message' do
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq(error_message)
end
end
end
end

View File

@ -72,6 +72,7 @@ RSpec.describe 'Database schema' do
oauth_applications: %w[owner_id],
product_analytics_events_experimental: %w[event_id txn_id user_id],
project_build_artifacts_size_refreshes: %w[last_job_artifact_id],
project_error_tracking_settings: %w[sentry_project_id],
project_group_links: %w[group_id],
project_statistics: %w[namespace_id],
projects: %w[creator_id ci_id mirror_user_id],

View File

@ -13,7 +13,7 @@ FactoryBot.define do
message { 'message' }
culprit { 'culprit' }
external_url { 'http://example.com/id' }
project_id { 'project1' }
project_id { '111111' }
project_name { 'project name' }
project_slug { 'project_name' }
short_id { 'ID' }

View File

@ -9,6 +9,7 @@ FactoryBot.define do
project_name { 'Sentry Project' }
organization_name { 'Sentry Org' }
integrated { false }
sentry_project_id { 10 }
trait :disabled do
enabled { false }

View File

@ -53,7 +53,7 @@
},
"user": {
"allOf": [
{ "$ref": "member_user.json" }
{ "$ref": "member_user_default.json" }
]
},
"state": { "type": "integer" },

View File

@ -0,0 +1,35 @@
{
"type": "object",
"required": [
"id",
"name",
"username",
"created_at",
"last_activity_on",
"avatar_url",
"web_url",
"blocked",
"show_status"
],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"username": { "type": "string" },
"created_at": { "type": ["string"] },
"avatar_url": { "type": ["string", "null"] },
"web_url": { "type": "string" },
"blocked": { "type": "boolean" },
"two_factor_enabled": { "type": "boolean" },
"availability": { "type": ["string", "null"] },
"last_activity_on": { "type": ["string", "null"] },
"status": {
"type": "object",
"required": ["emoji"],
"properties": {
"emoji": { "type": "string" }
},
"additionalProperties": false
},
"show_status": { "type": "boolean" }
}
}

View File

@ -154,4 +154,21 @@ describe('AccessDropdown', () => {
expect(template).not.toContain(user.name);
});
});
describe('deployKeyRowHtml', () => {
const deployKey = {
id: 1,
title: 'title <script>alert(document.domain)</script>',
fullname: 'fullname <script>alert(document.domain)</script>',
avatar_url: '',
username: '',
};
it('escapes deploy key title and fullname', () => {
const template = dropdown.deployKeyRowHtml(deployKey);
expect(template).not.toContain(deployKey.title);
expect(template).not.toContain(deployKey.fullname);
});
});
});

View File

@ -149,19 +149,4 @@ RSpec.describe IntegrationsHelper do
end
end
end
describe '#jira_issue_breadcrumb_link' do
let(:issue_reference) { nil }
subject { helper.jira_issue_breadcrumb_link(issue_reference) }
context 'when issue_reference contains HTML' do
let(:issue_reference) { "<script>alert('XSS')</script>" }
it 'escapes issue reference' do
is_expected.not_to include(issue_reference)
is_expected.to include(html_escape(issue_reference))
end
end
end
end

View File

@ -52,6 +52,7 @@ RSpec.describe ProjectsHelper do
context 'api_url present' do
let(:json) do
{
sentry_project_id: error_tracking_setting.sentry_project_id,
name: error_tracking_setting.project_name,
organization_name: error_tracking_setting.organization_name,
organization_slug: error_tracking_setting.organization_slug,

View File

@ -25,18 +25,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do
let(:project_data) do
{
'visibility' => 'private',
'created_at' => 10.days.ago,
'archived' => false,
'shared_runners_enabled' => true,
'container_registry_enabled' => true,
'only_allow_merge_if_pipeline_succeeds' => true,
'only_allow_merge_if_all_discussions_are_resolved' => true,
'request_access_enabled' => true,
'printing_merge_request_link_enabled' => true,
'remove_source_branch_after_merge' => true,
'autoclose_referenced_issues' => true,
'suggestion_commit_message' => 'message',
'wiki_enabled' => true
'created_at' => '2016-08-12T09:41:03'
}
end
@ -58,17 +47,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do
expect(imported_project).not_to be_nil
expect(imported_project.group).to eq(group)
expect(imported_project.suggestion_commit_message).to eq('message')
expect(imported_project.archived?).to eq(project_data['archived'])
expect(imported_project.shared_runners_enabled?).to eq(project_data['shared_runners_enabled'])
expect(imported_project.container_registry_enabled?).to eq(project_data['container_registry_enabled'])
expect(imported_project.only_allow_merge_if_pipeline_succeeds?).to eq(project_data['only_allow_merge_if_pipeline_succeeds'])
expect(imported_project.only_allow_merge_if_all_discussions_are_resolved?).to eq(project_data['only_allow_merge_if_all_discussions_are_resolved'])
expect(imported_project.request_access_enabled?).to eq(project_data['request_access_enabled'])
expect(imported_project.printing_merge_request_link_enabled?).to eq(project_data['printing_merge_request_link_enabled'])
expect(imported_project.remove_source_branch_after_merge?).to eq(project_data['remove_source_branch_after_merge'])
expect(imported_project.autoclose_referenced_issues?).to eq(project_data['autoclose_referenced_issues'])
expect(imported_project.wiki_enabled?).to eq(project_data['wiki_enabled'])
expect(imported_project.visibility).to eq(project_data['visibility'])
expect(imported_project.created_at).to eq(project_data['created_at'])
end
end

View File

@ -25,8 +25,8 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
let(:data) do
{
'name' => 'source_name',
'visibility' => 'private'
'visibility' => 'private',
'created_at' => '2016-11-18T09:29:42.634Z'
}
end
@ -76,8 +76,21 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
end
end
it 'converts all keys to symbols' do
expect(transformed_data.keys).to contain_exactly(:name, :path, :import_type, :visibility_level, :namespace_id)
context 'when data has extra keys' do
it 'returns a fixed number of keys' do
data = {
'visibility' => 'private',
'created_at' => '2016-11-18T09:29:42.634Z',
'my_key' => 'my_key',
'another_key' => 'another_key',
'last_key' => 'last_key'
}
transformed_data = described_class.new.transform(context, data)
expect(transformed_data.keys)
.to contain_exactly(:created_at, :import_type, :name, :namespace_id, :path, :visibility_level)
end
end
end
end

View File

@ -86,6 +86,65 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
include_examples 'logs raised exception and terminates validator process group'
end
end
context 'archive path validation' do
let(:filesize) { nil }
before do
expect(Gitlab::Import::Logger)
.to receive(:info)
.with(
import_upload_archive_path: filepath,
import_upload_archive_size: filesize,
message: error_message
)
end
context 'when archive path is traversed' do
let(:filepath) { '/foo/../bar' }
let(:error_message) { 'Invalid path' }
it 'returns false' do
expect(subject.valid?).to eq(false)
end
end
context 'when archive path is not a string' do
let(:filepath) { 123 }
let(:error_message) { 'Archive path is not a string' }
it 'returns false' do
expect(subject.valid?).to eq(false)
end
end
context 'which archive path is a symlink' do
let(:filepath) { File.join(Dir.tmpdir, 'symlink') }
let(:error_message) { 'Archive path is a symlink' }
before do
FileUtils.ln_s(filepath, filepath, force: true)
end
it 'returns false' do
expect(subject.valid?).to eq(false)
end
end
context 'when archive path is not a file' do
let(:filepath) { Dir.mktmpdir }
let(:filesize) { File.size(filepath) }
let(:error_message) { 'Archive path is not a file' }
after do
FileUtils.rm_rf(filepath)
end
it 'returns false' do
expect(subject.valid?).to eq(false)
end
end
end
end
def create_compressed_file

View File

@ -10,7 +10,9 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
let(:sentry_client) { instance_double(ErrorTracking::SentryClient) }
subject(:setting) { build(:project_error_tracking_setting, project: project) }
let(:sentry_project_id) { 10 }
subject(:setting) { build(:project_error_tracking_setting, project: project, sentry_project_id: sentry_project_id) }
describe 'Associations' do
it { is_expected.to belong_to(:project) }
@ -270,7 +272,7 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
end
describe '#issue_details' do
let(:issue) { build(:error_tracking_sentry_detailed_error) }
let(:issue) { build(:error_tracking_sentry_detailed_error, project_id: sentry_project_id) }
let(:commit_id) { issue.first_release_version }
let(:result) { subject.issue_details(opts) }
let(:opts) { { issue_id: 1 } }
@ -317,12 +319,33 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
end
end
describe '#issue_latest_event' do
let(:error_event) { build(:error_tracking_sentry_error_event, project_id: sentry_project_id) }
let(:result) { subject.issue_latest_event(opts) }
let(:opts) { { issue_id: 1 } }
before do
stub_reactive_cache(subject, error_event, {})
synchronous_reactive_cache(subject)
allow(subject).to receive(:sentry_client).and_return(sentry_client)
allow(sentry_client).to receive(:issue_latest_event).with(opts).and_return(error_event)
end
it 'returns the error event' do
expect(result[:latest_event].project_id).to eq(sentry_project_id)
end
end
describe '#update_issue' do
let(:result) { subject.update_issue(**opts) }
let(:opts) { { issue_id: 1, params: {} } }
before do
allow(subject).to receive(:sentry_client).and_return(sentry_client)
allow(sentry_client).to receive(:issue_details)
.with({ issue_id: 1 })
.and_return(Gitlab::ErrorTracking::DetailedError.new(project_id: sentry_project_id))
end
context 'when sentry response is successful' do
@ -344,6 +367,56 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
expect(result).to eq(error: 'Unexpected Error')
end
end
context 'when sentry_project_id is not set' do
let(:sentry_projects) do
[
Gitlab::ErrorTracking::Project.new(
id: 1111,
name: 'Some Project',
organization_name: 'Org'
),
Gitlab::ErrorTracking::Project.new(
id: sentry_project_id,
name: setting.project_name,
organization_name: setting.organization_name
)
]
end
context 'when sentry_project_id is not set' do
before do
setting.update!(sentry_project_id: nil)
allow(sentry_client).to receive(:projects).and_return(sentry_projects)
allow(sentry_client).to receive(:update_issue).with(opts).and_return(true)
end
it 'tries to backfill it from sentry API' do
expect(result).to eq(updated: true)
expect(setting.reload.sentry_project_id).to eq(sentry_project_id)
end
context 'when the project cannot be found on sentry' do
before do
sentry_projects.pop
end
it 'raises error' do
expect { result }.to raise_error(/Couldn't find project/)
end
end
end
context 'when mismatching sentry_project_id is detected' do
it 'raises error' do
setting.update!(sentry_project_id: sentry_project_id + 1)
expect { result }.to raise_error(/The Sentry issue appers to be outside/)
end
end
end
end
describe 'slugs' do

View File

@ -2102,4 +2102,25 @@ RSpec.describe ProjectPolicy do
it { is_expected.to be_disallowed(:register_project_runners) }
end
end
describe 'update_sentry_issue' do
using RSpec::Parameterized::TableSyntax
where(:role, :allowed) do
:owner | true
:maintainer | true
:developer | true
:reporter | false
:guest | false
end
let(:project) { public_project }
let(:current_user) { public_send(role) }
with_them do
it do
expect(subject.can?(:update_sentry_issue)).to be(allowed)
end
end
end
end

View File

@ -483,6 +483,29 @@ RSpec.describe API::Labels do
let(:request) { api("/projects/#{project.id}/labels", user) }
let(:params) { { name: valid_label_title_1 } }
end
context 'with group label' do
let_it_be(:group) { create(:group) }
let_it_be(:group_label) { create(:group_label, title: valid_group_label_title_1, group: group) }
before do
project.update!(group: group)
end
it 'returns 401 if user does not have access' do
delete api("/projects/#{project.id}/labels/#{group_label.id}", user)
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'returns 204 if user has access' do
group.add_developer(user)
delete api("/projects/#{project.id}/labels/#{group_label.id}", user)
expect(response).to have_gitlab_http_status(:no_content)
end
end
end
describe 'PUT /projects/:id/labels' do
@ -537,6 +560,44 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:bad_request)
end
context 'with group label' do
let_it_be(:group) { create(:group) }
let_it_be(:group_label) { create(:group_label, title: valid_group_label_title_1, group: group) }
before do
project.update!(group: group)
end
it 'allows updating of group label priority' do
put api("/projects/#{project.id}/labels/#{group_label.id}", user), params: { priority: 5 }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['priority']).to eq(5)
end
it 'returns 401 when updating other fields' do
put api("/projects/#{project.id}/labels/#{group_label.id}", user), params: {
priority: 5,
new_name: 'new label name'
}
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'returns 200 when user has access to the group label' do
group.add_developer(user)
put api("/projects/#{project.id}/labels/#{group_label.id}", user), params: {
priority: 5,
new_name: 'new label name'
}
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['priority']).to eq(5)
expect(json_response['name']).to eq('new label name')
end
end
end
describe 'PUT /projects/:id/labels/promote' do

View File

@ -11,7 +11,7 @@ RSpec.describe MemberUserEntity do
let(:entity_hash) { entity.as_json }
it 'matches json schema' do
expect(entity.to_json).to match_schema('entities/member_user')
expect(entity.to_json).to match_schema('entities/member_user_default')
end
it 'correctly exposes `avatar_url`' do
@ -27,10 +27,8 @@ RSpec.describe MemberUserEntity do
expect(entity_hash[:blocked]).to be(true)
end
it 'correctly exposes `two_factor_enabled`' do
allow(user).to receive(:two_factor_enabled?).and_return(true)
expect(entity_hash[:two_factor_enabled]).to be(true)
it 'does not expose `two_factor_enabled` by default' do
expect(entity_hash[:two_factor_enabled]).to be(nil)
end
it 'correctly exposes `status.emoji`' do
@ -44,4 +42,66 @@ RSpec.describe MemberUserEntity do
it 'correctly exposes `last_activity_on`' do
expect(entity_hash[:last_activity_on]).to be(user.last_activity_on)
end
context 'when options includes a source' do
let(:current_user) { create(:user) }
let(:options) { { current_user: current_user, source: source } }
let(:entity) { described_class.new(user, options) }
shared_examples 'correctly exposes user two_factor_enabled' do
context 'when the current_user has a role lower than minimum manage member role' do
before do
source.add_user(current_user, Gitlab::Access::DEVELOPER)
end
it 'does not expose user two_factor_enabled' do
expect(entity_hash[:two_factor_enabled]).to be(nil)
end
it 'matches json schema' do
expect(entity.to_json).to match_schema('entities/member_user_default')
end
end
context 'when the current user has a minimum manage member role or higher' do
before do
source.add_user(current_user, minimum_manage_member_role)
end
it 'matches json schema' do
expect(entity.to_json).to match_schema('entities/member_user_for_admin_member')
end
it 'exposes user two_factor_enabled' do
expect(entity_hash[:two_factor_enabled]).to be(false)
end
end
context 'when the current user is self' do
let(:current_user) { user }
it 'exposes user two_factor_enabled' do
expect(entity_hash[:two_factor_enabled]).to be(false)
end
it 'matches json schema' do
expect(entity.to_json).to match_schema('entities/member_user_for_admin_member')
end
end
end
context 'when the source is a group' do
let(:source) { create(:group) }
let(:minimum_manage_member_role) { Gitlab::Access::OWNER }
it_behaves_like 'correctly exposes user two_factor_enabled'
end
context 'when the source is a project' do
let(:source) { create(:project) }
let(:minimum_manage_member_role) { Gitlab::Access::MAINTAINER }
it_behaves_like 'correctly exposes user two_factor_enabled'
end
end
end

View File

@ -80,7 +80,8 @@ RSpec.describe BulkImports::FileDecompressionService do
subject { described_class.new(tmpdir: tmpdir, filename: 'symlink.gz') }
it 'raises an error and removes the file' do
expect { subject.execute }.to raise_error(described_class::ServiceError, 'Invalid file')
expect { subject.execute }
.to raise_error(BulkImports::FileDecompressionService::ServiceError, 'File decompression error')
expect(File.exist?(symlink)).to eq(false)
end

View File

@ -294,7 +294,7 @@ RSpec.describe Issues::CreateService do
context 'user is reporter or above' do
before do
project.add_reporter(user)
project.add_developer(user)
end
it 'assigns the sentry error' do

View File

@ -16,6 +16,6 @@ RSpec.shared_context 'sentry error tracking context' do
before do
expect(project).to receive(:error_tracking_setting).at_least(:once).and_return(error_tracking_setting)
project.add_reporter(user)
project.add_developer(user)
end
end