Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-06-30 21:09:49 +00:00
parent 3707364380
commit 9877050db1
117 changed files with 2210 additions and 411 deletions

View File

@ -1957,7 +1957,6 @@ Layout/LineLength:
- 'ee/spec/features/groups/iterations/user_edits_iteration_spec.rb'
- 'ee/spec/features/groups/iterations/user_views_iteration_cadence_spec.rb'
- 'ee/spec/features/groups/iterations/user_views_iteration_spec.rb'
- 'ee/spec/features/groups/members/manage_groups_spec.rb'
- 'ee/spec/features/groups/members/manage_members_spec.rb'
- 'ee/spec/features/groups/members/override_ldap_memberships_spec.rb'
- 'ee/spec/features/groups/saml_providers_spec.rb'

View File

@ -2,6 +2,27 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 15.1.1 (2022-06-30)
### Security (16 changes)
- [Fix group IP restrictions not enforced for container registry requests](gitlab-org/security/gitlab@0c9628791bf383734ec8f32e1d0040ca2fd62178) ([merge request](gitlab-org/security/gitlab!2550))
- [Gitlab Runner version upgrade](gitlab-org/security/gitlab@b7e06c1e812fdf0a2fab4aca07cdea33ff22b41c) ([merge request](gitlab-org/security/gitlab!2564))
- [Update ProjectAttributesTransformer to use fixed number of attributes](gitlab-org/security/gitlab@fae2720ffd7ec5ce3eb88e3b68b2879f4f664cf4) ([merge request](gitlab-org/security/gitlab!2547))
- [Escape deploy key title to prevent XSS](gitlab-org/security/gitlab@071c3fa4ae63d03117a3c02752711d29f6f620b1) ([merge request](gitlab-org/security/gitlab!2492))
- [Sanitize ZenTao breadcrumb links](gitlab-org/security/gitlab@5b16b65cfe57a946f25842b7818dafe6c8a934ea) ([merge request](gitlab-org/security/gitlab!2555))
- [Fix permissions in the project labels API](gitlab-org/security/gitlab@b3ff7ee5a64382ff9ee34bc3fc44acd0117f86d9) ([merge request](gitlab-org/security/gitlab!2532))
- [Security fix sentry issue leaks and access level check](gitlab-org/security/gitlab@a0ad79588f170e1c58206e42d8b550d75e874a4d) ([merge request](gitlab-org/security/gitlab!2531))
- [Check permissions before exposing user two factor enabled](gitlab-org/security/gitlab@3b7c699ffcca64721c0876da12435c148f8e83a7) ([merge request](gitlab-org/security/gitlab!2530))
- [Filter milestone release by user access](gitlab-org/security/gitlab@dc79edc16c7422279235d2ad8a4807644840fc4c) ([merge request](gitlab-org/security/gitlab!2535))
- [Fix the required access level in the Conan packages finder](gitlab-org/security/gitlab@5221ca59f09361f90798348851fa12c91e5d9e35) ([merge request](gitlab-org/security/gitlab!2513))
- [Allow inviting only groups with subset of allowed domains to groups](gitlab-org/security/gitlab@03dfb153355d0465ea25a6d73db895c975fc32df) ([merge request](gitlab-org/security/gitlab!2538))
- [Fix open redirect vulnerability](gitlab-org/security/gitlab@eb52b11c7b29319d16e21feec97bafbdf0f3c3e5) ([merge request](gitlab-org/security/gitlab!2542))
- [Adds a filter based on user access to Runner jobs endpoint](gitlab-org/security/gitlab@a35c6aa42c35da96bf1df263b4a3aa1fe38af75d) ([merge request](gitlab-org/security/gitlab!2508))
- [Prevent runners from picking IP restricted jobs](gitlab-org/security/gitlab@9d6f0da89f6d2e8f3c7fbccea0d22fc6b17e0305) ([merge request](gitlab-org/security/gitlab!2505))
- [Restrict CI lint access to pipeline creators](gitlab-org/security/gitlab@bf15e9ceddf4b30105103defa50dd4a9094ac246) ([merge request](gitlab-org/security/gitlab!2516))
- [Catch endless headers when reading HTTP responses](gitlab-org/security/gitlab@d9a6ca9aa36cfd6dd916be2d4f1e8e25329ecc73) ([merge request](gitlab-org/security/gitlab!2527))
## 15.1.0 (2022-06-21)
### Added (147 changes)
@ -963,6 +984,28 @@ entry.
- [Fix JH skipped subscription portal spec](gitlab-org/gitlab@0e7e7cb4a62d004989c47fafe6fe1f9ffd90da44) by @chaomao ([merge request](gitlab-org/gitlab!87213))
- [Add not null constraint to requirements.issue_id validate:false](gitlab-org/gitlab@5ccac890b13c53c5761ccb8e5cb7ca202e0656c3) ([merge request](gitlab-org/gitlab!86590))
## 15.0.4 (2022-06-30)
### Security (17 changes)
- [Fix group IP restrictions not enforced for container registry requests](gitlab-org/security/gitlab@7dea5867ea5e115a3a91576fec91de8e7f2a9915) ([merge request](gitlab-org/security/gitlab!2551))
- [Update rack gem to version 2.2.3.1](gitlab-org/security/gitlab@c0df8beef0297e9b99b954fcdcbf07cee3f0e9d6) ([merge request](gitlab-org/security/gitlab!2553))
- [Gitlab Runner version upgrade](gitlab-org/security/gitlab@012ff20c80754ff9ac38b82894346a51aa0a9b4c) ([merge request](gitlab-org/security/gitlab!2566))
- [Update ProjectAttributesTransformer to use fixed number of attributes](gitlab-org/security/gitlab@619d77865f3e61f3cfb6ca92011ded44f6baf0ad) ([merge request](gitlab-org/security/gitlab!2548))
- [Escape deploy key title to prevent XSS](gitlab-org/security/gitlab@7b1a458df5c553d6fa99b4fec0d677c9e924ad86) ([merge request](gitlab-org/security/gitlab!2493))
- [Sanitize ZenTao breadcrumb links](gitlab-org/security/gitlab@adb8b2829e3d6b69ea32a7524c6f772be1debf82) ([merge request](gitlab-org/security/gitlab!2556))
- [Fix permissions in the project labels API](gitlab-org/security/gitlab@4fd766e90ea6e8899897d7b7d9551b2edb5dce9a) ([merge request](gitlab-org/security/gitlab!2533))
- [Security fix sentry issue leaks and access level check](gitlab-org/security/gitlab@d43b2c600a5fc31592eb8f07a4fcfdf3141911f7) ([merge request](gitlab-org/security/gitlab!2500))
- [Check permissions before exposing user two factor enabled](gitlab-org/security/gitlab@aac30c9f3228efd643d3fc204ee49f740f1ebc81) ([merge request](gitlab-org/security/gitlab!2524))
- [Filter milestone release by user access](gitlab-org/security/gitlab@aa1b76b8eb2966463c8a10869e00f3320bf4ea1a) ([merge request](gitlab-org/security/gitlab!2536))
- [Fix the required access level in the Conan packages finder](gitlab-org/security/gitlab@fa090cd9d2adab46c6c3f2a70b351a61847b5c6c) ([merge request](gitlab-org/security/gitlab!2482))
- [Allow inviting only groups with subset of allowed domains to groups](gitlab-org/security/gitlab@981be1afc7c6bf8f699ced1ae930b201699e29e3) ([merge request](gitlab-org/security/gitlab!2511))
- [Fix open redirect vulnerability](gitlab-org/security/gitlab@fa9cf0a41f338e285701db231316897d362ce306) ([merge request](gitlab-org/security/gitlab!2541))
- [Adds a filter based on user access to Runner jobs endpoint](gitlab-org/security/gitlab@8be3da271d2a6ff3285846c50a5ce4dd584419ff) ([merge request](gitlab-org/security/gitlab!2496))
- [Prevent runners from picking IP restricted jobs](gitlab-org/security/gitlab@dcc830d14cc0ee616dc3ad263d66bd42f92b56a2) ([merge request](gitlab-org/security/gitlab!2504))
- [Restrict CI lint access to pipeline creators](gitlab-org/security/gitlab@42425cd68755c53ed33952111be9803ce3b37515) ([merge request](gitlab-org/security/gitlab!2514))
- [Catch endless headers when reading HTTP responses](gitlab-org/security/gitlab@d2ce0a236204b97a853bc35332d49d7427f38fbc) ([merge request](gitlab-org/security/gitlab!2528))
## 15.0.3 (2022-06-16)
### Fixed (2 changes)
@ -1737,6 +1780,28 @@ entry.
- [Move methods to build email unsubscribe link to helper](gitlab-org/gitlab@ae4391a84d14d51ca5b5f2ffaada96e3b37a1d51) ([merge request](gitlab-org/gitlab!84696)) **GitLab Enterprise Edition**
- [Deprecate `push_rules_supersede_code_owners` feature flag](gitlab-org/gitlab@9ee99872b66a69c5a2d1c1c9863d960832a1d91f) ([merge request](gitlab-org/gitlab!85390))
## 14.10.5 (2022-06-30)
### Security (17 changes)
- [Fix group IP restrictions not enforced for container registry requests](gitlab-org/security/gitlab@b146ad7b8c6fba9d3c5bea365ff8afd49949dcb0) ([merge request](gitlab-org/security/gitlab!2552))
- [Update rack gem to version 2.2.3.1](gitlab-org/security/gitlab@09ebb50ceee5a2226c1f70fa1d6c25391d51dda6) ([merge request](gitlab-org/security/gitlab!2554))
- [Gitlab Runner version upgrade](gitlab-org/security/gitlab@c91bfdb4f96e70e377a84b99c4edaa2fdecb8e16) ([merge request](gitlab-org/security/gitlab!2567))
- [Update ProjectAttributesTransformer to use fixed number of attributes](gitlab-org/security/gitlab@6f892fb2a4b84473c3796533551f915c16cf77d9) ([merge request](gitlab-org/security/gitlab!2549))
- [Escape deploy key title to prevent XSS](gitlab-org/security/gitlab@153a7c447e03a509b7f06ac7381f4f9db414c9ea) ([merge request](gitlab-org/security/gitlab!2494))
- [Sanitize ZenTao breadcrumb links](gitlab-org/security/gitlab@530c7be82ae90138898ff99008d994b1c85d8cf1) ([merge request](gitlab-org/security/gitlab!2557))
- [Fix permissions in the project labels API](gitlab-org/security/gitlab@f2c71f64c258bef9f56f4892d11a4dbf20d668e6) ([merge request](gitlab-org/security/gitlab!2534))
- [Security fix sentry issue leaks and access level check](gitlab-org/security/gitlab@c644d94f58e30e1a9d87521b039a347412f0fead) ([merge request](gitlab-org/security/gitlab!2501))
- [Check permissions before exposing user two factor enabled](gitlab-org/security/gitlab@8a623e8a4fdbd3421ac3ae0e37e156b7d3b04970) ([merge request](gitlab-org/security/gitlab!2525))
- [Filter milestone release by user access](gitlab-org/security/gitlab@d7d6431a52808107a71f15d29e856eef2cb313e5) ([merge request](gitlab-org/security/gitlab!2537))
- [Fix the required access level in the Conan packages finder](gitlab-org/security/gitlab@756fb242c4d6acf6cfd95fa39f37410eaf009747) ([merge request](gitlab-org/security/gitlab!2485))
- [Allow inviting only groups with subset of allowed domains to groups](gitlab-org/security/gitlab@ca50492a32a2e367b0bc75dae0f91dc52d23b2ed) ([merge request](gitlab-org/security/gitlab!2512))
- [Fix open redirect vulnerability](gitlab-org/security/gitlab@1450068a44d67af3cbe09fedcc4b1e9b4ea2e586) ([merge request](gitlab-org/security/gitlab!2540))
- [Adds a filter based on user access to Runner jobs endpoint](gitlab-org/security/gitlab@dafaf3e50e8b1a18ff362cbb60e9482c9d60fc33) ([merge request](gitlab-org/security/gitlab!2497))
- [Prevent runners from picking IP restricted jobs](gitlab-org/security/gitlab@0fad0cdde00b68c2a0f19ffa2681b438fcad4097) ([merge request](gitlab-org/security/gitlab!2503))
- [Restrict CI lint access to pipeline creators](gitlab-org/security/gitlab@c5b79e969f10e3604eff16a9edef716e700cd201) ([merge request](gitlab-org/security/gitlab!2515))
- [Catch endless headers when reading HTTP responses](gitlab-org/security/gitlab@65379002bd7a0259c425455c937b110bd96096dc) ([merge request](gitlab-org/security/gitlab!2529))
## 14.10.4 (2022-06-01)
### Security (7 changes)

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

