Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8754d20bbb
commit
20d8491f30
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlLink, GlIcon, GlButton } from '@gitlab/ui';
|
import { GlLink, GlIcon, GlButton } from '@gitlab/ui';
|
||||||
|
import { __ } from '~/locale';
|
||||||
import {
|
import {
|
||||||
issuableIconMap,
|
issuableIconMap,
|
||||||
issuableQaClassMap,
|
issuableQaClassMap,
|
||||||
|
@ -96,6 +97,11 @@ export default {
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isOpen: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasRelatedIssues() {
|
hasRelatedIssues() {
|
||||||
return this.relatedIssues.length > 0;
|
return this.relatedIssues.length > 0;
|
||||||
|
@ -139,6 +145,17 @@ export default {
|
||||||
qaClass() {
|
qaClass() {
|
||||||
return issuableQaClassMap[this.issuableType];
|
return issuableQaClassMap[this.issuableType];
|
||||||
},
|
},
|
||||||
|
toggleIcon() {
|
||||||
|
return this.isOpen ? 'chevron-lg-up' : 'chevron-lg-down';
|
||||||
|
},
|
||||||
|
toggleLabel() {
|
||||||
|
return this.isOpen ? __('Collapse') : __('Expand');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleToggle() {
|
||||||
|
this.isOpen = !this.isOpen;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
linkedIssueTypesTextMap,
|
linkedIssueTypesTextMap,
|
||||||
};
|
};
|
||||||
|
@ -148,8 +165,8 @@ export default {
|
||||||
<div id="related-issues" class="related-issues-block gl-mt-5">
|
<div id="related-issues" class="related-issues-block gl-mt-5">
|
||||||
<div class="card card-slim gl-overflow-hidden">
|
<div class="card card-slim gl-overflow-hidden">
|
||||||
<div
|
<div
|
||||||
:class="{ 'panel-empty-heading border-bottom-0': !hasBody }"
|
:class="{ 'panel-empty-heading border-bottom-0': !hasBody, 'gl-border-b-0': !isOpen }"
|
||||||
class="card-header gl-display-flex gl-justify-content-space-between gl-bg-gray-10"
|
class="card-header gl-display-flex gl-justify-content-space-between gl-align-items-center gl-bg-gray-10"
|
||||||
>
|
>
|
||||||
<h3
|
<h3
|
||||||
class="card-title h5 position-relative gl-my-0 gl-display-flex gl-align-items-center gl-h-7"
|
class="card-title h5 position-relative gl-my-0 gl-display-flex gl-align-items-center gl-h-7"
|
||||||
|
@ -182,6 +199,7 @@ export default {
|
||||||
<gl-button
|
<gl-button
|
||||||
v-if="canAdmin"
|
v-if="canAdmin"
|
||||||
data-qa-selector="related_issues_plus_button"
|
data-qa-selector="related_issues_plus_button"
|
||||||
|
data-testid="add-button"
|
||||||
icon="plus"
|
icon="plus"
|
||||||
:aria-label="addIssuableButtonText"
|
:aria-label="addIssuableButtonText"
|
||||||
:class="qaClass"
|
:class="qaClass"
|
||||||
|
@ -190,12 +208,23 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</h3>
|
||||||
<slot name="header-actions"></slot>
|
<slot name="header-actions"></slot>
|
||||||
|
<div class="gl-pl-4 gl-ml-3">
|
||||||
|
<gl-button
|
||||||
|
category="tertiary"
|
||||||
|
:icon="toggleIcon"
|
||||||
|
:aria-label="toggleLabel"
|
||||||
|
data-testid="toggle-links"
|
||||||
|
@click="handleToggle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
v-if="isOpen"
|
||||||
class="linked-issues-card-body gl-bg-gray-10"
|
class="linked-issues-card-body gl-bg-gray-10"
|
||||||
:class="{
|
:class="{
|
||||||
'gl-p-5': isFormVisible || shouldShowTokenBody,
|
'gl-p-5': isFormVisible || shouldShowTokenBody,
|
||||||
}"
|
}"
|
||||||
|
data-testid="related-issues-body"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isFormVisible"
|
v-if="isFormVisible"
|
||||||
|
|
|
@ -172,7 +172,7 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<gl-form-group
|
<gl-form-group
|
||||||
v-if="isEditing"
|
v-if="isEditing"
|
||||||
class="gl-my-5"
|
class="gl-my-5 gl-border-t gl-pt-6"
|
||||||
:label="__('Description')"
|
:label="__('Description')"
|
||||||
label-for="work-item-description"
|
label-for="work-item-description"
|
||||||
>
|
>
|
||||||
|
@ -182,7 +182,7 @@ export default {
|
||||||
:is-submitting="isSubmitting"
|
:is-submitting="isSubmitting"
|
||||||
:markdown-preview-path="markdownPreviewPath"
|
:markdown-preview-path="markdownPreviewPath"
|
||||||
:markdown-docs-path="$options.markdownDocsPath"
|
:markdown-docs-path="$options.markdownDocsPath"
|
||||||
class="gl-p-3 bordered-box"
|
class="gl-p-3 bordered-box gl-mt-5"
|
||||||
>
|
>
|
||||||
<template #textarea>
|
<template #textarea>
|
||||||
<textarea
|
<textarea
|
||||||
|
@ -217,9 +217,9 @@ export default {
|
||||||
}}</gl-button>
|
}}</gl-button>
|
||||||
</div>
|
</div>
|
||||||
</gl-form-group>
|
</gl-form-group>
|
||||||
<div v-else class="gl-mb-5">
|
<div v-else class="gl-mb-5 gl-border-t">
|
||||||
<div class="gl-display-flex gl-align-items-center gl-mb-5">
|
<div class="gl-display-inline-flex gl-align-items-center gl-mb-5">
|
||||||
<h3 class="gl-font-base gl-my-0">{{ __('Description') }}</h3>
|
<label class="d-block col-form-label gl-mr-5">{{ __('Description') }}</label>
|
||||||
<gl-button
|
<gl-button
|
||||||
v-if="canEdit"
|
v-if="canEdit"
|
||||||
class="gl-ml-auto"
|
class="gl-ml-auto"
|
||||||
|
|
|
@ -72,36 +72,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-nav-container .nav-controls {
|
.group-nav-container {
|
||||||
.group-filter-form {
|
.nav-controls {
|
||||||
flex: 1 1 auto;
|
.group-filter-form {
|
||||||
margin-right: $gl-padding-8;
|
flex: 1 1 auto;
|
||||||
|
margin-right: $gl-padding-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu-right {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
.dropdown,
|
||||||
|
.dropdown .dropdown-toggle,
|
||||||
|
.btn-success {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-filter-form,
|
||||||
|
.dropdown {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&,
|
||||||
|
.group-filter-form,
|
||||||
|
.group-filter-form-field,
|
||||||
|
.dropdown,
|
||||||
|
.dropdown .dropdown-toggle,
|
||||||
|
.btn-success {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu-right {
|
// Remove this selector once https://gitlab.com/gitlab-org/gitlab/-/issues/370050 is addressed.
|
||||||
margin-top: 0;
|
.scrolling-tabs-container {
|
||||||
}
|
width: 100%;
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
|
||||||
.dropdown,
|
|
||||||
.dropdown .dropdown-toggle,
|
|
||||||
.btn-success {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-filter-form,
|
|
||||||
.dropdown {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&,
|
|
||||||
.group-filter-form,
|
|
||||||
.group-filter-form-field,
|
|
||||||
.dropdown,
|
|
||||||
.dropdown .dropdown-toggle,
|
|
||||||
.btn-success {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,25 +144,15 @@ module Namespaces
|
||||||
end
|
end
|
||||||
|
|
||||||
def self_and_descendants_with_comparison_operators(include_self: true)
|
def self_and_descendants_with_comparison_operators(include_self: true)
|
||||||
base = all.select(:traversal_ids)
|
base = all.select(:id, :traversal_ids)
|
||||||
base = base.select(:id) if Feature.enabled?(:linear_scopes_superset)
|
|
||||||
base_cte = base.as_cte(:descendants_base_cte)
|
base_cte = base.as_cte(:descendants_base_cte)
|
||||||
|
|
||||||
namespaces = Arel::Table.new(:namespaces)
|
namespaces = Arel::Table.new(:namespaces)
|
||||||
|
|
||||||
withs = [base_cte.to_arel]
|
superset_cte = self.superset_cte(base_cte.table.name)
|
||||||
froms = []
|
withs = [base_cte.to_arel, superset_cte.to_arel]
|
||||||
|
|
||||||
if Feature.enabled?(:linear_scopes_superset)
|
|
||||||
superset_cte = self.superset_cte(base_cte.table.name)
|
|
||||||
withs += [superset_cte.to_arel]
|
|
||||||
froms = [superset_cte.table]
|
|
||||||
else
|
|
||||||
froms = [base_cte.table]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Order is important. namespace should be last to handle future joins.
|
# Order is important. namespace should be last to handle future joins.
|
||||||
froms += [namespaces]
|
froms = [superset_cte.table, namespaces]
|
||||||
|
|
||||||
base_ref = froms.first
|
base_ref = froms.first
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
name: linear_scopes_superset
|
name: import_export_web_upload_stream
|
||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87643
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93379
|
||||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/362687
|
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/370127
|
||||||
milestone: '15.1'
|
milestone: '15.3'
|
||||||
type: development
|
type: development
|
||||||
group: group::workspace
|
group: group::import
|
||||||
default_enabled: false
|
default_enabled: false
|
|
@ -1,7 +1,7 @@
|
||||||
#
|
#
|
||||||
# REQUIRED FIELDS
|
# REQUIRED FIELDS
|
||||||
#
|
#
|
||||||
- name: "`omniauth-cas3` gem" # (required) The name of the feature to be deprecated
|
- name: "CAS OmniAuth provider" # (required) The name of the feature to be deprecated
|
||||||
announcement_milestone: "15.3" # (required) The milestone when this feature was first announced as deprecated.
|
announcement_milestone: "15.3" # (required) The milestone when this feature was first announced as deprecated.
|
||||||
announcement_date: "2022-08-22" # (required) The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
|
announcement_date: "2022-08-22" # (required) The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
|
||||||
removal_milestone: "16.0" # (required) The milestone when this feature is planned to be removed
|
removal_milestone: "16.0" # (required) The milestone when this feature is planned to be removed
|
||||||
|
@ -11,5 +11,6 @@
|
||||||
stage: Manage # (required) String value of the stage that the feature was created in. e.g., Growth
|
stage: Manage # (required) String value of the stage that the feature was created in. e.g., Growth
|
||||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/369127 # (required) Link to the deprecation issue in GitLab
|
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/369127 # (required) Link to the deprecation issue in GitLab
|
||||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||||
The `omniauth-cas3` gem will be removed in our next major release, GitLab 16.0. This gem sees very little use and
|
The `omniauth-cas3` gem that provides GitLab with the CAS OmniAuth provider will be removed in our next major
|
||||||
its lack of upstream maintence is preventing GitLab's [upgrade to OmniAuth 2.0](https://gitlab.com/gitlab-org/gitlab/-/issues/30073).
|
release, GitLab 16.0. This gem sees very little use and its lack of upstream maintenance is preventing GitLab's
|
||||||
|
[upgrade to OmniAuth 2.0](https://gitlab.com/gitlab-org/gitlab/-/issues/30073).
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#
|
#
|
||||||
# REQUIRED FIELDS
|
# REQUIRED FIELDS
|
||||||
#
|
#
|
||||||
- name: "`omniauth_crowd` gem" # (required) The name of the feature to be deprecated
|
- name: "Atlassian Crowd OmniAuth provider" # (required) The name of the feature to be deprecated
|
||||||
announcement_milestone: "15.3" # (required) The milestone when this feature was first announced as deprecated.
|
announcement_milestone: "15.3" # (required) The milestone when this feature was first announced as deprecated.
|
||||||
announcement_date: "2022-08-22" # (required) The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
|
announcement_date: "2022-08-22" # (required) The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
|
||||||
removal_milestone: "16.0" # (required) The milestone when this feature is planned to be removed
|
removal_milestone: "16.0" # (required) The milestone when this feature is planned to be removed
|
||||||
|
@ -11,6 +11,7 @@
|
||||||
stage: Manage # (required) String value of the stage that the feature was created in. e.g., Growth
|
stage: Manage # (required) String value of the stage that the feature was created in. e.g., Growth
|
||||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/369117 # (required) Link to the deprecation issue in GitLab
|
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/369117 # (required) Link to the deprecation issue in GitLab
|
||||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||||
The `omniauth_crowd` gem will be removed in our next major release, GitLab 16.0. This gem sees very little use and
|
The `omniauth_crowd` gem that provides GitLab with the Atlassian Crowd OmniAuth provider will be removed in our
|
||||||
its [lack of compatability](https://github.com/robdimarco/omniauth_crowd/issues/37) with OmniAuth 2.0 is blocking
|
next major release, GitLab 16.0. This gem sees very little use and its
|
||||||
our upgrade.
|
[lack of compatibility](https://github.com/robdimarco/omniauth_crowd/issues/37) with OmniAuth 2.0 is
|
||||||
|
[blocking our upgrade](https://gitlab.com/gitlab-org/gitlab/-/issues/30073).
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth
|
stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth
|
||||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/368195 # (required) Link to the deprecation issue in GitLab
|
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/368195 # (required) Link to the deprecation issue in GitLab
|
||||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||||
The [**Maximum number of active pipelines per project** limit](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#set-cicd-limits) was never enabled by default and will be removed in GitLab 16.0. This limit can also be configured in the rails console under [`ci_active_pipelines`](https://docs.gitlab.com/ee/administration/instance_limits.html#number-of-pipelines-running-concurrently). Instead, use the other recommended rate limits that offer similar protection:
|
The [**Maximum number of active pipelines per project** limit](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#set-cicd-limits) was never enabled by default and will be removed in GitLab 16.0. This limit can also be configured in the Rails console under [`ci_active_pipelines`](https://docs.gitlab.com/ee/administration/instance_limits.html#number-of-pipelines-running-concurrently). Instead, use the other recommended rate limits that offer similar protection:
|
||||||
|
|
||||||
- [**Pipelines rate limits**](https://docs.gitlab.com/ee/user/admin_area/settings/rate_limit_on_pipelines_creation.html).
|
- [**Pipelines rate limits**](https://docs.gitlab.com/ee/user/admin_area/settings/rate_limit_on_pipelines_creation.html).
|
||||||
- [**Total number of jobs in currently active pipelines**](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#set-cicd-limits).
|
- [**Total number of jobs in currently active pipelines**](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#set-cicd-limits).
|
||||||
|
|
|
@ -5,7 +5,11 @@ group: Authentication and Authorization
|
||||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||||
---
|
---
|
||||||
|
|
||||||
# Atlassian Crowd OmniAuth Provider **(FREE SELF)**
|
# Atlassian Crowd OmniAuth provider (deprecated) **(FREE SELF)**
|
||||||
|
|
||||||
|
WARNING:
|
||||||
|
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/369117) in GitLab 15.3 and is planned for
|
||||||
|
removal in 16.0.
|
||||||
|
|
||||||
Authenticate to GitLab using the Atlassian Crowd OmniAuth provider. Enabling
|
Authenticate to GitLab using the Atlassian Crowd OmniAuth provider. Enabling
|
||||||
this provider also allows Crowd authentication for Git-over-https requests.
|
this provider also allows Crowd authentication for Git-over-https requests.
|
||||||
|
|
|
@ -883,13 +883,15 @@ gitaly['cgroups_repositories_cpu_shares'] => 512
|
||||||
on the repository the command is for. A circular hashing algorithm assigns
|
on the repository the command is for. A circular hashing algorithm assigns
|
||||||
Git commands to these cgroups, so a Git command for a repository is
|
Git commands to these cgroups, so a Git command for a repository is
|
||||||
always assigned to the same cgroup.
|
always assigned to the same cgroup.
|
||||||
- `cgroups_repositories_memory_bytes` is the total memory limit that is imposed collectively on all
|
- `cgroups_repositories_memory_bytes` is the total memory limit imposed on all Git processes contained in a repository cgroup.
|
||||||
Git processes that Gitaly spawns. 0 implies no limit. This value cannot exceed
|
A repository cgroup is one that contains Git processes for one or more repositories.
|
||||||
that of the top level `cgroups_memory_bytes`.
|
A consistent hash is used to assign repositories to these cgroups such that a Git process for a given repository always ends up in the same cgroup.
|
||||||
- `cgroups_repositories_cpu_shares` is the CPU limit that is imposed collectively on all Git
|
0 implies no limit. This value cannot exceed that of the top level `cgroups_memory_bytes`.
|
||||||
processes Gitaly spawns. 0 implies no limit. The maximum is 1024 shares,
|
- `cgroups_repositories_cpu_shares` is the CPU limit that is imposed on all Git processes contained in a repository cgroup.
|
||||||
which represents 100% of CPU. This value cannot exceed that of the top
|
A repository cgroup is one that contains Git processes for one or more repositories.
|
||||||
level`cgroups_cpu_shares`.
|
A consistent hash is used to assign repositories to these cgroups such that a Git process for a given repository always ends up in the same cgroup.
|
||||||
|
0 implies no limit. The maximum is 1024 shares, which represents 100% of CPU.
|
||||||
|
This value cannot exceed that of the top level`cgroups_cpu_shares`.
|
||||||
|
|
||||||
#### Configure cgroups (legacy method)
|
#### Configure cgroups (legacy method)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,11 @@ group: Authentication and Authorization
|
||||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||||
---
|
---
|
||||||
|
|
||||||
# CAS OmniAuth Provider **(FREE SELF)**
|
# CAS OmniAuth provider (deprecated) **(FREE SELF)**
|
||||||
|
|
||||||
|
WARNING:
|
||||||
|
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/369127) in GitLab 15.3 and is planned for
|
||||||
|
removal in 16.0.
|
||||||
|
|
||||||
To enable the CAS OmniAuth provider you must register your application with your
|
To enable the CAS OmniAuth provider you must register your application with your
|
||||||
CAS instance. This requires the service URL GitLab supplies to CAS. It should be
|
CAS instance. This requires the service URL GitLab supplies to CAS. It should be
|
||||||
|
|
|
@ -20,7 +20,6 @@ GitLab can be configured to authenticate access requests with the following auth
|
||||||
|
|
||||||
- Enable the [Auth0 OmniAuth](auth0.md) provider.
|
- Enable the [Auth0 OmniAuth](auth0.md) provider.
|
||||||
- Enable sign in with [Bitbucket](bitbucket.md) accounts.
|
- Enable sign in with [Bitbucket](bitbucket.md) accounts.
|
||||||
- Configure GitLab to sign in using [CAS](cas.md).
|
|
||||||
- Integrate with [Kerberos](kerberos.md).
|
- Integrate with [Kerberos](kerberos.md).
|
||||||
- Enable sign in via [LDAP](../administration/auth/ldap/index.md).
|
- Enable sign in via [LDAP](../administration/auth/ldap/index.md).
|
||||||
- Enable [OAuth2 provider](oauth_provider.md) application creation.
|
- Enable [OAuth2 provider](oauth_provider.md) application creation.
|
||||||
|
|
|
@ -18,7 +18,6 @@ GitLab supports the following OmniAuth providers.
|
||||||
| Provider documentation | OmniAuth provider name |
|
| Provider documentation | OmniAuth provider name |
|
||||||
|---------------------------------------------------------------------|----------------------------|
|
|---------------------------------------------------------------------|----------------------------|
|
||||||
| [AliCloud](alicloud.md) | `alicloud` |
|
| [AliCloud](alicloud.md) | `alicloud` |
|
||||||
| [Atlassian Crowd](../administration/auth/crowd.md) | `crowd` |
|
|
||||||
| [Atlassian](../administration/auth/atlassian.md) | `atlassian_oauth2` |
|
| [Atlassian](../administration/auth/atlassian.md) | `atlassian_oauth2` |
|
||||||
| [Auth0](auth0.md) | `auth0` |
|
| [Auth0](auth0.md) | `auth0` |
|
||||||
| [Authentiq](../administration/auth/authentiq.md) | `authentiq` |
|
| [Authentiq](../administration/auth/authentiq.md) | `authentiq` |
|
||||||
|
@ -26,7 +25,6 @@ GitLab supports the following OmniAuth providers.
|
||||||
| [Azure v2](azure.md) | `azure_activedirectory_v2` |
|
| [Azure v2](azure.md) | `azure_activedirectory_v2` |
|
||||||
| [Azure v1](azure.md) | `azure_oauth2` |
|
| [Azure v1](azure.md) | `azure_oauth2` |
|
||||||
| [Bitbucket Cloud](bitbucket.md) | `bitbucket` |
|
| [Bitbucket Cloud](bitbucket.md) | `bitbucket` |
|
||||||
| [CAS](cas.md) | `cas3` |
|
|
||||||
| [DingTalk](ding_talk.md) | `dingtalk` |
|
| [DingTalk](ding_talk.md) | `dingtalk` |
|
||||||
| [Facebook](facebook.md) | `facebook` |
|
| [Facebook](facebook.md) | `facebook` |
|
||||||
| [Generic OAuth 2.0](oauth2_generic.md) | `oauth2_generic` |
|
| [Generic OAuth 2.0](oauth2_generic.md) | `oauth2_generic` |
|
||||||
|
@ -49,7 +47,7 @@ Setting | Description | Default value
|
||||||
---------------------------|-------------|--------------
|
---------------------------|-------------|--------------
|
||||||
`allow_single_sign_on` | Enables you to list the providers that automatically create a GitLab account. The provider names are available in the **OmniAuth provider name** column in the [supported providers table](#supported-providers). | The default is `false`. If `false`, users must be created manually, or they can't sign in using OmniAuth.
|
`allow_single_sign_on` | Enables you to list the providers that automatically create a GitLab account. The provider names are available in the **OmniAuth provider name** column in the [supported providers table](#supported-providers). | The default is `false`. If `false`, users must be created manually, or they can't sign in using OmniAuth.
|
||||||
`auto_link_ldap_user` | If enabled, creates an LDAP identity in GitLab for users that are created through an OmniAuth provider. You can enable this setting if you have the [LDAP (ActiveDirectory)](../administration/auth/ldap/index.md) integration enabled. Requires the `uid` of the user to be the same in both LDAP and the OmniAuth provider. | The default is `false`.
|
`auto_link_ldap_user` | If enabled, creates an LDAP identity in GitLab for users that are created through an OmniAuth provider. You can enable this setting if you have the [LDAP (ActiveDirectory)](../administration/auth/ldap/index.md) integration enabled. Requires the `uid` of the user to be the same in both LDAP and the OmniAuth provider. | The default is `false`.
|
||||||
`block_auto_created_users` | If enabled, blocks users that are automatically created from signing in until they are approved by an administrator. | The default is `true`. If you set the value to `false`, make sure you only define providers for `allow_single_sign_on` that you can control, like SAML, Crowd, or Google. Otherwise, any user on the internet can sign in to GitLab without an administrator's approval.
|
`block_auto_created_users` | If enabled, blocks users that are automatically created from signing in until they are approved by an administrator. | The default is `true`. If you set the value to `false`, make sure you only define providers for `allow_single_sign_on` that you can control, like SAML or Google. Otherwise, any user on the internet can sign in to GitLab without an administrator's approval.
|
||||||
|
|
||||||
To change these settings:
|
To change these settings:
|
||||||
|
|
||||||
|
@ -134,7 +132,7 @@ variable in `args` for a provider, you can select another claim to use for the G
|
||||||
args: { gitlab_username_claim: 'sub' } # For users signing in with the provider you configure, the GitLab username will be set to the "sub" received from the provider
|
args: { gitlab_username_claim: 'sub' } # For users signing in with the provider you configure, the GitLab username will be set to the "sub" received from the provider
|
||||||
},
|
},
|
||||||
|
|
||||||
# Here are examples using GitHub and Crowd
|
# Here are examples using GitHub and Kerberos
|
||||||
|
|
||||||
gitlab_rails['omniauth_providers'] = {
|
gitlab_rails['omniauth_providers'] = {
|
||||||
name: "github"
|
name: "github"
|
||||||
|
@ -142,9 +140,9 @@ variable in `args` for a provider, you can select another claim to use for the G
|
||||||
args: { gitlab_username_claim: 'name' } # For users signing in with GitHub, the GitLab username will be set to the "name" received from GitHub
|
args: { gitlab_username_claim: 'name' } # For users signing in with GitHub, the GitLab username will be set to the "name" received from GitHub
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "crowd"
|
name: "kerberos"
|
||||||
...
|
...
|
||||||
args: { gitlab_username_claim: 'uid' } # For users signing in with Crowd, the GitLab username will be set to the "uid" received from Crowd
|
args: { gitlab_username_claim: 'uid' } # For users signing in with Kerberos, the GitLab username will be set to the "uid" received from Kerberos
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
@ -160,7 +158,7 @@ variable in `args` for a provider, you can select another claim to use for the G
|
||||||
...
|
...
|
||||||
args: { gitlab_username_claim: 'name' }
|
args: { gitlab_username_claim: 'name' }
|
||||||
}
|
}
|
||||||
- { name: 'crowd',
|
- { name: 'kerberos',
|
||||||
...
|
...
|
||||||
args: { gitlab_username_claim: 'uid' }
|
args: { gitlab_username_claim: 'uid' }
|
||||||
}
|
}
|
||||||
|
@ -442,5 +440,4 @@ then override the icon in one of two ways:
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
Most supported OmniAuth providers don't support Git over HTTP password authentication.
|
Most supported OmniAuth providers don't support Git over HTTP password authentication.
|
||||||
The only exception is [Atlassian Crowd](../administration/auth/crowd.md) (since GitLab [13.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46935)).
|
|
||||||
As a workaround, you can authenticate using a [personal access token](../user/profile/personal_access_tokens.md).
|
As a workaround, you can authenticate using a [personal access token](../user/profile/personal_access_tokens.md).
|
||||||
|
|
|
@ -47,48 +47,50 @@ sole discretion of GitLab Inc.
|
||||||
|
|
||||||
## Announced in 15.3
|
## Announced in 15.3
|
||||||
|
|
||||||
|
<div class="deprecation removal-160 breaking-change">
|
||||||
|
|
||||||
|
### Atlassian Crowd OmniAuth provider
|
||||||
|
|
||||||
|
Planned removal: GitLab <span class="removal-milestone">16.0</span> (2023-05-22)
|
||||||
|
|
||||||
|
WARNING:
|
||||||
|
This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
|
||||||
|
Review the details carefully before upgrading.
|
||||||
|
|
||||||
|
The `omniauth_crowd` gem that provides GitLab with the Atlassian Crowd OmniAuth provider will be removed in our
|
||||||
|
next major release, GitLab 16.0. This gem sees very little use and its
|
||||||
|
[lack of compatibility](https://github.com/robdimarco/omniauth_crowd/issues/37) with OmniAuth 2.0 is
|
||||||
|
[blocking our upgrade](https://gitlab.com/gitlab-org/gitlab/-/issues/30073).
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="deprecation removal-160 breaking-change">
|
||||||
|
|
||||||
|
### CAS OmniAuth provider
|
||||||
|
|
||||||
|
Planned removal: GitLab <span class="removal-milestone">16.0</span> (2023-05-22)
|
||||||
|
|
||||||
|
WARNING:
|
||||||
|
This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
|
||||||
|
Review the details carefully before upgrading.
|
||||||
|
|
||||||
|
The `omniauth-cas3` gem that provides GitLab with the CAS OmniAuth provider will be removed in our next major
|
||||||
|
release, GitLab 16.0. This gem sees very little use and its lack of upstream maintenance is preventing GitLab's
|
||||||
|
[upgrade to OmniAuth 2.0](https://gitlab.com/gitlab-org/gitlab/-/issues/30073).
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="deprecation removal-160">
|
<div class="deprecation removal-160">
|
||||||
|
|
||||||
### Maximum number of active pipelines per project limit (`ci_active_pipelines`)
|
### Maximum number of active pipelines per project limit (`ci_active_pipelines`)
|
||||||
|
|
||||||
Planned removal: GitLab <span class="removal-milestone">16.0</span> (2023-05-22)
|
Planned removal: GitLab <span class="removal-milestone">16.0</span> (2023-05-22)
|
||||||
|
|
||||||
The [**Maximum number of active pipelines per project** limit](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#set-cicd-limits) was never enabled by default and will be removed in GitLab 16.0. This limit can also be configured in the rails console under [`ci_active_pipelines`](https://docs.gitlab.com/ee/administration/instance_limits.html#number-of-pipelines-running-concurrently). Instead, use the other recommended rate limits that offer similar protection:
|
The [**Maximum number of active pipelines per project** limit](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#set-cicd-limits) was never enabled by default and will be removed in GitLab 16.0. This limit can also be configured in the Rails console under [`ci_active_pipelines`](https://docs.gitlab.com/ee/administration/instance_limits.html#number-of-pipelines-running-concurrently). Instead, use the other recommended rate limits that offer similar protection:
|
||||||
|
|
||||||
- [**Pipelines rate limits**](https://docs.gitlab.com/ee/user/admin_area/settings/rate_limit_on_pipelines_creation.html).
|
- [**Pipelines rate limits**](https://docs.gitlab.com/ee/user/admin_area/settings/rate_limit_on_pipelines_creation.html).
|
||||||
- [**Total number of jobs in currently active pipelines**](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#set-cicd-limits).
|
- [**Total number of jobs in currently active pipelines**](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#set-cicd-limits).
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="deprecation removal-160 breaking-change">
|
|
||||||
|
|
||||||
### `omniauth-cas3` gem
|
|
||||||
|
|
||||||
Planned removal: GitLab <span class="removal-milestone">16.0</span> (2023-05-22)
|
|
||||||
|
|
||||||
WARNING:
|
|
||||||
This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
|
|
||||||
Review the details carefully before upgrading.
|
|
||||||
|
|
||||||
The `omniauth-cas3` gem will be removed in our next major release, GitLab 16.0. This gem sees very little use and
|
|
||||||
its lack of upstream maintence is preventing GitLab's [upgrade to OmniAuth 2.0](https://gitlab.com/gitlab-org/gitlab/-/issues/30073).
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="deprecation removal-160 breaking-change">
|
|
||||||
|
|
||||||
### `omniauth_crowd` gem
|
|
||||||
|
|
||||||
Planned removal: GitLab <span class="removal-milestone">16.0</span> (2023-05-22)
|
|
||||||
|
|
||||||
WARNING:
|
|
||||||
This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
|
|
||||||
Review the details carefully before upgrading.
|
|
||||||
|
|
||||||
The `omniauth_crowd` gem will be removed in our next major release, GitLab 16.0. This gem sees very little use and
|
|
||||||
its [lack of compatability](https://github.com/robdimarco/omniauth_crowd/issues/37) with OmniAuth 2.0 is blocking
|
|
||||||
our upgrade.
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ module Gitlab
|
||||||
# class must be present in the Gitlab::BackgroundMigration module, and the batch class (if specified) must be
|
# class must be present in the Gitlab::BackgroundMigration module, and the batch class (if specified) must be
|
||||||
# present in the Gitlab::BackgroundMigration::BatchingStrategies module.
|
# present in the Gitlab::BackgroundMigration::BatchingStrategies module.
|
||||||
#
|
#
|
||||||
# If migration with same job_class_name, table_name, column_name, and job_aruments already exists, this helper
|
# If migration with same job_class_name, table_name, column_name, and job_arguments already exists, this helper
|
||||||
# will log an warning and not create a new one.
|
# will log an warning and not create a new one.
|
||||||
#
|
#
|
||||||
# job_class_name - The background migration job class as a string
|
# job_class_name - The background migration job class as a string
|
||||||
|
|
|
@ -12,12 +12,13 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
attr_reader :project, :current_user, :lock_file
|
attr_reader :project, :current_user, :lock_file, :logger
|
||||||
|
|
||||||
public
|
public
|
||||||
|
|
||||||
def initialize(attributes = {})
|
def initialize(attributes = {})
|
||||||
@options = attributes
|
@options = attributes
|
||||||
|
@logger = Gitlab::Export::Logger.build
|
||||||
end
|
end
|
||||||
|
|
||||||
def method_missing(method, *args)
|
def method_missing(method, *args)
|
||||||
|
@ -43,6 +44,10 @@ module Gitlab
|
||||||
|
|
||||||
true
|
true
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
|
payload = { message: "After export strategy failed" }
|
||||||
|
Gitlab::ExceptionLogFormatter.format!(e, payload)
|
||||||
|
log_error(payload)
|
||||||
|
|
||||||
project.import_export_shared.error(e)
|
project.import_export_shared.error(e)
|
||||||
false
|
false
|
||||||
ensure
|
ensure
|
||||||
|
@ -108,6 +113,18 @@ module Gitlab
|
||||||
def log_validation_errors
|
def log_validation_errors
|
||||||
errors.full_messages.each { |msg| project.import_export_shared.add_error_message(msg) }
|
errors.full_messages.each { |msg| project.import_export_shared.add_error_message(msg) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log_info(params)
|
||||||
|
logger.info(log_default_params.merge(params))
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_error(params)
|
||||||
|
logger.error(log_default_params.merge(params))
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_default_params
|
||||||
|
{ project_name: project.name, project_id: project.id }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ module Gitlab
|
||||||
module AfterExportStrategies
|
module AfterExportStrategies
|
||||||
class MoveFileStrategy < BaseAfterExportStrategy
|
class MoveFileStrategy < BaseAfterExportStrategy
|
||||||
def initialize(archive_path:)
|
def initialize(archive_path:)
|
||||||
|
super
|
||||||
@archive_path = archive_path
|
@archive_path = archive_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,17 @@ module Gitlab
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def strategy_execute
|
def strategy_execute
|
||||||
handle_response_error(send_file)
|
log_info(message: "Started uploading project", export_size: export_size)
|
||||||
|
|
||||||
|
upload_duration = Benchmark.realtime do
|
||||||
|
if Feature.enabled?(:import_export_web_upload_stream) && !project.export_file.file_storage?
|
||||||
|
upload_project_as_remote_stream
|
||||||
|
else
|
||||||
|
handle_response_error(send_file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log_info(message: "Finished uploading project", export_size: export_size, upload_duration: upload_duration)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_response_error(response)
|
def handle_response_error(response)
|
||||||
|
@ -44,8 +54,22 @@ module Gitlab
|
||||||
export_file.close if export_file
|
export_file.close if export_file
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def upload_project_as_remote_stream
|
||||||
|
Gitlab::ImportExport::RemoteStreamUpload.new(
|
||||||
|
download_url: project.export_file.url,
|
||||||
|
upload_url: url,
|
||||||
|
options: {
|
||||||
|
upload_method: http_method.downcase.to_sym,
|
||||||
|
upload_content_type: 'application/gzip'
|
||||||
|
}).execute
|
||||||
|
rescue Gitlab::ImportExport::RemoteStreamUpload::StreamError => e
|
||||||
|
log_error(message: e.message, response_body: e.response_body.truncate(3000))
|
||||||
|
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
|
||||||
def export_file
|
def export_file
|
||||||
project.export_file.open
|
@export_file ||= project.export_file.open
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_file_options
|
def send_file_options
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# This class downloads a file from one URL and uploads it to another URL
|
||||||
|
# without having to save the file on the disk and loading the whole file in
|
||||||
|
# memory. The download and upload are performed in chunks size of
|
||||||
|
# `buffer_size`. A chunk is downloaded, then uploaded, then a next chunk is
|
||||||
|
# downloaded and uploaded. This repeats until all the file is processed.
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module ImportExport
|
||||||
|
class RemoteStreamUpload
|
||||||
|
def initialize(download_url:, upload_url:, options: {})
|
||||||
|
@download_url = download_url
|
||||||
|
@upload_url = upload_url
|
||||||
|
@upload_method = options[:upload_method] || :post
|
||||||
|
@upload_content_type = options[:upload_content_type] || 'application/gzip'
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute
|
||||||
|
receive_data(download_url) do |response, chunks|
|
||||||
|
send_data(upload_url, response.content_length, chunks) do |response|
|
||||||
|
if response.code != '200'
|
||||||
|
raise StreamError.new("Invalid response code while uploading file. Code: #{response.code}", response.body)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
class StreamError < StandardError
|
||||||
|
attr_reader :response_body
|
||||||
|
|
||||||
|
def initialize(message, response_body = '')
|
||||||
|
super(message)
|
||||||
|
@response_body = response_body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
class ChunkStream
|
||||||
|
DEFAULT_BUFFER_SIZE = 128.kilobytes
|
||||||
|
|
||||||
|
def initialize(chunks)
|
||||||
|
@chunks = chunks
|
||||||
|
@last_chunk = nil
|
||||||
|
@end_of_chunks = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def read(n1 = nil, n2 = nil)
|
||||||
|
ensure_chunk&.read(n1, n2)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ensure_chunk
|
||||||
|
return @last_chunk if @last_chunk && !@last_chunk.eof?
|
||||||
|
return if @end_of_chunks
|
||||||
|
|
||||||
|
@last_chunk = read_next_chunk
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_next_chunk
|
||||||
|
next_chunk = StringIO.new
|
||||||
|
|
||||||
|
begin
|
||||||
|
next_chunk.write(@chunks.next) until next_chunk.size > DEFAULT_BUFFER_SIZE
|
||||||
|
rescue StopIteration
|
||||||
|
@end_of_chunks = true
|
||||||
|
end
|
||||||
|
|
||||||
|
next_chunk.rewind
|
||||||
|
|
||||||
|
next_chunk
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_reader :download_url, :upload_url, :upload_method, :upload_content_type, :logger
|
||||||
|
|
||||||
|
def receive_data(uri)
|
||||||
|
http = Gitlab::HTTPConnectionAdapter.new(URI(uri), {}).connection
|
||||||
|
|
||||||
|
http.start do
|
||||||
|
request = Net::HTTP::Get.new(uri)
|
||||||
|
http.request(request) do |response|
|
||||||
|
if response.code == '200'
|
||||||
|
yield(response, response.enum_for(:read_body))
|
||||||
|
else
|
||||||
|
raise StreamError.new(
|
||||||
|
"Invalid response code while downloading file. Code: #{response.code}",
|
||||||
|
response.body
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_data(uri, content_length, chunks)
|
||||||
|
http = Gitlab::HTTPConnectionAdapter.new(URI(uri), {}).connection
|
||||||
|
|
||||||
|
http.start do
|
||||||
|
request = upload_request_class(upload_method).new(uri)
|
||||||
|
request.body_stream = ChunkStream.new(chunks)
|
||||||
|
request.content_length = content_length
|
||||||
|
request.content_type = upload_content_type
|
||||||
|
|
||||||
|
http.request(request) do |response|
|
||||||
|
yield(response)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def upload_request_class(upload_method)
|
||||||
|
return Net::HTTP::Put if upload_method == :put
|
||||||
|
|
||||||
|
Net::HTTP::Post
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -11627,7 +11627,7 @@ msgstr ""
|
||||||
msgid "DORA4Metrics|Change failure rate"
|
msgid "DORA4Metrics|Change failure rate"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DORA4Metrics|Change failure rate (%%)"
|
msgid "DORA4Metrics|Change failure rate (percentage)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DORA4Metrics|Date"
|
msgid "DORA4Metrics|Date"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { GlButton, GlIcon } from '@gitlab/ui';
|
import { nextTick } from 'vue';
|
||||||
import { shallowMount, mount } from '@vue/test-utils';
|
import { GlIcon } from '@gitlab/ui';
|
||||||
|
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import {
|
import {
|
||||||
issuable1,
|
issuable1,
|
||||||
issuable2,
|
issuable2,
|
||||||
|
@ -17,7 +18,9 @@ import {
|
||||||
describe('RelatedIssuesBlock', () => {
|
describe('RelatedIssuesBlock', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
const findIssueCountBadgeAddButton = () => wrapper.find(GlButton);
|
const findIssueCountBadgeAddButton = () => wrapper.findByTestId('add-button');
|
||||||
|
const findToggleButton = () => wrapper.findByTestId('toggle-links');
|
||||||
|
const findRelatedIssuesBody = () => wrapper.findByTestId('related-issues-body');
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
if (wrapper) {
|
if (wrapper) {
|
||||||
|
@ -28,7 +31,7 @@ describe('RelatedIssuesBlock', () => {
|
||||||
|
|
||||||
describe('with defaults', () => {
|
describe('with defaults', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = mount(RelatedIssuesBlock, {
|
wrapper = mountExtended(RelatedIssuesBlock, {
|
||||||
propsData: {
|
propsData: {
|
||||||
pathIdSeparator: PathIdSeparator.Issue,
|
pathIdSeparator: PathIdSeparator.Issue,
|
||||||
issuableType: issuableTypesMap.ISSUE,
|
issuableType: issuableTypesMap.ISSUE,
|
||||||
|
@ -43,7 +46,7 @@ describe('RelatedIssuesBlock', () => {
|
||||||
`(
|
`(
|
||||||
'displays "$titleText" in the header, "$helpLinkText" aria-label for help link, and "$addButtonText" aria-label for add button when issuableType is set to "$issuableType"',
|
'displays "$titleText" in the header, "$helpLinkText" aria-label for help link, and "$addButtonText" aria-label for add button when issuableType is set to "$issuableType"',
|
||||||
({ issuableType, pathIdSeparator, titleText, helpLinkText, addButtonText }) => {
|
({ issuableType, pathIdSeparator, titleText, helpLinkText, addButtonText }) => {
|
||||||
wrapper = mount(RelatedIssuesBlock, {
|
wrapper = mountExtended(RelatedIssuesBlock, {
|
||||||
propsData: {
|
propsData: {
|
||||||
pathIdSeparator,
|
pathIdSeparator,
|
||||||
issuableType,
|
issuableType,
|
||||||
|
@ -73,7 +76,7 @@ describe('RelatedIssuesBlock', () => {
|
||||||
it('displays header text slot data', () => {
|
it('displays header text slot data', () => {
|
||||||
const headerText = '<div>custom header text</div>';
|
const headerText = '<div>custom header text</div>';
|
||||||
|
|
||||||
wrapper = shallowMount(RelatedIssuesBlock, {
|
wrapper = shallowMountExtended(RelatedIssuesBlock, {
|
||||||
propsData: {
|
propsData: {
|
||||||
pathIdSeparator: PathIdSeparator.Issue,
|
pathIdSeparator: PathIdSeparator.Issue,
|
||||||
issuableType: 'issue',
|
issuableType: 'issue',
|
||||||
|
@ -89,7 +92,7 @@ describe('RelatedIssuesBlock', () => {
|
||||||
it('displays header actions slot data', () => {
|
it('displays header actions slot data', () => {
|
||||||
const headerActions = '<button data-testid="custom-button">custom button</button>';
|
const headerActions = '<button data-testid="custom-button">custom button</button>';
|
||||||
|
|
||||||
wrapper = shallowMount(RelatedIssuesBlock, {
|
wrapper = shallowMountExtended(RelatedIssuesBlock, {
|
||||||
propsData: {
|
propsData: {
|
||||||
pathIdSeparator: PathIdSeparator.Issue,
|
pathIdSeparator: PathIdSeparator.Issue,
|
||||||
issuableType: 'issue',
|
issuableType: 'issue',
|
||||||
|
@ -103,7 +106,7 @@ describe('RelatedIssuesBlock', () => {
|
||||||
|
|
||||||
describe('with isFetching=true', () => {
|
describe('with isFetching=true', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = mount(RelatedIssuesBlock, {
|
wrapper = mountExtended(RelatedIssuesBlock, {
|
||||||
propsData: {
|
propsData: {
|
||||||
pathIdSeparator: PathIdSeparator.Issue,
|
pathIdSeparator: PathIdSeparator.Issue,
|
||||||
isFetching: true,
|
isFetching: true,
|
||||||
|
@ -119,7 +122,7 @@ describe('RelatedIssuesBlock', () => {
|
||||||
|
|
||||||
describe('with canAddRelatedIssues=true', () => {
|
describe('with canAddRelatedIssues=true', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = mount(RelatedIssuesBlock, {
|
wrapper = mountExtended(RelatedIssuesBlock, {
|
||||||
propsData: {
|
propsData: {
|
||||||
pathIdSeparator: PathIdSeparator.Issue,
|
pathIdSeparator: PathIdSeparator.Issue,
|
||||||
canAdmin: true,
|
canAdmin: true,
|
||||||
|
@ -135,7 +138,7 @@ describe('RelatedIssuesBlock', () => {
|
||||||
|
|
||||||
describe('with isFormVisible=true', () => {
|
describe('with isFormVisible=true', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = mount(RelatedIssuesBlock, {
|
wrapper = mountExtended(RelatedIssuesBlock, {
|
||||||
propsData: {
|
propsData: {
|
||||||
pathIdSeparator: PathIdSeparator.Issue,
|
pathIdSeparator: PathIdSeparator.Issue,
|
||||||
isFormVisible: true,
|
isFormVisible: true,
|
||||||
|
@ -159,7 +162,7 @@ describe('RelatedIssuesBlock', () => {
|
||||||
const categorizedHeadings = () => wrapper.findAll('h4');
|
const categorizedHeadings = () => wrapper.findAll('h4');
|
||||||
const headingTextAt = (index) => categorizedHeadings().at(index).text();
|
const headingTextAt = (index) => categorizedHeadings().at(index).text();
|
||||||
const mountComponent = (showCategorizedIssues) => {
|
const mountComponent = (showCategorizedIssues) => {
|
||||||
wrapper = mount(RelatedIssuesBlock, {
|
wrapper = mountExtended(RelatedIssuesBlock, {
|
||||||
propsData: {
|
propsData: {
|
||||||
pathIdSeparator: PathIdSeparator.Issue,
|
pathIdSeparator: PathIdSeparator.Issue,
|
||||||
relatedIssues: [issuable1, issuable2, issuable3],
|
relatedIssues: [issuable1, issuable2, issuable3],
|
||||||
|
@ -217,7 +220,7 @@ describe('RelatedIssuesBlock', () => {
|
||||||
},
|
},
|
||||||
].forEach(({ issuableType, icon }) => {
|
].forEach(({ issuableType, icon }) => {
|
||||||
it(`issuableType=${issuableType} is passed`, () => {
|
it(`issuableType=${issuableType} is passed`, () => {
|
||||||
wrapper = shallowMount(RelatedIssuesBlock, {
|
wrapper = shallowMountExtended(RelatedIssuesBlock, {
|
||||||
propsData: {
|
propsData: {
|
||||||
pathIdSeparator: PathIdSeparator.Issue,
|
pathIdSeparator: PathIdSeparator.Issue,
|
||||||
issuableType,
|
issuableType,
|
||||||
|
@ -230,4 +233,28 @@ describe('RelatedIssuesBlock', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('toggle', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = shallowMountExtended(RelatedIssuesBlock, {
|
||||||
|
propsData: {
|
||||||
|
pathIdSeparator: PathIdSeparator.Issue,
|
||||||
|
issuableType: issuableTypesMap.ISSUE,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is expanded by default', () => {
|
||||||
|
expect(findToggleButton().props('icon')).toBe('chevron-lg-up');
|
||||||
|
expect(findRelatedIssuesBody().exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('expands on click toggle button', async () => {
|
||||||
|
findToggleButton().vm.$emit('click');
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(findToggleButton().props('icon')).toBe('chevron-lg-down');
|
||||||
|
expect(findRelatedIssuesBody().exists()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,12 +9,21 @@ RSpec.describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
|
||||||
allow_next_instance_of(ProjectExportWorker) do |job|
|
allow_next_instance_of(ProjectExportWorker) do |job|
|
||||||
allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
|
allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
stub_feature_flags(import_export_web_upload_stream: false)
|
||||||
|
stub_uploads_object_storage(FileUploader, enabled: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:example_url) { 'http://www.example.com' }
|
let(:example_url) { 'http://www.example.com' }
|
||||||
let(:strategy) { subject.new(url: example_url, http_method: 'post') }
|
let(:strategy) { subject.new(url: example_url, http_method: 'post') }
|
||||||
let!(:project) { create(:project, :with_export) }
|
let(:user) { build(:user) }
|
||||||
let!(:user) { build(:user) }
|
let(:project) { import_export_upload.project }
|
||||||
|
let(:import_export_upload) do
|
||||||
|
create(
|
||||||
|
:import_export_upload,
|
||||||
|
export_file: fixture_file_upload('spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
subject { described_class }
|
subject { described_class }
|
||||||
|
|
||||||
|
@ -36,21 +45,43 @@ RSpec.describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
|
||||||
describe '#execute' do
|
describe '#execute' do
|
||||||
context 'when upload succeeds' do
|
context 'when upload succeeds' do
|
||||||
before do
|
before do
|
||||||
allow(strategy).to receive(:send_file)
|
stub_full_request(example_url, method: :post).to_return(status: 200)
|
||||||
allow(strategy).to receive(:handle_response_error)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not remove the exported project file after the upload' do
|
it 'does not remove the exported project file after the upload', :aggregate_failures do
|
||||||
expect(project).not_to receive(:remove_exports)
|
expect(project).not_to receive(:remove_exports)
|
||||||
|
|
||||||
strategy.execute(user, project)
|
expect { strategy.execute(user, project) }.not_to change(project, :export_status)
|
||||||
end
|
|
||||||
|
|
||||||
it 'has finished export status' do
|
|
||||||
strategy.execute(user, project)
|
|
||||||
|
|
||||||
expect(project.export_status).to eq(:finished)
|
expect(project.export_status).to eq(:finished)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'logs when upload starts and finishes' do
|
||||||
|
export_size = import_export_upload.export_file.size
|
||||||
|
|
||||||
|
expect_next_instance_of(Gitlab::Export::Logger) do |logger|
|
||||||
|
expect(logger).to receive(:info).ordered.with(
|
||||||
|
{
|
||||||
|
message: "Started uploading project",
|
||||||
|
project_id: project.id,
|
||||||
|
project_name: project.name,
|
||||||
|
export_size: export_size
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(logger).to receive(:info).ordered.with(
|
||||||
|
{
|
||||||
|
message: "Finished uploading project",
|
||||||
|
project_id: project.id,
|
||||||
|
project_name: project.name,
|
||||||
|
export_size: export_size,
|
||||||
|
upload_duration: anything
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
strategy.execute(user, project)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when upload fails' do
|
context 'when upload fails' do
|
||||||
|
@ -64,5 +95,124 @@ RSpec.describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
|
||||||
expect(errors.first).to eq "Error uploading the project. Code 404: Page not found"
|
expect(errors.first).to eq "Error uploading the project. Code 404: Page not found"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when object store is disabled' do
|
||||||
|
it 'reads file from disk and uploads to external url' do
|
||||||
|
stub_request(:post, example_url).to_return(status: 200)
|
||||||
|
expect(Gitlab::ImportExport::RemoteStreamUpload).not_to receive(:new)
|
||||||
|
expect(Gitlab::HttpIO).not_to receive(:new)
|
||||||
|
|
||||||
|
strategy.execute(user, project)
|
||||||
|
|
||||||
|
expect(a_request(:post, example_url)).to have_been_made
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when object store is enabled' do
|
||||||
|
before do
|
||||||
|
object_store_url = 'http://object-storage/project.tar.gz'
|
||||||
|
stub_uploads_object_storage(FileUploader)
|
||||||
|
stub_request(:get, object_store_url)
|
||||||
|
stub_request(:post, example_url)
|
||||||
|
allow(import_export_upload.export_file).to receive(:url).and_return(object_store_url)
|
||||||
|
allow(import_export_upload.export_file).to receive(:file_storage?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'reads file using Gitlab::HttpIO and uploads to external url' do
|
||||||
|
expect_next_instance_of(Gitlab::HttpIO) do |http_io|
|
||||||
|
expect(http_io).to receive(:read).and_call_original
|
||||||
|
end
|
||||||
|
expect(Gitlab::ImportExport::RemoteStreamUpload).not_to receive(:new)
|
||||||
|
|
||||||
|
strategy.execute(user, project)
|
||||||
|
|
||||||
|
expect(a_request(:post, example_url)).to have_been_made
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when `import_export_web_upload_stream` feature is enabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(import_export_web_upload_stream: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when remote object store is disabled' do
|
||||||
|
it 'reads file from disk and uploads to external url' do
|
||||||
|
stub_request(:post, example_url).to_return(status: 200)
|
||||||
|
expect(Gitlab::ImportExport::RemoteStreamUpload).not_to receive(:new)
|
||||||
|
expect(Gitlab::HttpIO).not_to receive(:new)
|
||||||
|
|
||||||
|
strategy.execute(user, project)
|
||||||
|
|
||||||
|
expect(a_request(:post, example_url)).to have_been_made
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when object store is enabled' do
|
||||||
|
let(:object_store_url) { 'http://object-storage/project.tar.gz' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_uploads_object_storage(FileUploader)
|
||||||
|
|
||||||
|
allow(import_export_upload.export_file).to receive(:url).and_return(object_store_url)
|
||||||
|
allow(import_export_upload.export_file).to receive(:file_storage?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'uploads file as a remote stream' do
|
||||||
|
arguments = {
|
||||||
|
download_url: object_store_url,
|
||||||
|
upload_url: example_url,
|
||||||
|
options: {
|
||||||
|
upload_method: :post,
|
||||||
|
upload_content_type: 'application/gzip'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_next_instance_of(Gitlab::ImportExport::RemoteStreamUpload, arguments) do |remote_stream_upload|
|
||||||
|
expect(remote_stream_upload).to receive(:execute)
|
||||||
|
end
|
||||||
|
expect(Gitlab::HttpIO).not_to receive(:new)
|
||||||
|
|
||||||
|
strategy.execute(user, project)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when upload as remote stream raises an exception' do
|
||||||
|
before do
|
||||||
|
allow_next_instance_of(Gitlab::ImportExport::RemoteStreamUpload) do |remote_stream_upload|
|
||||||
|
allow(remote_stream_upload).to receive(:execute).and_raise(
|
||||||
|
Gitlab::ImportExport::RemoteStreamUpload::StreamError.new('Exception error message', 'Response body')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'logs the exception and stores the error message' do
|
||||||
|
expect_next_instance_of(Gitlab::Export::Logger) do |logger|
|
||||||
|
expect(logger).to receive(:error).ordered.with(
|
||||||
|
{
|
||||||
|
project_id: project.id,
|
||||||
|
project_name: project.name,
|
||||||
|
message: 'Exception error message',
|
||||||
|
response_body: 'Response body'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(logger).to receive(:error).ordered.with(
|
||||||
|
{
|
||||||
|
project_id: project.id,
|
||||||
|
project_name: project.name,
|
||||||
|
message: 'After export strategy failed',
|
||||||
|
'exception.class' => 'Gitlab::ImportExport::RemoteStreamUpload::StreamError',
|
||||||
|
'exception.message' => 'Exception error message',
|
||||||
|
'exception.backtrace' => anything
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
strategy.execute(user, project)
|
||||||
|
|
||||||
|
expect(project.import_export_shared.errors.first).to eq('Exception error message')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::ImportExport::RemoteStreamUpload do
|
||||||
|
include StubRequests
|
||||||
|
|
||||||
|
subject do
|
||||||
|
described_class.new(
|
||||||
|
download_url: download_url,
|
||||||
|
upload_url: upload_url,
|
||||||
|
options: {
|
||||||
|
upload_method: upload_method,
|
||||||
|
upload_content_type: upload_content_type
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:download_url) { 'http://object-storage/file.txt' }
|
||||||
|
let(:upload_url) { 'http://example.com/file.txt' }
|
||||||
|
let(:upload_method) { :post }
|
||||||
|
let(:upload_content_type) { 'text/plain' }
|
||||||
|
|
||||||
|
describe '#execute' do
|
||||||
|
context 'when download request and upload request return 200' do
|
||||||
|
it 'uploads the downloaded content' do
|
||||||
|
stub_request(:get, download_url).to_return(status: 200, body: 'ABC', headers: { 'Content-Length' => 3 })
|
||||||
|
stub_request(:post, upload_url)
|
||||||
|
|
||||||
|
subject.execute
|
||||||
|
|
||||||
|
expect(
|
||||||
|
a_request(:post, upload_url).with(
|
||||||
|
body: 'ABC', headers: { 'Content-Length' => 3, 'Content-Type' => 'text/plain' }
|
||||||
|
)
|
||||||
|
).to have_been_made
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when upload method is put' do
|
||||||
|
let(:upload_method) { :put }
|
||||||
|
|
||||||
|
it 'uploads using the put method' do
|
||||||
|
stub_request(:get, download_url).to_return(status: 200, body: 'ABC', headers: { 'Content-Length' => 3 })
|
||||||
|
stub_request(:put, upload_url)
|
||||||
|
|
||||||
|
subject.execute
|
||||||
|
|
||||||
|
expect(
|
||||||
|
a_request(:put, upload_url).with(
|
||||||
|
body: 'ABC', headers: { 'Content-Length' => 3, 'Content-Type' => 'text/plain' }
|
||||||
|
)
|
||||||
|
).to have_been_made
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when download request does not return 200' do
|
||||||
|
it do
|
||||||
|
stub_request(:get, download_url).to_return(status: 404)
|
||||||
|
|
||||||
|
expect { subject.execute }.to raise_error(
|
||||||
|
Gitlab::ImportExport::RemoteStreamUpload::StreamError,
|
||||||
|
"Invalid response code while downloading file. Code: 404"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when upload request does not returns 200' do
|
||||||
|
it do
|
||||||
|
stub_request(:get, download_url).to_return(status: 200, body: 'ABC', headers: { 'Content-Length' => 3 })
|
||||||
|
stub_request(:post, upload_url).to_return(status: 403)
|
||||||
|
|
||||||
|
expect { subject.execute }.to raise_error(
|
||||||
|
Gitlab::ImportExport::RemoteStreamUpload::StreamError,
|
||||||
|
"Invalid response code while uploading file. Code: 403"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when download URL is a local address' do
|
||||||
|
let(:download_url) { 'http://127.0.0.1/file.txt' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, download_url)
|
||||||
|
stub_request(:post, upload_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises error' do
|
||||||
|
expect { subject.execute }.to raise_error(
|
||||||
|
Gitlab::HTTP::BlockedUrlError,
|
||||||
|
"URL 'http://127.0.0.1/file.txt' is blocked: Requests to localhost are not allowed"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when local requests are allowed' do
|
||||||
|
before do
|
||||||
|
stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises does not error' do
|
||||||
|
expect { subject.execute }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when download URL is a local network' do
|
||||||
|
let(:download_url) { 'http://172.16.0.0/file.txt' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, download_url)
|
||||||
|
stub_request(:post, upload_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises error' do
|
||||||
|
expect { subject.execute }.to raise_error(
|
||||||
|
Gitlab::HTTP::BlockedUrlError,
|
||||||
|
"URL 'http://172.16.0.0/file.txt' is blocked: Requests to the local network are not allowed"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when local network requests are allowed' do
|
||||||
|
before do
|
||||||
|
stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises does not error' do
|
||||||
|
expect { subject.execute }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when upload URL is a local address' do
|
||||||
|
let(:upload_url) { 'http://127.0.0.1/file.txt' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, download_url)
|
||||||
|
stub_request(:post, upload_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises error' do
|
||||||
|
stub_request(:get, download_url)
|
||||||
|
|
||||||
|
expect { subject.execute }.to raise_error(
|
||||||
|
Gitlab::HTTP::BlockedUrlError,
|
||||||
|
"URL 'http://127.0.0.1/file.txt' is blocked: Requests to localhost are not allowed"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when local requests are allowed' do
|
||||||
|
before do
|
||||||
|
stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises does not error' do
|
||||||
|
expect { subject.execute }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when upload URL it is a request to local network' do
|
||||||
|
let(:upload_url) { 'http://172.16.0.0/file.txt' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, download_url)
|
||||||
|
stub_request(:post, upload_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises error' do
|
||||||
|
expect { subject.execute }.to raise_error(
|
||||||
|
Gitlab::HTTP::BlockedUrlError,
|
||||||
|
"URL 'http://172.16.0.0/file.txt' is blocked: Requests to the local network are not allowed"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when local network requests are allowed' do
|
||||||
|
before do
|
||||||
|
stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises does not error' do
|
||||||
|
expect { subject.execute }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when upload URL resolves to a local address' do
|
||||||
|
let(:upload_url) { 'http://example.com/file.txt' }
|
||||||
|
|
||||||
|
it 'raises error' do
|
||||||
|
stub_request(:get, download_url)
|
||||||
|
stub_full_request(upload_url, ip_address: '127.0.0.1', method: upload_method)
|
||||||
|
|
||||||
|
expect { subject.execute }.to raise_error(
|
||||||
|
Gitlab::HTTP::BlockedUrlError,
|
||||||
|
"URL 'http://example.com/file.txt' is blocked: Requests to localhost are not allowed"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe Gitlab::ImportExport::RemoteStreamUpload::ChunkStream do
|
||||||
|
describe 'StringIO#copy_stream compatibility' do
|
||||||
|
it 'copies all chunks' do
|
||||||
|
chunks = %w[ABC EFD].to_enum
|
||||||
|
chunk_stream = described_class.new(chunks)
|
||||||
|
new_stream = StringIO.new
|
||||||
|
|
||||||
|
IO.copy_stream(chunk_stream, new_stream)
|
||||||
|
new_stream.rewind
|
||||||
|
|
||||||
|
expect(new_stream.read).to eq('ABCEFD')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with chunks smaller and bigger than buffer size' do
|
||||||
|
before do
|
||||||
|
stub_const('Gitlab::ImportExport::RemoteStreamUpload::ChunkStream::DEFAULT_BUFFER_SIZE', 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'copies all chunks' do
|
||||||
|
chunks = %w[A BC DEF GHIJ KLMNOPQ RSTUVWXYZ].to_enum
|
||||||
|
chunk_stream = described_class.new(chunks)
|
||||||
|
new_stream = StringIO.new
|
||||||
|
|
||||||
|
IO.copy_stream(chunk_stream, new_stream)
|
||||||
|
new_stream.rewind
|
||||||
|
|
||||||
|
expect(new_stream.read).to eq('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -273,14 +273,6 @@ RSpec.shared_examples 'namespace traversal scopes' do
|
||||||
|
|
||||||
include_examples '.self_and_descendants'
|
include_examples '.self_and_descendants'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with linear_scopes_superset feature flag disabled' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(linear_scopes_superset: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
include_examples '.self_and_descendants'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples '.self_and_descendant_ids' do
|
shared_examples '.self_and_descendant_ids' do
|
||||||
|
@ -324,14 +316,6 @@ RSpec.shared_examples 'namespace traversal scopes' do
|
||||||
|
|
||||||
include_examples '.self_and_descendant_ids'
|
include_examples '.self_and_descendant_ids'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with linear_scopes_superset feature flag disabled' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(linear_scopes_superset: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
include_examples '.self_and_descendant_ids'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples '.self_and_hierarchy' do
|
shared_examples '.self_and_hierarchy' do
|
||||||
|
|
Loading…
Reference in New Issue