@ -44,7 +44,7 @@ export default {
},
computed: {
buttonText() {
if (!this.hasSelectedColor()) {
if (!this.hasSelectedColor) {
return this.dropdownButtonText;
}
@ -103,7 +103,7 @@ export default {
:color="localSelectedColor.color"
:title="localSelectedColor.title"
/>
<span v-else>{{ buttonText }}</span>
<span v-else data-testid="fallback-button-text">{{ buttonText }}</span>
</template>
<template #header>
<dropdown-header

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

@ -129,7 +129,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

@ -6,19 +6,29 @@ module Ci
ALLOWED_INDEXED_COLUMNS = %w[id].freeze
def initialize(runner, params = {})
def initialize(runner, current_user, params = {})
@runner = runner
@user = current_user
@params = params
end
def execute
items = @runner.builds
items = by_permission(items)
items = by_status(items)
sort_items(items)
end
private
# rubocop: disable CodeReuse/ActiveRecord
def by_permission(items)
return items if @user.can_read_all_resources?
items.for_project(@user.authorized_project_mirrors(Gitlab::Access::REPORTER).select(:project_id))
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def by_status(items)
return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status])

View File

@ -25,7 +25,7 @@ module Packages
end
def projects_visible_to_current_user
::Project.public_or_visible_to_user(current_user)
::Project.public_or_visible_to_user(current_user, ::Gitlab::Access::REPORTER)
end
end
end

View File

@ -12,7 +12,7 @@ module Resolvers
Should not be requested more than once per request.
MD
authorize :read_pipeline
authorize :create_pipeline
argument :project_path, GraphQL::Types::ID,
required: true,

View File

@ -160,27 +160,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

@ -300,6 +300,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

@ -153,11 +153,11 @@ module TimeboxesHelper
n_("%{releases} release", "%{releases} releases", count) % { releases: count }
end
def recent_releases_with_counts(milestone)
def recent_releases_with_counts(milestone, user)
total_count = milestone.releases.size
return [[], 0, 0] if total_count == 0
recent_releases = milestone.releases.recent.to_a
recent_releases = milestone.releases.recent.filter { |release| Ability.allowed?(user, :read_release, release) }
more_count = total_count - recent_releases.size
[recent_releases, total_count, more_count]
end

View File

@ -171,6 +171,11 @@ class ApplicationSetting < ApplicationRecord
validates :kroki_formats, json_schema: { filename: 'application_setting_kroki_formats' }
validates :metrics_method_call_threshold,
numericality: { greater_than_or_equal_to: 0 },
presence: true,
if: :prometheus_metrics_enabled
validates :plantuml_url,
presence: true,
if: :plantuml_enabled

View File

@ -4,6 +4,8 @@ module Ci
# This model represents a shadow table of the main database's projects table.
# It allows us to navigate the project and namespace hierarchy on the ci database.
class ProjectMirror < ApplicationRecord
include FromUnion
belongs_to :project
scope :by_namespace_id, -> (namespace_id) { where(namespace_id: namespace_id) }

View File

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

View File

@ -38,7 +38,8 @@ module Enums
bridge_pipeline_is_child_pipeline: 1_006, # not used anymore, but cannot be deleted because of old data
downstream_pipeline_creation_failed: 1_007,
secrets_provider_not_found: 1_008,
reached_max_descendant_pipelines_depth: 1_009
reached_max_descendant_pipelines_depth: 1_009,
ip_restriction_failure: 1_010
}
end
end

View File

@ -5,9 +5,6 @@ class DeployToken < ApplicationRecord
include TokenAuthenticatable
include PolicyActor
include Gitlab::Utils::StrongMemoize
include IgnorableColumns
ignore_column :token, remove_with: '15.2', remove_after: '2022-07-22'
add_authentication_token_field :token, encrypted: :required

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

@ -1657,6 +1657,14 @@ class User < ApplicationRecord
true
end
def authorized_project_mirrors(level)
projects = Ci::ProjectMirror.by_project_id(ci_project_mirrors_for_project_members(level))
namespace_projects = Ci::ProjectMirror.by_namespace_id(ci_namespace_mirrors_for_group_members(level).select(:namespace_id))
Ci::ProjectMirror.from_union([projects, namespace_projects])
end
def ci_owned_runners
@ci_owned_runners ||= begin
Ci::Runner
@ -2113,6 +2121,10 @@ class User < ApplicationRecord
end
# rubocop: enable CodeReuse/ServiceClass
def ci_project_mirrors_for_project_members(level)
project_members.where('access_level >= ?', level).pluck(:source_id)
end
def notification_email_verified
return if notification_email.blank? || temp_oauth_email?
@ -2250,7 +2262,7 @@ class User < ApplicationRecord
end
def ci_owned_project_runners_from_project_members
project_ids = project_members.where('access_level >= ?', Gitlab::Access::MAINTAINER).pluck(:source_id)
project_ids = ci_project_mirrors_for_project_members(Gitlab::Access::MAINTAINER)
Ci::Runner
.joins(:runner_projects)

View File

@ -325,7 +325,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
@ -440,6 +439,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

@ -33,7 +33,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
trace_size_exceeded: 'The job log size limit was reached',
builds_disabled: 'The CI/CD is disabled for this project',
environment_creation_failure: 'This job could not be executed because it would create an environment with an invalid parameter.',
deployment_rejected: 'This deployment job was rejected.'
deployment_rejected: 'This deployment job was rejected.',
ip_restriction_failure: "This job could not be executed because group IP address restrictions are enabled, and the runner's IP address is not in the allowed range."
}.freeze
TROUBLESHOOTING_DOC = {

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

@ -89,7 +89,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

@ -1,5 +1,5 @@
= gitlab_ui_form_for @application_setting, url: metrics_and_profiling_admin_application_settings_path(anchor: 'js-prometheus-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting)
= form_errors(@application_setting, pajamas_alert: true)
%fieldset
.form-group

View File

@ -113,6 +113,8 @@
= render_if_exists 'admin/application_settings/slack'
-# this partial is from JiHu, see details in https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417
= render_if_exists 'admin/application_settings/dingtalk_integration'
-# this partial is from JiHu, see details in https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/640
= render_if_exists 'admin/application_settings/feishu_integration'
= render 'admin/application_settings/third_party_offers'
= render 'admin/application_settings/snowplow'
= render 'admin/application_settings/eks'

View File

@ -14,7 +14,7 @@
- if milestone.due_date || milestone.start_date
.text-tertiary.gl-mb-2
= milestone_date_range(milestone)
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone)
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone, current_user)
- unless total_count == 0
.text-tertiary.gl-mb-2.milestone-release-links
= sprite_icon("rocket", size: 12)

View File

@ -138,7 +138,7 @@
= milestone.merge_requests.merged.count
- if project
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone)
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone, current_user)
.block.releases
.sidebar-collapsed-icon.has-tooltip{ title: milestone_releases_tooltip_text(milestone), data: { container: 'body', placement: 'left', boundary: 'viewport' } }
%strong

View File

@ -1020,6 +1020,15 @@
:weight: 1
:idempotent: false
:tags: []
- :name: github_importer:github_import_import_issue_event
:worker_name: Gitlab::GithubImport::ImportIssueEventWorker
:feature_category: :importers
:has_external_dependencies: true
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: false
:tags: []
- :name: github_importer:github_import_import_lfs_object
:worker_name: Gitlab::GithubImport::ImportLfsObjectWorker
:feature_category: :importers
@ -1092,6 +1101,15 @@
:weight: 1
:idempotent: false
:tags: []
- :name: github_importer:github_import_stage_import_issue_events
:worker_name: Gitlab::GithubImport::Stage::ImportIssueEventsWorker
:feature_category: :importers
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: false
:tags: []
- :name: github_importer:github_import_stage_import_issues_and_diff_notes
:worker_name: Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker
:feature_category: :importers

View File

@ -23,6 +23,7 @@ module Gitlab
pull_requests_merged_by: Stage::ImportPullRequestsMergedByWorker,
pull_request_reviews: Stage::ImportPullRequestsReviewsWorker,
issues_and_diff_notes: Stage::ImportIssuesAndDiffNotesWorker,
issue_events: Stage::ImportIssueEventsWorker,
notes: Stage::ImportNotesWorker,
lfs_objects: Stage::ImportLfsObjectsWorker,
finish: Stage::FinishImportWorker

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
class ImportIssueEventWorker # rubocop:disable Scalability/IdempotentWorker
include ObjectImporter
def representation_class
Representation::IssueEvent
end
def importer_class
Importer::IssueEventImporter
end
def object_type
:issue_event
end
end
end
end

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
module Stage
class ImportIssueEventsWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
data_consistency :always
sidekiq_options retry: 3
include GithubImport::Queue
include StageMethods
# client - An instance of Gitlab::GithubImport::Client.
# project - An instance of Project.
def import(client, project)
importer = ::Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter
return skip_to_next_stage(project, importer) if feature_disabled?(project)
start_importer(project, importer, client)
end
private
def start_importer(project, importer, client)
info(project.id, message: "starting importer", importer: importer.name)
waiter = importer.new(project, client).execute
move_to_next_stage(project, waiter.key => waiter.jobs_remaining)
end
def skip_to_next_stage(project, importer)
info(project.id, message: "skipping importer", importer: importer.name)
move_to_next_stage(project)
end
def move_to_next_stage(project, waiters = {})
AdvanceStageWorker.perform_async(project.id, waiters, :notes)
end
def feature_disabled?(project)
Feature.disabled?(:github_importer_issue_events_import, project.group, type: :ops)
end
end
end
end
end

View File

@ -21,7 +21,7 @@ module Gitlab
hash[waiter.key] = waiter.jobs_remaining
end
AdvanceStageWorker.perform_async(project.id, waiters, :notes)
AdvanceStageWorker.perform_async(project.id, waiters, :issue_events)
end
# The importers to run in this stage. Issues can't be imported earlier

View File

@ -0,0 +1,8 @@
---
name: github_importer_issue_events_import
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89134
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365977
milestone: '15.2'
type: ops
group: group::import
default_enabled: false

View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
module Net
class HTTPResponse
# rubocop: disable Cop/LineBreakAfterGuardClauses
# rubocop: disable Cop/LineBreakAroundConditionalBlock
# rubocop: disable Layout/EmptyLineAfterGuardClause
# rubocop: disable Style/AndOr
# rubocop: disable Style/CharacterLiteral
# rubocop: disable Style/InfiniteLoop
# Original method:
# https://github.com/ruby/ruby/blob/v2_7_5/lib/net/http/response.rb#L54-L69
#
# Our changes:
# - Pass along the `start_time` to `Gitlab::BufferedIo`, so we can raise a timeout
# if reading the headers takes too long.
# - Limit the regexes to avoid ReDoS attacks.
def self.each_response_header(sock)
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
key = value = nil
while true
line = sock.is_a?(Gitlab::BufferedIo) ? sock.readuntil("\n", true, start_time) : sock.readuntil("\n", true)
line = line.sub(/\s{0,10}\z/, '')
break if line.empty?
if line[0] == ?\s or line[0] == ?\t and value
# :nocov:
value << ' ' unless value.empty?
value << line.strip
# :nocov:
else
yield key, value if key
key, value = line.strip.split(/\s{0,10}:\s{0,10}/, 2)
raise Net::HTTPBadResponse, 'wrong header line format' if value.nil?
end
end
yield key, value if key
end
# rubocop: enable Cop/LineBreakAfterGuardClauses
# rubocop: enable Cop/LineBreakAroundConditionalBlock
# rubocop: enable Layout/EmptyLineAfterGuardClause
# rubocop: enable Style/AndOr
# rubocop: enable Style/CharacterLiteral
# rubocop: enable Style/InfiniteLoop
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class AddInstallableConanPackagesIndexToPackages < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
INDEX_NAME = 'idx_installable_conan_pkgs_on_project_id_id'
# as defined by Packages::Package.package_types
CONAN_PACKAGE_TYPE = 3
# as defined by Packages::Package::INSTALLABLE_STATUSES
DEFAULT_STATUS = 0
HIDDEN_STATUS = 1
def up
where = "package_type = #{CONAN_PACKAGE_TYPE} AND status IN (#{DEFAULT_STATUS}, #{HIDDEN_STATUS})"
add_concurrent_index :packages_packages,
[:project_id, :id],
where: where,
name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :packages_packages, INDEX_NAME
end
end

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,12 @@
# frozen_string_literal: true
class AddFeiShuIntegration < Gitlab::Database::Migration[2.0]
def change
add_column :application_settings, :feishu_integration_enabled, :boolean, null: false,
default: false, comment: 'JiHu-specific column'
add_column :application_settings, :encrypted_feishu_app_key, :binary, comment: 'JiHu-specific column'
add_column :application_settings, :encrypted_feishu_app_key_iv, :binary, comment: 'JiHu-specific column'
add_column :application_settings, :encrypted_feishu_app_secret, :binary, comment: 'JiHu-specific column'
add_column :application_settings, :encrypted_feishu_app_secret_iv, :binary, comment: 'JiHu-specific column'
end
end

View File

@ -0,0 +1 @@
1fdb60b1c72b687aa8bede083ac7038097d538dc815e334d74296b1d39c2acb8

View File

@ -0,0 +1 @@
1f03beba0775e2a4eead512819592f590b02b70096cee250dfcdf426440cb5f5

View File

@ -0,0 +1 @@
80c35cd4dbc2e00e721ccb9313ff0f2f4f85e781c7961680e14769c308f067ed

View File

@ -11324,6 +11324,11 @@ CREATE TABLE application_settings (
phone_verification_code_enabled boolean DEFAULT false NOT NULL,
max_number_of_repository_downloads smallint DEFAULT 0 NOT NULL,
max_number_of_repository_downloads_within_time_period integer DEFAULT 0 NOT NULL,
feishu_integration_enabled boolean DEFAULT false NOT NULL,
encrypted_feishu_app_key bytea,
encrypted_feishu_app_key_iv bytea,
encrypted_feishu_app_secret bytea,
encrypted_feishu_app_secret_iv bytea,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
@ -11386,6 +11391,16 @@ COMMENT ON COLUMN application_settings.encrypted_dingtalk_app_secret_iv IS 'JiHu
COMMENT ON COLUMN application_settings.phone_verification_code_enabled IS 'JiHu-specific column';
COMMENT ON COLUMN application_settings.feishu_integration_enabled IS 'JiHu-specific column';
COMMENT ON COLUMN application_settings.encrypted_feishu_app_key IS 'JiHu-specific column';
COMMENT ON COLUMN application_settings.encrypted_feishu_app_key_iv IS 'JiHu-specific column';
COMMENT ON COLUMN application_settings.encrypted_feishu_app_secret IS 'JiHu-specific column';
COMMENT ON COLUMN application_settings.encrypted_feishu_app_secret_iv IS 'JiHu-specific column';
CREATE SEQUENCE application_settings_id_seq
START WITH 1
INCREMENT BY 1
@ -19341,7 +19356,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 (
@ -26973,6 +26989,8 @@ CREATE UNIQUE INDEX idx_external_audit_event_destination_id_key_uniq ON audit_ev
CREATE INDEX idx_geo_con_rep_updated_events_on_container_repository_id ON geo_container_repository_updated_events USING btree (container_repository_id);
CREATE INDEX idx_installable_conan_pkgs_on_project_id_id ON packages_packages USING btree (project_id, id) WHERE ((package_type = 3) AND (status = ANY (ARRAY[0, 1])));
CREATE INDEX idx_installable_helm_pkgs_on_project_id_id ON packages_packages USING btree (project_id, id);
CREATE INDEX idx_installable_npm_pkgs_on_project_id_name_version_id ON packages_packages USING btree (project_id, name, version, id) WHERE ((package_type = 2) AND (status = 0));

View File

@ -269,6 +269,9 @@ control over how the Pages daemon runs and serves content in your environment.
| `max_uri_length` | The maximum length of URIs accepted by GitLab Pages. Set to 0 for unlimited length. [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/659) in GitLab 14.5.
| `metrics_address` | The address to listen on for metrics requests. |
| `redirect_http` | Redirect pages from HTTP to HTTPS, true/false. |
| `redirects_max_config_size` | The maximum size of the _redirects file, in bytes (default: 65536). |
| `redirects_max_path_segments` | The maximum number of path segments allowed in _redirects rules URLs (default: 25). |
| `redirects_max_rule_count` | The maximum number of rules allowed in _redirects (default: 1000). |
| `sentry_dsn` | The address for sending Sentry crash reporting to. |
| `sentry_enabled` | Enable reporting and logging with Sentry, true/false. |
| `sentry_environment` | The environment for Sentry crash reporting. |
@ -577,6 +580,19 @@ HTTP Strict Transport Security (HSTS) can be enabled through the `gitlab_pages['
gitlab_pages['headers'] = ['Strict-Transport-Security: max-age=63072000']
```
### Pages project redirects limits
> [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/merge_requests/778) in GitLab 15.2.
GitLab Pages comes with a set of default limits for the [`_redirects` file](../../user/project/pages/redirects.md)
to minimize the impact on performance. You can configure these limits if you'd like to increase or decrease the limits.
```ruby
gitlab_pages['redirects_max_config_size'] = 131072
gitlab_pages['redirects_max_path_segments'] = 50
gitlab_pages['redirects_max_rule_count'] = 2000
```
## Activate verbose logging for daemon
Follow the steps below to configure verbose logging of GitLab Pages daemon.

View File

@ -359,7 +359,8 @@ and will be removed in [GitLab 16.0](https://gitlab.com/gitlab-org/gitlab/-/issu
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3.
List jobs that are being processed or were processed by specified runner.
List jobs that are being processed or were processed by the specified runner. The list of jobs is limited
to projects where the user has at least the Reporter role.
```plaintext
GET /runners/:id/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

@ -480,6 +480,9 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
1. Ensure all GitLab web nodes are running GitLab 15.1.Z.
1. [Enable the `active_support_hash_digest_sha256` feature flag](../administration/feature_flags.md#how-to-enable-and-disable-features-behind-flags) to switch `ActiveSupport::Digest` to use SHA256:
1. Only then, continue to upgrade to later versions of GitLab.
- Unauthenticated requests to the [`ciConfig` GraphQL field](../api/graphql/reference/index.md#queryciconfig) are no longer supported.
Before you upgrade to GitLab 15.1, add an [access token](../api/index.md#authentication) to your requests.
The user creating the token must have [permission](../user/permissions.md) to create pipelines in the project.
### 15.0.0

View File

@ -649,6 +649,7 @@ at the group level.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7297) in GitLab 12.2.
> - Support for specifying multiple email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1.
> - Support for restricting access to projects in the group [added](https://gitlab.com/gitlab-org/gitlab/-/issues/14004) in GitLab 14.1.2.
> - Support for restricting group memberships to groups with a subset of the allowed email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/354791) in GitLab 15.0.1
You can prevent users with email addresses in specific domains from being added to a group and its projects.
@ -669,6 +670,8 @@ The most popular public email domains cannot be restricted, such as:
- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
- `msn.com`, `live.com`, `outlook.com`
When you share a group, both the source and target namespaces must allow the domains of the members' email addresses.
## Restrict Git access protocols
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/365601) in GitLab 15.1 [with a flag](../../administration/feature_flags.md) named `group_level_git_protocol_control`. Disabled by default.

View File

@ -45,8 +45,9 @@ Note that:
- All paths must start with a forward slash `/`.
- A default status code of `301` is applied if no [status code](#http-status-codes) is provided.
- The `_redirects` file has a file size limit of 64KB and a maximum of 1,000 rules per project.
Only the first 1,000 rules are processed.
- The `_redirects` file has a file size limit and a maximum number of rules per project,
configured at the instance level. Only the first matching rules within the configured maximum are processed.
The default file size limit is 64KB, and the default maximum number of rules is 1,000.
- If your GitLab Pages site uses the default domain name (such as
`namespace.gitlab.io/projectname`) you must prefix every rule with the project name:

View File

@ -128,7 +128,7 @@ module API
runner = get_runner(params[:id])
authenticate_list_runners_jobs!(runner)
jobs = ::Ci::RunnerJobsFinder.new(runner, params).execute
jobs = ::Ci::RunnerJobsFinder.new(runner, current_user, params).execute
present paginate(jobs), with: Entities::Ci::JobBasicWithProject
end

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

@ -14,6 +14,7 @@ module Gitlab
HEADER_READ_TIMEOUT = 20
# rubocop: disable Style/RedundantBegin
# rubocop: disable Style/RedundantReturn
# rubocop: disable Cop/LineBreakAfterGuardClauses
# rubocop: disable Layout/EmptyLineAfterGuardClause
@ -21,9 +22,7 @@ module Gitlab
# Original method:
# https://github.com/ruby/ruby/blob/cdb7d699d0641e8f081d590d06d07887ac09961f/lib/net/protocol.rb#L190-L200
override :readuntil
def readuntil(terminator, ignore_eof = false)
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
def readuntil(terminator, ignore_eof = false, start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC))
begin
until idx = @rbuf.index(terminator)
if (elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) > HEADER_READ_TIMEOUT
@ -39,6 +38,7 @@ module Gitlab
return rbuf_consume(@rbuf.size)
end
end
# rubocop: disable Style/RedundantBegin
# rubocop: enable Style/RedundantReturn
# rubocop: enable Cop/LineBreakAfterGuardClauses
# rubocop: enable Layout/EmptyLineAfterGuardClause

View File

@ -13,7 +13,7 @@ module Gitlab
}.freeze
def check_runner_upgrade_status(runner_version)
runner_version = ::Gitlab::VersionInfo.parse(runner_version) unless runner_version.is_a?(::Gitlab::VersionInfo)
runner_version = ::Gitlab::VersionInfo.parse(runner_version, parse_suffix: true)
return :invalid unless runner_version.valid?

View File

@ -38,7 +38,8 @@ module Gitlab
trace_size_exceeded: 'log size limit exceeded',
builds_disabled: 'project builds are disabled',
environment_creation_failure: 'environment creation failure',
deployment_rejected: 'deployment rejected'
deployment_rejected: 'deployment rejected',
ip_restriction_failure: 'IP address restriction failure'
}.freeze
private_constant :REASONS

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

@ -0,0 +1,50 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
module Importer
module Events
class Closed
attr_reader :project, :user_id
def initialize(project, user_id)
@project = project
@user_id = user_id
end
# issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
def execute(issue_event)
create_event(issue_event)
create_state_event(issue_event)
end
private
def create_event(issue_event)
Event.create!(
project_id: project.id,
author_id: user_id,
action: 'closed',
target_type: Issue.name,
target_id: issue_event.issue_db_id,
created_at: issue_event.created_at,
updated_at: issue_event.created_at
)
end
def create_state_event(issue_event)
ResourceStateEvent.create!(
user_id: user_id,
issue_id: issue_event.issue_db_id,
source_commit: issue_event.commit_id,
state: ResourceStateEvent.states[:closed],
close_after_error_tracking_resolve: false,
close_auto_resolve_prometheus_alert: false,
created_at: issue_event.created_at
)
end
end
end
end
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
module Importer
class IssueEventImporter
attr_reader :issue_event, :project, :client, :user_finder
# issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
# project - An instance of `Project`.
# client - An instance of `Gitlab::GithubImport::Client`.
def initialize(issue_event, project, client)
@issue_event = issue_event
@project = project
@client = client
@user_finder = UserFinder.new(project, client)
end
def execute
case issue_event.event
when 'closed'
Gitlab::GithubImport::Importer::Events::Closed.new(project, author_id)
.execute(issue_event)
else
Gitlab::GithubImport::Logger.debug(
message: 'UNSUPPORTED_EVENT_TYPE',
event_type: issue_event.event, event_github_id: issue_event.id
)
end
end
private
def author_id
id, _status = user_finder.author_id_for(issue_event, author_key: :actor)
id
end
end
end
end
end

View File

@ -37,15 +37,15 @@ module Gitlab
private
def noteables
project.merge_requests.where.not(iid: already_imported_noteables) # rubocop: disable CodeReuse/ActiveRecord
def parent_collection
project.merge_requests.where.not(iid: already_imported_parents) # rubocop: disable CodeReuse/ActiveRecord
end
def page_counter_id(merge_request)
"merge_request/#{merge_request.id}/#{collection_method}"
end
def notes_imported_cache_key
def parent_imported_cache_key
"github-importer/merge_request/diff_notes/already-imported/#{project.id}"
end
end

View File

@ -0,0 +1,74 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
module Importer
class SingleEndpointIssueEventsImporter
include ParallelScheduling
include SingleEndpointNotesImporting
PROCESSED_PAGE_CACHE_KEY = 'issues/%{issue_iid}/%{collection}'
BATCH_SIZE = 100
def initialize(project, client, parallel: true)
@project = project
@client = client
@parallel = parallel
@already_imported_cache_key = ALREADY_IMPORTED_CACHE_KEY %
{ project: project.id, collection: collection_method }
end
def each_associated(parent_record, associated)
return if already_imported?(associated)
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
associated.issue_db_id = parent_record.id
yield(associated)
mark_as_imported(associated)
end
def importer_class
IssueEventImporter
end
def representation_class
Representation::IssueEvent
end
def sidekiq_worker_class
ImportIssueEventWorker
end
def object_type
:issue_event
end
def collection_method
:issue_timeline
end
def parent_collection
project.issues.where.not(iid: already_imported_parents).select(:id, :iid) # rubocop: disable CodeReuse/ActiveRecord
end
def parent_imported_cache_key
"github-importer/issues/#{collection_method}/already-imported/#{project.id}"
end
def page_counter_id(issue)
PROCESSED_PAGE_CACHE_KEY % { issue_iid: issue.iid, collection: collection_method }
end
def id_for_already_imported_cache(event)
event.id
end
def collection_options
{ state: 'all', sort: 'created', direction: 'asc' }
end
end
end
end
end

View File

@ -37,15 +37,15 @@ module Gitlab
private
def noteables
project.issues.where.not(iid: already_imported_noteables) # rubocop: disable CodeReuse/ActiveRecord
def parent_collection
project.issues.where.not(iid: already_imported_parents) # rubocop: disable CodeReuse/ActiveRecord
end
def page_counter_id(issue)
"issue/#{issue.id}/#{collection_method}"
end
def notes_imported_cache_key
def parent_imported_cache_key
"github-importer/issue/notes/already-imported/#{project.id}"
end
end

View File

@ -37,15 +37,15 @@ module Gitlab
private
def noteables
project.merge_requests.where.not(iid: already_imported_noteables) # rubocop: disable CodeReuse/ActiveRecord
def parent_collection
project.merge_requests.where.not(iid: already_imported_parents) # rubocop: disable CodeReuse/ActiveRecord
end
def page_counter_id(merge_request)
"merge_request/#{merge_request.id}/#{collection_method}"
end
def notes_imported_cache_key
def parent_imported_cache_key
"github-importer/merge_request/notes/already-imported/#{project.id}"
end
end

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
module Gitlab
module GithubImport
module Representation
class IssueEvent
include ToHash
include ExposeAttribute
attr_reader :attributes
expose_attribute :id, :actor, :event, :commit_id, :created_at
expose_attribute :issue_db_id # set in SingleEndpointIssueEventsImporter#each_associated
# Builds a event from a GitHub API response.
#
# event - An instance of `Sawyer::Resource` containing the event details.
def self.from_api_response(event)
new(
id: event.id,
actor: event.actor && Representation::User.from_api_response(event.actor),
event: event.event,
commit_id: event.commit_id,
issue_db_id: event.issue_db_id,
created_at: event.created_at
)
end
# Builds a event using a Hash that was built from a JSON payload.
def self.from_json_hash(raw_hash)
hash = Representation.symbolize_hash(raw_hash)
hash[:actor] = Representation::User.from_json_hash(hash[:actor]) if hash[:actor]
new(hash)
end
# attributes - A Hash containing the event details. The keys of this
# Hash (and any nested hashes) must be symbols.
def initialize(attributes)
@attributes = attributes
end
def github_identifiers
{ id: id }
end
end
end
end
end

View File

@ -4,9 +4,14 @@
# - SingleEndpointDiffNotesImporter
# - SingleEndpointIssueNotesImporter
# - SingleEndpointMergeRequestNotesImporter
# if `github_importer_single_endpoint_notes_import` feature flag is on.
#
# `github_importer_single_endpoint_notes_import`
# feature flag is on.
# - SingleEndpointIssueEventsImporter
# if `github_importer_issue_events_import` feature flag is on.
#
# Fetches associated objects page by page to each item of parent collection.
# Currently `associated` is note or event.
# Currently `parent` is MergeRequest or Issue record.
#
# It fetches 1 PR's associated objects at a time using `issue_comments` or
# `pull_request_comments` endpoint, which is slower than `NotesImporter`
@ -18,67 +23,75 @@ module Gitlab
module SingleEndpointNotesImporting
BATCH_SIZE = 100
def each_object_to_import
each_notes_page do |page|
page.objects.each do |note|
next if already_imported?(note)
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
yield(note)
mark_as_imported(note)
def each_object_to_import(&block)
each_associated_page do |parent_record, associated_page|
associated_page.objects.each do |associated|
each_associated(parent_record, associated, &block)
end
end
end
def id_for_already_imported_cache(note)
note.id
def id_for_already_imported_cache(associated)
associated.id
end
def parent_collection
raise NotImplementedError
end
def parent_imported_cache_key
raise NotImplementedError
end
def page_counter_id(parent)
raise NotImplementedError
end
private
def each_notes_page
noteables.each_batch(of: BATCH_SIZE, column: :iid) do |batch|
batch.each do |noteable|
# The page counter needs to be scoped by noteable to avoid skipping
# pages of notes from already imported noteables.
page_counter = PageCounter.new(project, page_counter_id(noteable))
# Sometimes we need to add some extra info from parent
# to associated record that is not available by default
# in Github API response object. For example:
# lib/gitlab/github_import/importer/single_endpoint_issue_events_importer.rb:26
def each_associated(_parent_record, associated)
return if already_imported?(associated)
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
yield(associated)
mark_as_imported(associated)
end
def each_associated_page
parent_collection.each_batch(of: BATCH_SIZE, column: :iid) do |batch|
batch.each do |parent_record|
# The page counter needs to be scoped by parent_record to avoid skipping
# pages of notes from already imported parent_record.
page_counter = PageCounter.new(project, page_counter_id(parent_record))
repo = project.import_source
options = collection_options.merge(page: page_counter.current)
client.each_page(collection_method, repo, noteable.iid, options) do |page|
client.each_page(collection_method, repo, parent_record.iid, options) do |page|
next unless page_counter.set(page.number)
yield page
yield parent_record, page
end
mark_notes_imported(noteable)
mark_parent_imported(parent_record)
end
end
end
def mark_notes_imported(noteable)
def mark_parent_imported(parent)
Gitlab::Cache::Import::Caching.set_add(
notes_imported_cache_key,
noteable.iid
parent_imported_cache_key,
parent.iid
)
end
def already_imported_noteables
Gitlab::Cache::Import::Caching.values_from_set(notes_imported_cache_key)
end
def noteables
NotImplementedError
end
def notes_imported_cache_key
NotImplementedError
end
def page_counter_id(noteable)
NotImplementedError
def already_imported_parents
Gitlab::Cache::Import::Caching.values_from_set(parent_imported_cache_key)
end
end
end

View File

@ -39,13 +39,9 @@ module Gitlab
#
# If the object has no author ID we'll use the ID of the GitLab ghost
# user.
def author_id_for(object)
id =
if object&.author
user_id_for(object.author)
else
GithubImport.ghost_user_id
end
def author_id_for(object, author_key: :author)
user_info = author_key == :actor ? object&.actor : object&.author
id = user_info ? user_id_for(user_info) : GithubImport.ghost_user_id
if id
[id, true]

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

@ -38,7 +38,8 @@ module Gitlab
# @param [String] namespace
def self.restore_full_path(namespace:, project:)
if project.include?(ENCODED_SLASH)
project.gsub(ENCODED_SLASH, SLASH)
# Replace multiple slashes with single ones to make sure the redirect stays on the same host
project.gsub(ENCODED_SLASH, SLASH).gsub(%r{\/{2,}}, '/')
else
"#{namespace}/#{project}"
end

View File

@ -6,20 +6,27 @@ module Gitlab
attr_reader :major, :minor, :patch
def self.parse(str)
if str && m = str.match(/(\d+)\.(\d+)\.(\d+)/)
VersionInfo.new(m[1].to_i, m[2].to_i, m[3].to_i)
VERSION_REGEX = /(\d+)\.(\d+)\.(\d+)/.freeze
def self.parse(str, parse_suffix: false)
if str.is_a?(self.class)
str
elsif str && m = str.match(VERSION_REGEX)
VersionInfo.new(m[1].to_i, m[2].to_i, m[3].to_i, parse_suffix ? m.post_match : nil)
else
VersionInfo.new
end
end
def initialize(major = 0, minor = 0, patch = 0)
def initialize(major = 0, minor = 0, patch = 0, suffix = nil)
@major = major
@minor = minor
@patch = patch
@suffix_s = suffix.to_s
end
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def <=>(other)
return unless other.is_a? VersionInfo
return unless valid? && other.valid?
@ -36,19 +43,31 @@ module Gitlab
1
elsif @patch < other.patch
-1
elsif @suffix_s.empty? && other.suffix.present?
1
elsif other.suffix.empty? && @suffix_s.present?
-1
else
0
suffix <=> other.suffix
end
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
def to_s
if valid?
"%d.%d.%d" % [@major, @minor, @patch]
"%d.%d.%d%s" % [@major, @minor, @patch, @suffix_s]
else
"Unknown"
'Unknown'
end
end
def suffix
@suffix ||= @suffix_s.strip.gsub('-', '.pre.').scan(/\d+|[a-z]+/i).map do |s|
/^\d+$/ =~ s ? s.to_i : s
end.freeze
end
def valid?
@major >= 0 && @minor >= 0 && @patch >= 0 && @major + @minor + @patch > 0
end

View File

@ -21342,6 +21342,9 @@ msgstr ""
msgid "Invited"
msgstr ""
msgid "Invited group allowed email domains must contain a subset of the allowed email domains of the root ancestor group. Go to the group's 'Settings &gt; General' page and check 'Restrict membership by email domain'."
msgstr ""
msgid "IrkerService|Channels and users separated by whitespaces. %{recipients_docs_link}"
msgstr ""

View File

@ -52,8 +52,8 @@
"@babel/preset-env": "^7.18.2",
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "2.22.0",
"@gitlab/ui": "42.11.0",
"@gitlab/svgs": "2.24.0",
"@gitlab/ui": "42.12.0",
"@gitlab/visual-review-tools": "1.7.3",
"@rails/actioncable": "6.1.4-7",
"@rails/ujs": "6.1.4-7",

View File

@ -307,6 +307,18 @@ RSpec.describe Projects::ErrorTrackingController do
end
describe 'format json' do
context 'when user is a reporter' do
before do
project.add_reporter(user)
end
it 'returns 404 error' do
update_issue
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when update result is successful' do
before do
allow(issue_update_service).to receive(:execute)

View File

@ -70,6 +70,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

@ -5,12 +5,17 @@ require 'spec_helper'
RSpec.describe Ci::RunnerJobsFinder do
let(:project) { create(:project) }
let(:runner) { create(:ci_runner, :instance) }
let(:user) { create(:user) }
let(:params) { {} }
subject { described_class.new(runner, params).execute }
subject { described_class.new(runner, user, params).execute }
before do
project.add_developer(user)
end
describe '#execute' do
context 'when params is empty' do
let(:params) { {} }
let!(:job) { create(:ci_build, runner: runner, project: project) }
let!(:job1) { create(:ci_build, project: project) }
@ -20,6 +25,50 @@ RSpec.describe Ci::RunnerJobsFinder do
end
end
context 'when the user has guest access' do
it 'does not returns jobs the user does not have permission to see' do
another_project = create(:project)
job = create(:ci_build, runner: runner, project: another_project)
another_project.add_guest(user)
is_expected.not_to match_array(job)
end
end
context 'when the user has permission to read all resources' do
let(:user) { create(:user, :admin) }
it 'returns all the jobs assigned to a runner' do
jobs = create_list(:ci_build, 5, runner: runner, project: project)
is_expected.to match_array(jobs)
end
end
context 'when the user has different access levels in different projects' do
it 'returns only the jobs the user has permission to see' do
guest_project = create(:project)
reporter_project = create(:project)
_guest_jobs = create_list(:ci_build, 2, runner: runner, project: guest_project)
reporter_jobs = create_list(:ci_build, 3, runner: runner, project: reporter_project)
guest_project.add_guest(user)
reporter_project.add_reporter(user)
is_expected.to match_array(reporter_jobs)
end
end
context 'when the user has reporter access level or greater' do
it 'returns jobs assigned to the Runner that the user has accesss to' do
jobs = create_list(:ci_build, 3, runner: runner, project: project)
is_expected.to match_array(jobs)
end
end
context 'when params contains status' do
Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status|
context "when status is #{target_status}" do

View File

@ -2,22 +2,53 @@
require 'spec_helper'
RSpec.describe ::Packages::Conan::PackageFinder do
using RSpec::Parameterized::TableSyntax
let_it_be_with_reload(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
let_it_be(:private_project) { create(:project, :private) }
let_it_be(:conan_package) { create(:conan_package, project: project) }
let_it_be(:conan_package2) { create(:conan_package, project: project) }
let_it_be(:errored_package) { create(:conan_package, :error, project: project) }
let_it_be(:private_package) { create(:conan_package, project: private_project) }
describe '#execute' do
let!(:conan_package) { create(:conan_package, project: project) }
let!(:conan_package2) { create(:conan_package, project: project) }
let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" }
let(:finder) { described_class.new(user, query: query) }
subject { described_class.new(user, query: query).execute }
subject { finder.execute }
context 'packages that are not installable' do
let!(:conan_package3) { create(:conan_package, :error, project: project) }
let!(:non_visible_project) { create(:project, :private) }
let!(:non_visible_conan_package) { create(:conan_package, project: non_visible_project) }
let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" }
where(:visibility, :role, :packages_visible) do
:private | :maintainer | true
:private | :developer | true
:private | :reporter | true
:private | :guest | false
:private | :anonymous | false
it { is_expected.to eq [conan_package, conan_package2] }
:internal | :maintainer | true
:internal | :developer | true
:internal | :reporter | true
:internal | :guest | true
:internal | :anonymous | false
:public | :maintainer | true
:public | :developer | true
:public | :reporter | true
:public | :guest | true
:public | :anonymous | true
end
with_them do
let(:expected_packages) { packages_visible ? [conan_package, conan_package2] : [] }
let(:user) { role == :anonymous ? nil : super() }
before do
project.update_column(:visibility_level, Gitlab::VisibilityLevel.string_options[visibility.to_s])
project.add_user(user, role) unless role == :anonymous
end
it { is_expected.to eq(expected_packages) }
end
end
end

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

@ -130,7 +130,7 @@ describe('Markdown component', () => {
expect(columns[0].innerHTML).toContain('<img src="data:image/jpeg;base64');
expect(columns[1].innerHTML).toContain('<img src="data:image/png;base64');
expect(columns[2].innerHTML).toContain('<img src="data:image/jpeg;base64');
expect(columns[3].innerHTML).toContain('<img>');
expect(columns[3].innerHTML).toContain('<img src="attachment:bogus">');
expect(columns[4].innerHTML).toContain('<img src="https://www.google.com/');
});
});

View File

@ -159,4 +159,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

@ -1,57 +1,30 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { GlDropdown } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { DROPDOWN_VARIANT } from '~/vue_shared/components/color_select_dropdown/constants';
import DropdownContents from '~/vue_shared/components/color_select_dropdown/dropdown_contents.vue';
import DropdownContentsColorView from '~/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue';
import DropdownHeader from '~/vue_shared/components/color_select_dropdown/dropdown_header.vue';
import { color } from './mock_data';
const showDropdown = jest.fn();
const focusInput = jest.fn();
const defaultProps = {
dropdownTitle: '',
selectedColor: color,
dropdownButtonText: '',
dropdownButtonText: 'Pick a color',
variant: '',
isVisible: false,
};
const GlDropdownStub = {
template: `
<div>
<slot name="header"></slot>
<slot></slot>
</div>
`,
methods: {
show: showDropdown,
hide: jest.fn(),
},
};
const DropdownHeaderStub = {
template: `
<div>Hello, I am a header</div>
`,
methods: {
focusInput,
},
};
describe('DropdownContent', () => {
let wrapper;
const createComponent = ({ propsData = {} } = {}) => {
wrapper = shallowMount(DropdownContents, {
wrapper = mountExtended(DropdownContents, {
propsData: {
...defaultProps,
...propsData,
},
stubs: {
GlDropdown: GlDropdownStub,
DropdownHeader: DropdownHeaderStub,
},
});
};
@ -60,16 +33,17 @@ describe('DropdownContent', () => {
});
const findColorView = () => wrapper.findComponent(DropdownContentsColorView);
const findDropdownHeader = () => wrapper.findComponent(DropdownHeaderStub);
const findDropdown = () => wrapper.findComponent(GlDropdownStub);
const findDropdownHeader = () => wrapper.findComponent(DropdownHeader);
const findDropdown = () => wrapper.findComponent(GlDropdown);
it('calls dropdown `show` method on `isVisible` prop change', async () => {
createComponent();
const spy = jest.spyOn(wrapper.vm.$refs.dropdown, 'show');
await wrapper.setProps({
isVisible: true,
});
expect(showDropdown).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledTimes(1);
});
it('does not emit `setColor` event on dropdown hide if color did not change', () => {
@ -110,4 +84,12 @@ describe('DropdownContent', () => {
expect(findDropdownHeader().exists()).toBe(true);
});
it('handles no selected color', () => {
createComponent({ propsData: { selectedColor: {} } });
expect(wrapper.findByTestId('fallback-button-text').text()).toEqual(
defaultProps.dropdownButtonText,
);
});
});

View File

@ -7,24 +7,13 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
describe '#resolve' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:sha) { nil }
let_it_be(:content) do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml'))
end
let(:ci_lint) do
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(ci_lint_double).to receive(:validate).and_return(fake_result)
ci_lint_double
end
before do
allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint)
end
subject(:response) do
resolve(described_class,
args: { project_path: project.full_path, content: content, sha: sha },
@ -51,52 +40,77 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
end
end
context 'with a valid .gitlab-ci.yml' do
context 'with a sha' do
let(:sha) { '1231231' }
it_behaves_like 'a valid config file'
end
context 'without a sha' do
it_behaves_like 'a valid config file'
end
end
context 'with an invalid .gitlab-ci.yml' do
let(:content) { 'invalid' }
let(:fake_result) do
Gitlab::Ci::Lint::Result.new(
jobs: [],
merged_yaml: content,
errors: ['Invalid configuration format'],
warnings: [],
includes: []
)
end
it 'responds with errors about invalid syntax' do
expect(response[:status]).to eq(:invalid)
expect(response[:errors]).to eq(['Invalid configuration format'])
end
end
context 'with an invalid SHA' do
let_it_be(:sha) { ':' }
context 'when the user can create a pipeline' do
let(:ci_lint) do
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument)
allow(ci_lint_double).to receive(:validate).and_return(fake_result)
ci_lint_double
end
it 'logs the invalid SHA to Sentry' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
.with(GRPC::InvalidArgument, sha: ':')
before do
allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint)
response
project.add_developer(user)
end
context 'with a valid .gitlab-ci.yml' do
context 'with a sha' do
let(:sha) { '1231231' }
it_behaves_like 'a valid config file'
end
context 'without a sha' do
it_behaves_like 'a valid config file'
end
end
context 'with an invalid .gitlab-ci.yml' do
let(:content) { 'invalid' }
let(:fake_result) do
Gitlab::Ci::Lint::Result.new(
jobs: [],
merged_yaml: content,
errors: ['Invalid configuration format'],
warnings: [],
includes: []
)
end
it 'responds with errors about invalid syntax' do
expect(response[:status]).to eq(:invalid)
expect(response[:errors]).to match_array(['Invalid configuration format'])
end
end
context 'with an invalid SHA' do
let_it_be(:sha) { ':' }
let(:ci_lint) do
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument)
ci_lint_double
end
it 'logs the invalid SHA to Sentry' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
.with(GRPC::InvalidArgument, sha: ':')
response
end
end
end
context 'when the user cannot create a pipeline' do
before do
project.add_guest(user)
end
it 'returns an error stating that the user cannot access the linting' do
expect(response).to be_instance_of(::Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end

View File

@ -150,19 +150,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

@ -38,4 +38,23 @@ RSpec.describe TimeboxesHelper do
end
end
end
describe "#recent_releases_with_counts" do
let_it_be(:milestone) { create(:milestone) }
let_it_be(:project) { milestone.project }
let_it_be(:user) { create(:user) }
subject { helper.recent_releases_with_counts(milestone, user) }
before do
project.add_developer(user)
end
it "returns releases with counts" do
_old_releases = create_list(:release, 2, project: project, milestones: [milestone])
recent_public_releases = create_list(:release, 3, project: project, milestones: [milestone], released_at: '2022-01-01T18:00:00Z')
is_expected.to match([match_array(recent_public_releases), 5, 2])
end
end
end

View File

@ -0,0 +1,79 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Net::HTTPResponse patch header read timeout' do
describe '.each_response_header' do
let(:server_response) do
<<~EOS
Content-Type: text/html
Header-Two: foo
Hello World
EOS
end
before do
stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1)
end
subject(:each_response_header) { Net::HTTPResponse.each_response_header(socket) { |k, v| } }
context 'with Net::BufferedIO' do
let(:socket) { Net::BufferedIO.new(StringIO.new(server_response)) }
it 'does not forward start time to the socket' do
allow(socket).to receive(:readuntil).and_call_original
expect(socket).to receive(:readuntil).with("\n", true)
each_response_header
end
context 'when the response contains many consecutive spaces' do
before do
expect(socket).to receive(:readuntil).and_return(
"a: #{' ' * 100_000} b",
''
)
end
it 'has no regex backtracking issues' do
Timeout.timeout(1) do
each_response_header
end
end
end
end
context 'with Gitlab::BufferedIo' do
let(:mock_io) { StringIO.new(server_response) }
let(:socket) { Gitlab::BufferedIo.new(mock_io) }
it 'forwards start time to the socket' do
allow(socket).to receive(:readuntil).and_call_original
expect(socket).to receive(:readuntil).with("\n", true, kind_of(Numeric))
each_response_header
end
context 'when the response contains an infinite number of headers' do
before do
read_counter = 0
allow(mock_io).to receive(:read_nonblock) do
read_counter += 1
raise 'Test did not raise HeaderReadTimeout' if read_counter > 10
sleep 0.01
+"Yet-Another-Header: foo\n"
end
end
it 'raises a timeout error' do
expect { each_response_header }.to raise_error(Gitlab::HTTP::HeaderReadTimeout,
/Request timed out after reading headers for 0\.[0-9]+ seconds/)
end
end
end
end
end

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

@ -1,54 +1,50 @@
# rubocop:disable Style/FrozenStringLiteralComment
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BufferedIo do
describe '#readuntil' do
let(:never_ending_tcp_socket) do
Class.new do
def initialize(*_)
@read_counter = 0
end
def setsockopt(*_); end
def closed?
false
end
def close
true
end
def to_io
StringIO.new('Hello World!')
end
def write_nonblock(data, *_)
data.size
end
def read_nonblock(buffer_size, *_)
sleep 0.01
@read_counter += 1
raise 'Test did not raise HeaderReadTimeout' if @read_counter > 10
'H' * buffer_size
end
end
end
let(:mock_io) { StringIO.new('a') }
let(:start_time) { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
before do
stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1)
end
subject(:readuntil) do
Gitlab::BufferedIo.new(never_ending_tcp_socket.new).readuntil('a')
Gitlab::BufferedIo.new(mock_io).readuntil('a', false, start_time)
end
it 'raises a timeout error' do
expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
it 'does not raise a timeout error' do
expect { readuntil }.not_to raise_error
end
context 'when the response contains infinitely long headers' do
before do
read_counter = 0
allow(mock_io).to receive(:read_nonblock) do |buffer_size, *_|
read_counter += 1
raise 'Test did not raise HeaderReadTimeout' if read_counter > 10
sleep 0.01
'H' * buffer_size
end
end
it 'raises a timeout error' do
expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
end
context 'when not passing start_time' do
subject(:readuntil) do
Gitlab::BufferedIo.new(mock_io).readuntil('a', false)
end
it 'raises a timeout error' do
expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
end
end
end
end
end
# rubocop:enable Style/FrozenStringLiteralComment

View File

@ -21,7 +21,9 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
end
context 'with available_runner_releases configured up to 14.1.1' do
let(:available_runner_releases) { %w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2 14.1.0 14.1.1] }
let(:available_runner_releases) do
%w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2-rc1 14.0.2 14.1.0 14.1.1]
end
context 'with nil runner_version' do
let(:runner_version) { nil }
@ -62,10 +64,11 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
'v14.1.0/1.1.0' | :recommended # suffixes are correctly handled
'v14.1.0' | :recommended # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes
'v14.0.1' | :recommended # recommended upgrade since 14.0.2 is available
'v14.0.2-rc1' | :recommended # recommended upgrade since 14.0.2 is available and we'll move out of a release candidate
'v14.0.2' | :not_available # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version
'v13.10.1' | :available # available upgrade: 14.1.1
'v13.10.1~beta.1574.gf6ea9389' | :available # suffixes are correctly handled
'v13.10.1/1.1.0' | :available # suffixes are correctly handled
'v13.10.1~beta.1574.gf6ea9389' | :recommended # suffixes are correctly handled, official 13.10.1 is available
'v13.10.1/1.1.0' | :recommended # suffixes are correctly handled, official 13.10.1 is available
'v13.10.0' | :recommended # recommended upgrade since 13.10.1 is available
'v13.9.2' | :recommended # recommended upgrade since backports are no longer released for this version
'v13.9.0' | :recommended # recommended upgrade since backports are no longer released for this version

View File

@ -0,0 +1,72 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
subject(:importer) { described_class.new(project, user.id) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:commit_id) { nil }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
'actor' => { 'id' => 4, 'login' => 'alice' },
'event' => 'closed',
'created_at' => '2022-04-26 18:30:53 UTC',
'commit_id' => commit_id,
'issue_db_id' => issue.id
)
end
let(:expected_event_attrs) do
{
project_id: project.id,
author_id: user.id,
target_id: issue.id,
target_type: Issue.name,
action: 'closed',
created_at: issue_event.created_at,
updated_at: issue_event.created_at
}.stringify_keys
end
let(:expected_state_event_attrs) do
{
user_id: user.id,
issue_id: issue.id,
state: 'closed',
created_at: issue_event.created_at
}.stringify_keys
end
it 'creates expected event and state event' do
importer.execute(issue_event)
expect(issue.events.count).to eq 1
expect(issue.events[0].attributes)
.to include expected_event_attrs
expect(issue.resource_state_events.count).to eq 1
expect(issue.resource_state_events[0].attributes)
.to include expected_state_event_attrs
end
context 'when closed by commit' do
let!(:closing_commit) { create(:commit, project: project) }
let(:commit_id) { closing_commit.id }
it 'creates expected event and state event' do
importer.execute(issue_event)
expect(issue.events.count).to eq 1
state_event = issue.resource_state_events.last
expect(state_event.source_commit).to eq commit_id[0..40]
end
end
end

View File

@ -0,0 +1,77 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab_redis_cache do
let(:importer) { described_class.new(issue_event, project, client) }
let(:project) { create(:project) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
'actor' => { 'id' => actor_id, 'login' => 'alice' },
'event' => event_name,
'commit_id' => '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
'commit_url' =>
'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
'created_at' => '2022-04-26 18:30:53 UTC',
'performed_via_github_app' => nil
)
end
let(:actor_id) { user.id }
let(:event_name) { 'closed' }
shared_examples 'triggers specific event importer' do |importer_class|
it importer_class.name do
specific_importer = double(importer_class.name) # rubocop:disable RSpec/VerifiedDoubles
expect(importer_class)
.to receive(:new).with(project, user.id)
.and_return(specific_importer)
expect(specific_importer).to receive(:execute).with(issue_event)
importer.execute
end
end
describe '#execute' do
before do
allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
allow(finder).to receive(:author_id_for)
.with(issue_event, author_key: :actor)
.and_return(user.id, true)
end
issue_event.attributes[:issue_db_id] = issue.id
end
context "when it's closed issue event" do
let(:event_name) { 'closed' }
it_behaves_like 'triggers specific event importer',
Gitlab::GithubImport::Importer::Events::Closed
end
context "when it's unknown issue event" do
let(:event_name) { 'fake' }
it 'logs warning and skips' do
expect(Gitlab::GithubImport::Logger).to receive(:debug)
.with(
message: 'UNSUPPORTED_EVENT_TYPE',
event_type: issue_event.event,
event_github_id: issue_event.id
)
importer.execute
end
end
end
end

View File

@ -0,0 +1,128 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter do
let(:client) { double }
let_it_be(:project) { create(:project, :import_started, import_source: 'http://somegithub.com') }
let_it_be(:issue) { create(:issue, project: project) }
subject { described_class.new(project, client, parallel: parallel) }
let(:parallel) { true }
it { is_expected.to include_module(Gitlab::GithubImport::ParallelScheduling) }
describe '#importer_class' do
it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::IssueEventImporter) }
end
describe '#representation_class' do
it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::IssueEvent) }
end
describe '#sidekiq_worker_class' do
it { expect(subject.sidekiq_worker_class).to eq(Gitlab::GithubImport::ImportIssueEventWorker) }
end
describe '#object_type' do
it { expect(subject.object_type).to eq(:issue_event) }
end
describe '#collection_method' do
it { expect(subject.collection_method).to eq(:issue_timeline) }
end
describe '#page_counter_id' do
it { expect(subject.page_counter_id(issue)).to eq("issues/#{issue.iid}/issue_timeline") }
end
describe '#id_for_already_imported_cache' do
let(:event) { instance_double('Event', id: 1) }
it { expect(subject.id_for_already_imported_cache(event)).to eq(1) }
end
describe '#collection_options' do
it do
expect(subject.collection_options)
.to eq({ state: 'all', sort: 'created', direction: 'asc' })
end
end
describe '#each_object_to_import', :clean_gitlab_redis_cache do
let(:issue_event) do
struct = Struct.new(:id, :event, :created_at, :issue_db_id, keyword_init: true)
struct.new(id: rand(10), event: 'closed', created_at: '2022-04-26 18:30:53 UTC')
end
let(:page) do
instance_double(
Gitlab::GithubImport::Client::Page,
number: 1, objects: [issue_event]
)
end
let(:page_counter) { instance_double(Gitlab::GithubImport::PageCounter) }
before do
allow(client).to receive(:each_page)
.once
.with(
:issue_timeline,
project.import_source,
issue.iid,
{ state: 'all', sort: 'created', direction: 'asc', page: 1 }
).and_yield(page)
end
it 'imports each issue event page by page' do
counter = 0
subject.each_object_to_import do |object|
expect(object).to eq issue_event
expect(issue_event.issue_db_id).to eq issue.id
counter += 1
end
expect(counter).to eq 1
end
it 'triggers page number increment' do
expect(Gitlab::GithubImport::PageCounter)
.to receive(:new).with(project, 'issues/1/issue_timeline')
.and_return(page_counter)
expect(page_counter).to receive(:current).and_return(1)
expect(page_counter)
.to receive(:set).with(page.number).and_return(true)
counter = 0
subject.each_object_to_import { counter += 1 }
expect(counter).to eq 1
end
context 'when page is already processed' do
before do
page_counter = Gitlab::GithubImport::PageCounter.new(
project, subject.page_counter_id(issue)
)
page_counter.set(page.number)
end
it "doesn't process this page" do
counter = 0
subject.each_object_to_import { counter += 1 }
expect(counter).to eq 0
end
end
context 'when event is already processed' do
it "doesn't process this event" do
subject.mark_as_imported(issue_event)
counter = 0
subject.each_object_to_import { counter += 1 }
expect(counter).to eq 0
end
end
end
end

View File

@ -0,0 +1,111 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
shared_examples 'an IssueEvent' do
it 'returns an instance of IssueEvent' do
expect(issue_event).to be_an_instance_of(described_class)
end
context 'the returned IssueEvent' do
it 'includes the issue event id' do
expect(issue_event.id).to eq(6501124486)
end
it 'includes the issue event "event"' do
expect(issue_event.event).to eq('closed')
end
it 'includes the issue event commit_id' do
expect(issue_event.commit_id).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
end
it 'includes the issue_db_id' do
expect(issue_event.issue_db_id).to eq(100500)
end
context 'when actor data present' do
it 'includes the actor details' do
expect(issue_event.actor)
.to be_an_instance_of(Gitlab::GithubImport::Representation::User)
expect(issue_event.actor.id).to eq(4)
expect(issue_event.actor.login).to eq('alice')
end
end
context 'when actor data is empty' do
let(:with_actor) { false }
it 'does not return such info' do
expect(issue_event.actor).to eq nil
end
end
it 'includes the created timestamp' do
expect(issue_event.created_at).to eq('2022-04-26 18:30:53 UTC')
end
end
describe '#github_identifiers' do
it 'returns a hash with needed identifiers' do
expect(issue_event.github_identifiers).to eq({ id: 6501124486 })
end
end
end
describe '.from_api_response' do
let(:response) do
event_resource = Struct.new(
:id, :node_id, :url, :actor, :event, :commit_id, :commit_url,
:issue_db_id, :created_at, :performed_via_github_app,
keyword_init: true
)
user_resource = Struct.new(:id, :login, keyword_init: true)
event_resource.new(
id: 6501124486,
node_id: 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
url: 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
actor: with_actor ? user_resource.new(id: 4, login: 'alice') : nil,
event: 'closed',
commit_id: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
commit_url: 'https://api.github.com/repos/octocat/Hello-World/commits'\
'/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
issue_db_id: 100500,
created_at: '2022-04-26 18:30:53 UTC',
performed_via_github_app: nil
)
end
let(:with_actor) { true }
it_behaves_like 'an IssueEvent' do
let(:issue_event) { described_class.from_api_response(response) }
end
end
describe '.from_json_hash' do
it_behaves_like 'an IssueEvent' do
let(:hash) do
{
'id' => 6501124486,
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
'actor' => (with_actor ? { 'id' => 4, 'login' => 'alice' } : nil),
'event' => 'closed',
'commit_id' => '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
'commit_url' =>
'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
"issue_db_id" => 100500,
'created_at' => '2022-04-26 18:30:53 UTC',
'performed_via_github_app' => nil
}
end
let(:with_actor) { true }
let(:issue_event) { described_class.from_json_hash(hash) }
end
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::SingleEndpointNotesImporting do
let(:importer_class) do
Class.new do
def self.name
'MyImporter'
end
include(Gitlab::GithubImport::SingleEndpointNotesImporting)
end
end
let(:importer_instance) { importer_class.new }
describe '#parent_collection' do
it { expect { importer_instance.parent_collection }.to raise_error(NotImplementedError) }
end
describe '#parent_imported_cache_key' do
it { expect { importer_instance.parent_imported_cache_key }.to raise_error(NotImplementedError) }
end
describe '#page_counter_id' do
it { expect { importer_instance.page_counter_id(build(:merge_request)) }.to raise_error(NotImplementedError) }
end
end

View File

@ -420,6 +420,8 @@ project:
- zentao_integration
# dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417
- dingtalk_integration
# dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/640
- feishu_integration
- redmine_integration
- youtrack_integration
- custom_issue_tracker_integration

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

@ -2,28 +2,44 @@
require 'fast_spec_helper'
RSpec.describe 'Gitlab::VersionInfo' do
RSpec.describe Gitlab::VersionInfo do
before do
@unknown = Gitlab::VersionInfo.new
@v0_0_1 = Gitlab::VersionInfo.new(0, 0, 1)
@v0_1_0 = Gitlab::VersionInfo.new(0, 1, 0)
@v1_0_0 = Gitlab::VersionInfo.new(1, 0, 0)
@v1_0_1 = Gitlab::VersionInfo.new(1, 0, 1)
@v1_1_0 = Gitlab::VersionInfo.new(1, 1, 0)
@v2_0_0 = Gitlab::VersionInfo.new(2, 0, 0)
@unknown = described_class.new
@v0_0_1 = described_class.new(0, 0, 1)
@v0_1_0 = described_class.new(0, 1, 0)
@v1_0_0 = described_class.new(1, 0, 0)
@v1_0_1 = described_class.new(1, 0, 1)
@v1_0_1_b1 = described_class.new(1, 0, 1, '-b1')
@v1_0_1_rc1 = described_class.new(1, 0, 1, '-rc1')
@v1_0_1_rc2 = described_class.new(1, 0, 1, '-rc2')
@v1_1_0 = described_class.new(1, 1, 0)
@v1_1_0_beta1 = described_class.new(1, 1, 0, '-beta1')
@v2_0_0 = described_class.new(2, 0, 0)
@v13_10_1_1574_89 = described_class.parse("v13.10.1~beta.1574.gf6ea9389", parse_suffix: true)
@v13_10_1_1575_89 = described_class.parse("v13.10.1~beta.1575.gf6ea9389", parse_suffix: true)
@v13_10_1_1575_90 = described_class.parse("v13.10.1~beta.1575.gf6ea9390", parse_suffix: true)
end
describe '>' do
it { expect(@v2_0_0).to be > @v1_1_0 }
it { expect(@v1_1_0).to be > @v1_0_1 }
it { expect(@v1_0_1_b1).to be > @v1_0_0 }
it { expect(@v1_0_1_rc1).to be > @v1_0_0 }
it { expect(@v1_0_1_rc1).to be > @v1_0_1_b1 }
it { expect(@v1_0_1_rc2).to be > @v1_0_1_rc1 }
it { expect(@v1_0_1).to be > @v1_0_1_rc1 }
it { expect(@v1_0_1).to be > @v1_0_1_rc2 }
it { expect(@v1_0_1).to be > @v1_0_0 }
it { expect(@v1_0_0).to be > @v0_1_0 }
it { expect(@v1_1_0_beta1).to be > @v1_0_1_rc2 }
it { expect(@v1_1_0).to be > @v1_1_0_beta1 }
it { expect(@v0_1_0).to be > @v0_0_1 }
end
describe '>=' do
it { expect(@v2_0_0).to be >= Gitlab::VersionInfo.new(2, 0, 0) }
it { expect(@v2_0_0).to be >= described_class.new(2, 0, 0) }
it { expect(@v2_0_0).to be >= @v1_1_0 }
it { expect(@v1_0_1_rc2).to be >= @v1_0_1_rc1 }
end
describe '<' do
@ -31,64 +47,115 @@ RSpec.describe 'Gitlab::VersionInfo' do
it { expect(@v0_1_0).to be < @v1_0_0 }
it { expect(@v1_0_0).to be < @v1_0_1 }
it { expect(@v1_0_1).to be < @v1_1_0 }
it { expect(@v1_0_0).to be < @v1_0_1_rc2 }
it { expect(@v1_0_1_rc1).to be < @v1_0_1 }
it { expect(@v1_0_1_rc1).to be < @v1_0_1_rc2 }
it { expect(@v1_0_1_rc2).to be < @v1_0_1 }
it { expect(@v1_1_0).to be < @v2_0_0 }
it { expect(@v13_10_1_1574_89).to be < @v13_10_1_1575_89 }
it { expect(@v13_10_1_1575_89).to be < @v13_10_1_1575_90 }
end
describe '<=' do
it { expect(@v0_0_1).to be <= Gitlab::VersionInfo.new(0, 0, 1) }
it { expect(@v0_0_1).to be <= described_class.new(0, 0, 1) }
it { expect(@v0_0_1).to be <= @v0_1_0 }
it { expect(@v1_0_1_b1).to be <= @v1_0_1_rc1 }
it { expect(@v1_0_1_rc1).to be <= @v1_0_1_rc2 }
it { expect(@v1_1_0_beta1).to be <= @v1_1_0 }
end
describe '==' do
it { expect(@v0_0_1).to eq(Gitlab::VersionInfo.new(0, 0, 1)) }
it { expect(@v0_1_0).to eq(Gitlab::VersionInfo.new(0, 1, 0)) }
it { expect(@v1_0_0).to eq(Gitlab::VersionInfo.new(1, 0, 0)) }
it { expect(@v0_0_1).to eq(described_class.new(0, 0, 1)) }
it { expect(@v0_1_0).to eq(described_class.new(0, 1, 0)) }
it { expect(@v1_0_0).to eq(described_class.new(1, 0, 0)) }
it { expect(@v1_0_1_rc1).to eq(described_class.new(1, 0, 1, '-rc1')) }
end
describe '!=' do
it { expect(@v0_0_1).not_to eq(@v0_1_0) }
it { expect(@v1_0_1_rc1).not_to eq(@v1_0_1_rc2) }
end
describe '.unknown' do
it { expect(@unknown).not_to be @v0_0_1 }
it { expect(@unknown).not_to be Gitlab::VersionInfo.new }
it { expect(@unknown).not_to be described_class.new }
it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) }
it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) }
end
describe '.parse' do
it { expect(Gitlab::VersionInfo.parse("1.0.0")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0.1")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0-ee")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("git 1.0.0b1")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("git 1.0b1")).not_to be_valid }
it { expect(described_class.parse("1.0.0")).to eq(@v1_0_0) }
it { expect(described_class.parse("1.0.0.1")).to eq(@v1_0_0) }
it { expect(described_class.parse("1.0.0-ee")).to eq(@v1_0_0) }
it { expect(described_class.parse("1.0.0-rc1")).to eq(@v1_0_0) }
it { expect(described_class.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) }
it { expect(described_class.parse("git 1.0.0b1")).to eq(@v1_0_0) }
it { expect(described_class.parse("git 1.0b1")).not_to be_valid }
context 'with parse_suffix: true' do
let(:versions) do
<<-VERSIONS.lines
0.0.1
0.1.0
1.0.0
1.0.1-b1
1.0.1-rc1
1.0.1-rc2
1.0.1
1.1.0-beta1
1.1.0
2.0.0
v13.10.0-pre
v13.10.0-rc1
v13.10.0-rc2
v13.10.0
v13.10.1~beta.1574.gf6ea9389
v13.10.1~beta.1575.gf6ea9389
v13.10.1-rc1
v13.10.1-rc2
v13.10.1
VERSIONS
end
let(:parsed_versions) do
versions.map(&:strip).map { |version| described_class.parse(version, parse_suffix: true) }
end
it 'versions are returned in a correct order' do
expect(parsed_versions.shuffle.sort).to eq(parsed_versions)
end
end
end
describe '.to_s' do
it { expect(@v1_0_0.to_s).to eq("1.0.0") }
it { expect(@v1_0_1_rc1.to_s).to eq("1.0.1-rc1") }
it { expect(@unknown.to_s).to eq("Unknown") }
end
describe '.hash' do
it { expect(Gitlab::VersionInfo.parse("1.0.0").hash).to eq(@v1_0_0.hash) }
it { expect(Gitlab::VersionInfo.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) }
it { expect(Gitlab::VersionInfo.parse("1.0.1b1").hash).to eq(@v1_0_1.hash) }
it { expect(described_class.parse("1.0.0").hash).to eq(@v1_0_0.hash) }
it { expect(described_class.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) }
it { expect(described_class.parse("1.0.1b1").hash).to eq(@v1_0_1.hash) }
it { expect(described_class.parse("1.0.1-rc1", parse_suffix: true).hash).to eq(@v1_0_1_rc1.hash) }
end
describe '.eql?' do
it { expect(Gitlab::VersionInfo.parse("1.0.0").eql?(@v1_0_0)).to be_truthy }
it { expect(Gitlab::VersionInfo.parse("1.0.0.1").eql?(@v1_0_0)).to be_truthy }
it { expect(described_class.parse("1.0.0").eql?(@v1_0_0)).to be_truthy }
it { expect(described_class.parse("1.0.0.1").eql?(@v1_0_0)).to be_truthy }
it { expect(@v1_0_1_rc1.eql?(@v1_0_1_rc1)).to be_truthy }
it { expect(@v1_0_1_rc1.eql?(@v1_0_1_rc2)).to be_falsey }
it { expect(@v1_0_1_rc1.eql?(@v1_0_1)).to be_falsey }
it { expect(@v1_0_1.eql?(@v1_0_0)).to be_falsey }
it { expect(@v1_1_0.eql?(@v1_0_0)).to be_falsey }
it { expect(@v1_0_0.eql?(@v1_0_0)).to be_truthy }
it { expect([@v1_0_0, @v1_1_0, @v1_0_0].uniq).to eq [@v1_0_0, @v1_1_0] }
it { expect([@v1_0_0, @v1_1_0, @v1_0_0, @v1_0_1_rc1, @v1_0_1_rc1].uniq).to eq [@v1_0_0, @v1_1_0, @v1_0_1_rc1] }
end
describe '.same_minor_version?' do
it { expect(@v0_1_0.same_minor_version?(@v0_0_1)).to be_falsey }
it { expect(@v1_0_1.same_minor_version?(@v1_0_0)).to be_truthy }
it { expect(@v1_0_1_rc1.same_minor_version?(@v1_0_0)).to be_truthy }
it { expect(@v1_0_0.same_minor_version?(@v1_0_1)).to be_truthy }
it { expect(@v1_1_0.same_minor_version?(@v1_0_0)).to be_falsey }
it { expect(@v2_0_0.same_minor_version?(@v1_0_0)).to be_falsey }
@ -98,5 +165,6 @@ RSpec.describe 'Gitlab::VersionInfo' do
it { expect(@v0_1_0.without_patch).to eq(@v0_1_0) }
it { expect(@v1_0_0.without_patch).to eq(@v1_0_0) }
it { expect(@v1_0_1.without_patch).to eq(@v1_0_0) }
it { expect(@v1_0_1_rc1.without_patch).to eq(@v1_0_0) }
end
end

Some files were not shown because too many files have changed in this diff Show More