Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-10-28 18:14:18 +00:00
parent 03cd4f8da4
commit b267f3a3ac
117 changed files with 1911 additions and 323 deletions

View file

@ -32,7 +32,6 @@ Rails/SaveBang:
- 'ee/spec/models/approval_project_rule_spec.rb'
- 'ee/spec/models/burndown_spec.rb'
- 'ee/spec/models/elasticsearch_indexed_namespace_spec.rb'
- 'ee/spec/models/epic_spec.rb'
- 'ee/spec/models/gitlab_subscription_spec.rb'
- 'ee/spec/models/issue_spec.rb'
- 'ee/spec/models/label_note_spec.rb'

View file

@ -2,6 +2,24 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 14.4.1 (2021-10-28)
### Security (13 changes)
- [Highlight usage of unicode bidi characters](gitlab-org/security/gitlab@cef762a270783780112c7bf318e353a39de1aa1e) ([merge request](gitlab-org/security/gitlab!1937))
- [Fix dompurify.js to prevent path traversal attacks](gitlab-org/security/gitlab@9a891cbe465a302f260f0f81fc490cacb9e8c70e) ([merge request](gitlab-org/security/gitlab!1929))
- [Refresh authorizations on transfer of groups having project shares](gitlab-org/security/gitlab@bdf8b6e90d0a1f719c0f389f29ea5dc41c22f119) ([merge request](gitlab-org/security/gitlab!1916))
- [Adding a '[redacted]' to mask private email addresses](gitlab-org/security/gitlab@324fe6286b266c3990676bc93b3f6ab03eea5f6b) ([merge request](gitlab-org/security/gitlab!1927))
- [Do not allow Applications API to create apps with blank scopes](gitlab-org/security/gitlab@4e2c4d2a88acf7167e1078e8a27679545ab90c9c) ([merge request](gitlab-org/security/gitlab!1922))
- [Don't allow author to resolve discussions when MR is locked via GraphQL](gitlab-org/security/gitlab@34ffcb55a70ad6db38292f79fe73c05fb2655738) ([merge request](gitlab-org/security/gitlab!1919))
- [Workhorse: Allow uploading only a single file](gitlab-org/security/gitlab@0aee710db4bbab84c78b9e38f459bfca606aaf80) ([merge request](gitlab-org/security/gitlab!1913))
- [Set PipelineSchedules to inactive](gitlab-org/security/gitlab@de405edc9de4519656675ed6825534aac6b738da) ([merge request](gitlab-org/security/gitlab!1911))
- [Do not display the root password by default](gitlab-org/security/gitlab@138a62f89ce6616d63e3cf18eeda291a380b9ebc) ([merge request](gitlab-org/security/gitlab!1909))
- [Group owners should see SCIM token only once](gitlab-org/security/gitlab@43d19f580543d0203b1d841f921536474ca4be38) ([merge request](gitlab-org/security/gitlab!1906)) **GitLab Enterprise Edition**
- [Respect visibility level settings when updating project via API](gitlab-org/security/gitlab@f96258f3622cf72b46158f22c4660ff60a2c25ae) ([merge request](gitlab-org/security/gitlab!1903))
- [Avoid decoding the whole tiff image on isTIFF check](gitlab-org/security/gitlab@b93683df51ce85f909d5072ec2a0e7756d64038e) ([merge request](gitlab-org/security/gitlab!1899))
- [Remove external_webhook_token from exported project](gitlab-org/security/gitlab@874aa74a23fc3c44f390500bc8379c30ebc51452) ([merge request](gitlab-org/security/gitlab!1872))
## 14.4.0 (2021-10-21)
### Added (79 changes)
@ -391,6 +409,24 @@ entry.
- [Cleanup bigint conversion for ci_builds](gitlab-org/gitlab@176992aa2b2e76b22637a07d5bafbd6541324a7d) ([merge request](gitlab-org/gitlab!70351))
- [Drop support for data-track-event](gitlab-org/gitlab@ac6027fbef6adf41643412a84945fda6f15c9666) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70234))
## 14.3.4 (2021-10-28)
### Security (13 changes)
- [Highlight usage of unicode bidi characters](gitlab-org/security/gitlab@0b9bcafa73bc12ad873f75584b993f7b94f1f2e7) ([merge request](gitlab-org/security/gitlab!1938))
- [Fix dompurify.js to prevent path traversal attacks](gitlab-org/security/gitlab@6599afd4d7357ab356fcb773af19f8388978b3ed) ([merge request](gitlab-org/security/gitlab!1930))
- [Refresh authorizations on transfer of groups having project shares](gitlab-org/security/gitlab@faad71f44a1b1048b73897d450c923a18ec18c0b) ([merge request](gitlab-org/security/gitlab!1917))
- [Do not allow Applications API to create apps with blank scopes](gitlab-org/security/gitlab@293931500c84ef7ea9a2117d3ddf094f8ac15dcf) ([merge request](gitlab-org/security/gitlab!1923))
- [Don't allow author to resolve discussions when MR is locked via GraphQL](gitlab-org/security/gitlab@5027cb2b0303645a921b95d324d3d55dcf7632e4) ([merge request](gitlab-org/security/gitlab!1920))
- [Workhorse: Allow uploading only a single file](gitlab-org/security/gitlab@c18c2ddfa34a4c3e476136ab3eba9be7f265ad59) ([merge request](gitlab-org/security/gitlab!1914))
- [Group owners should see SCIM token only once](gitlab-org/security/gitlab@3d6664461da720fb256d8e139961b383e33a3b90) ([merge request](gitlab-org/security/gitlab!1907)) **GitLab Enterprise Edition**
- [Respect visibility level settings when updating project via API](gitlab-org/security/gitlab@124ca62c02bfa8ef6f7de7b328f80756fd01c052) ([merge request](gitlab-org/security/gitlab!1904))
- [Avoid decoding the whole tiff image on isTIFF check](gitlab-org/security/gitlab@8e6ffd52f50170a5cf2761e50a3d6efaca5fe64f) ([merge request](gitlab-org/security/gitlab!1900))
- [Adding a '[redacted]' to mask private email addresses](gitlab-org/security/gitlab@6f2a2b2240eb7590bbc773f35d3927d4854a31b5) ([merge request](gitlab-org/security/gitlab!1894))
- [Do not display the root password by default](gitlab-org/security/gitlab@87893548183fc4a111e12c0bdb3e409175a41668) ([merge request](gitlab-org/security/gitlab!1803))
- [Set PipelineSchedules to inactive](gitlab-org/security/gitlab@0e77e1cd938f876f3e9c049a84486c8c90cd0f3f) ([merge request](gitlab-org/security/gitlab!1879))
- [Remove external_webhook_token from exported project](gitlab-org/security/gitlab@1362f7481aad5e4295da11f0db53e31600c7c7b5) ([merge request](gitlab-org/security/gitlab!1866))
## 14.3.3 (2021-10-12)
### Fixed (3 changes)
@ -939,6 +975,24 @@ entry.
- [Remove the FF ci_reset_bridge_with_subsequent_jobs](gitlab-org/gitlab@a4a75095b9b0250d0b1bdadea90c8a4cd24449b2) ([merge request](gitlab-org/gitlab!68295))
- [Removes ci_same_stage_job_needs ff](gitlab-org/gitlab@5e509cf7aa90041a541b19dda563120a359f0bf9) ([merge request](gitlab-org/gitlab!68041))
## 14.2.6 (2021-10-28)
### Security (13 changes)
- [Highlight usage of unicode bidi characters](gitlab-org/security/gitlab@18a768bb3cd19b6dc780bb85d91a93605ec8aa4f) ([merge request](gitlab-org/security/gitlab!1939))
- [Fix dompurify.js to prevent path traversal attacks](gitlab-org/security/gitlab@cfd7c715162c22060b9b80268ef501a9e604421a) ([merge request](gitlab-org/security/gitlab!1931))
- [Refresh authorizations on transfer of groups having project shares](gitlab-org/security/gitlab@3fc08eb869156a090b015e78da79c8ced16a7162) ([merge request](gitlab-org/security/gitlab!1918))
- [Do not allow Applications API to create apps with blank scopes](gitlab-org/security/gitlab@c4ffc8c0ee5356bcb9b76dbfa92517589b4225a8) ([merge request](gitlab-org/security/gitlab!1924))
- [Don't allow author to resolve discussions when MR is locked via GraphQL](gitlab-org/security/gitlab@fe2d0b6f250b60619da97f162c93c9e645daf4af) ([merge request](gitlab-org/security/gitlab!1921))
- [Workhorse: Allow uploading only a single file](gitlab-org/security/gitlab@89b04599592b7dfc0e4883cfde5d3ecd9ea855b2) ([merge request](gitlab-org/security/gitlab!1915))
- [Group owners should see SCIM token only once](gitlab-org/security/gitlab@d52c1e41f38039db075a7a3418b8eb9ed8474c2a) ([merge request](gitlab-org/security/gitlab!1908)) **GitLab Enterprise Edition**
- [Respect visibility level settings when updating project via API](gitlab-org/security/gitlab@3051d6a00d1a56133a77ecd24313bafb4565d576) ([merge request](gitlab-org/security/gitlab!1905))
- [Avoid decoding the whole tiff image on isTIFF check](gitlab-org/security/gitlab@bab7f45def8fc81fe4b0961a21b4c90a60358ff9) ([merge request](gitlab-org/security/gitlab!1901))
- [Adding a '[redacted]' to mask private email addresses](gitlab-org/security/gitlab@8eb9749f40b87b9b49b034bceb263219a4d3b114) ([merge request](gitlab-org/security/gitlab!1895))
- [Do not display the root password by default](gitlab-org/security/gitlab@4ccf08b6645b9f616657edd266d9d31e3602d170) ([merge request](gitlab-org/security/gitlab!1802))
- [Set PipelineSchedules to inactive](gitlab-org/security/gitlab@ebee16945325d22ceb5c07b7ba48df6fd0b2f067) ([merge request](gitlab-org/security/gitlab!1878))
- [Remove external_webhook_token from exported project](gitlab-org/security/gitlab@f3ef12185902f3ed5c9d62ffce07418fd704a753) ([merge request](gitlab-org/security/gitlab!1865))
## 14.2.5 (2021-09-30)
### Security (28 changes)

View file

@ -1 +1 @@
bf9abd731ba2dab65e8c275c51d578d95dd3d506
80d41b90597e1ca027cc6f02a09a6d3607d75ba2

View file

@ -1,5 +1,5 @@
import { sanitize as dompurifySanitize, addHook } from 'dompurify';
import { getBaseURL, relativePathToAbsolute } from '~/lib/utils/url_utility';
import { getNormalizedURL, getBaseURL, relativePathToAbsolute } from '~/lib/utils/url_utility';
const defaultConfig = {
// Safely allow SVG <use> tags
@ -11,12 +11,14 @@ const defaultConfig = {
// Only icons urls from `gon` are allowed
const getAllowedIconUrls = (gon = window.gon) =>
[gon.sprite_file_icons, gon.sprite_icons].filter(Boolean);
[gon.sprite_file_icons, gon.sprite_icons]
.filter(Boolean)
.map((path) => relativePathToAbsolute(path, getBaseURL()));
const isUrlAllowed = (url) => getAllowedIconUrls().some((allowedUrl) => url.startsWith(allowedUrl));
const isUrlAllowed = (url) =>
getAllowedIconUrls().some((allowedUrl) => getNormalizedURL(url).startsWith(allowedUrl));
const isHrefSafe = (url) =>
isUrlAllowed(url) || isUrlAllowed(relativePathToAbsolute(url, getBaseURL())) || url.match(/^#/);
const isHrefSafe = (url) => url.match(/^#/) || isUrlAllowed(url);
const removeUnsafeHref = (node, attr) => {
if (!node.hasAttribute(attr)) {
@ -36,13 +38,14 @@ const removeUnsafeHref = (node, attr) => {
* <use href="/assets/icons-xxx.svg#icon_name"></use>
* </svg>
*
* It validates both href & xlink:href attributes.
* Note that `xlink:href` is deprecated, but still in use
* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
*
* @param {Object} node - Node to sanitize
*/
const sanitizeSvgIcon = (node) => {
removeUnsafeHref(node, 'href');
// Note: `xlink:href` is deprecated, but still in use
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
removeUnsafeHref(node, 'xlink:href');
};

View file

@ -399,6 +399,24 @@ export function isSafeURL(url) {
}
}
/**
* Returns a normalized url
*
* https://gitlab.com/foo/../baz => https://gitlab.com/baz
*
* @param {String} url - URL to be transformed
* @param {String?} baseUrl - current base URL
* @returns {String}
*/
export const getNormalizedURL = (url, baseUrl) => {
const base = baseUrl || getBaseURL();
try {
return new URL(url, base).href;
} catch (e) {
return '';
}
};
export function getWebSocketProtocol() {
return window.location.protocol.replace('http', 'ws');
}

View file

@ -1,12 +1,13 @@
<script>
import { GlAlert, GlFormGroup, GlFormInputGroup, GlSkeletonLoader, GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import {
DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
DEPENDENCY_PROXY_DOCS_PATH,
} from '~/packages_and_registries/settings/group/constants';
import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants';
import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql';
@ -22,11 +23,16 @@ export default {
},
inject: ['groupPath', 'dependencyProxyAvailable'],
i18n: {
proxyNotAvailableText: __('Dependency Proxy feature is limited to public groups for now.'),
proxyDisabledText: __('Dependency Proxy disabled. To enable it, contact the group owner.'),
proxyImagePrefix: __('Dependency Proxy image prefix'),
copyImagePrefixText: __('Copy prefix'),
blobCountAndSize: __('Contains %{count} blobs of images (%{size})'),
proxyNotAvailableText: s__(
'DependencyProxy|Dependency Proxy feature is limited to public groups for now.',
),
proxyDisabledText: s__(
'DependencyProxy|Dependency Proxy disabled. To enable it, contact the group owner.',
),
proxyImagePrefix: s__('DependencyProxy|Dependency Proxy image prefix'),
copyImagePrefixText: s__('DependencyProxy|Copy prefix'),
blobCountAndSize: s__('DependencyProxy|Contains %{count} blobs of images (%{size})'),
pageTitle: s__('DependencyProxy|Dependency Proxy'),
},
data() {
return {
@ -40,7 +46,7 @@ export default {
return !this.dependencyProxyAvailable;
},
variables() {
return { fullPath: this.groupPath };
return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE };
},
},
},
@ -62,7 +68,7 @@ export default {
<template>
<div>
<title-area :title="__('Dependency Proxy')" :info-messages="infoMessages" />
<title-area :title="$options.i18n.pageTitle" :info-messages="infoMessages" />
<gl-alert
v-if="!dependencyProxyAvailable"
:dismissible="false"

View file

@ -0,0 +1,49 @@
<script>
import { GlSprintf } from '@gitlab/ui';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { s__ } from '~/locale';
export default {
name: 'ManifestRow',
components: {
GlSprintf,
ListItem,
TimeagoTooltip,
},
props: {
manifest: {
type: Object,
required: true,
},
},
computed: {
name() {
return this.manifest?.imageName.split(':')[0];
},
version() {
return this.manifest?.imageName.split(':')[1];
},
},
i18n: {
cachedAgoMessage: s__('DependencyProxy|Cached %{time}'),
},
};
</script>
<template>
<list-item>
<template #left-primary> {{ name }} </template>
<template #left-secondary> {{ version }} </template>
<template #right-primary> &nbsp; </template>
<template #right-secondary>
<timeago-tooltip :time="manifest.createdAt" data-testid="cached-message">
<template #default="{ timeAgo }">
<gl-sprintf :message="$options.i18n.cachedAgoMessage">
<template #time>{{ timeAgo }}</template>
</gl-sprintf>
</template>
</timeago-tooltip>
</template>
</list-item>
</template>

View file

@ -0,0 +1,46 @@
<script>
import { GlKeysetPagination } from '@gitlab/ui';
import { s__ } from '~/locale';
import ManifestRow from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue';
export default {
name: 'ManifestsLists',
components: {
ManifestRow,
GlKeysetPagination,
},
props: {
manifests: {
type: Array,
required: false,
default: () => [],
},
pagination: {
type: Object,
required: true,
},
},
i18n: {
listTitle: s__('DependencyProxy|Manifest list'),
},
};
</script>
<template>
<div class="gl-mt-5">
<h3 class="gl-font-base">{{ $options.i18n.listTitle }}</h3>
<div
class="gl-border-t-1 gl-border-gray-100 gl-border-t-solid gl-display-flex gl-flex-direction-column"
>
<manifest-row v-for="(manifest, index) in manifests" :key="index" :manifest="manifest" />
</div>
<div class="gl-display-flex gl-justify-content-center">
<gl-keyset-pagination
v-bind="pagination"
class="gl-mt-3"
@prev="$emit('prev-page')"
@next="$emit('next-page')"
/>
</div>
</div>
</template>

View file

@ -0,0 +1 @@
export const GRAPHQL_PAGE_SIZE = 20;

View file

@ -1,4 +1,12 @@
query getDependencyProxyDetails($fullPath: ID!) {
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query getDependencyProxyDetails(
$fullPath: ID!
$first: Int
$last: Int
$after: String
$before: String
) {
group(fullPath: $fullPath) {
dependencyProxyBlobCount
dependencyProxyTotalSize
@ -6,5 +14,14 @@ query getDependencyProxyDetails($fullPath: ID!) {
dependencyProxySetting {
enabled
}
dependencyProxyManifests(after: $after, before: $before, first: $first, last: $last) {
nodes {
createdAt
imageName
}
pageInfo {
...PageInfo
}
}
}
}

View file

@ -1,10 +1,5 @@
<script>
import { GlIcon } from '@gitlab/ui';
export default {
components: {
GlIcon,
},
props: {
label: {
type: String,
@ -29,10 +24,14 @@ export default {
<div class="project-feature-row">
<label v-if="label" class="label-bold">
{{ label }}
<a v-if="helpPath" :href="helpPath" target="_blank">
<gl-icon name="question-o" />
</a>
</label>
<span v-if="helpText" class="form-text text-muted"> {{ helpText }} </span> <slot></slot>
<div>
<span v-if="helpText" class="text-muted"> {{ helpText }} </span>
<span v-if="helpPath"
><a :href="helpPath" target="_blank">{{ __('Learn more') }}</a
>.</span
>
</div>
<slot></slot>
</div>
</template>

View file

@ -37,6 +37,10 @@ export default {
securityAndComplianceLabel: s__('ProjectSettings|Security & Compliance'),
snippetsLabel: s__('ProjectSettings|Snippets'),
wikiLabel: s__('ProjectSettings|Wiki'),
pucWarningLabel: s__('ProjectSettings|Warn about Potentially Unwanted Characters'),
pucWarningHelpText: s__(
'ProjectSettings|Highlight the usage of hidden unicode characters. These have innocent uses for right-to-left languages, but can also be used in potential exploits.',
),
},
components: {
@ -178,6 +182,7 @@ export default {
securityAndComplianceAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
operationsAccessLevel: featureAccessLevel.EVERYONE,
containerRegistryAccessLevel: featureAccessLevel.EVERYONE,
warnAboutPotentiallyUnwantedCharacters: true,
lfsEnabled: true,
requestAccessEnabled: true,
highlightChangesClass: false,
@ -395,6 +400,9 @@ export default {
ref="project-visibility-settings"
:help-path="visibilityHelpPath"
:label="s__('ProjectSettings|Project visibility')"
:help-text="
s__('ProjectSettings|Manage who can see the project in the public access directory.')
"
>
<div class="project-feature-controls gl-display-flex gl-align-items-center gl-my-3 gl-mx-0">
<div class="select-wrapper gl-flex-grow-1">
@ -752,5 +760,19 @@ export default {
}}</template>
</gl-form-checkbox>
</project-setting-row>
<project-setting-row class="gl-mb-5">
<input
:value="warnAboutPotentiallyUnwantedCharacters"
type="hidden"
name="project[project_setting_attributes][warn_about_potentially_unwanted_characters]"
/>
<gl-form-checkbox
v-model="warnAboutPotentiallyUnwantedCharacters"
name="project[project_setting_attributes][warn_about_potentially_unwanted_characters]"
>
{{ $options.i18n.pucWarningLabel }}
<template #help>{{ $options.i18n.pucWarningHelpText }}</template>
</gl-form-checkbox>
</project-setting-row>
</div>
</template>

View file

@ -66,7 +66,13 @@ export default {
data-qa-selector="clone_button"
/>
</div>
<snippet-blob v-for="blob in blobs" :key="blob.path" :snippet="snippet" :blob="blob" />
<snippet-blob
v-for="blob in blobs"
:key="blob.path"
:snippet="snippet"
:blob="blob"
class="project-highlight-puc"
/>
</template>
</div>
</template>

View file

@ -85,3 +85,9 @@
td.line-numbers {
line-height: 1;
}
.project-highlight-puc .unicode-bidi::before {
content: '<EFBFBD>';
cursor: pointer;
text-decoration: underline wavy $red-500;
}

View file

@ -76,6 +76,6 @@ class JiraConnect::ApplicationController < ApplicationController
end
def signed_install_active?
Feature.enabled?(:jira_connect_asymmetric_jwt)
Feature.enabled?(:jira_connect_asymmetric_jwt, default_enabled: :yaml)
end
end

View file

@ -105,8 +105,7 @@ class Projects::BranchesController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def destroy
@branch_name = Addressable::URI.unescape(params[:id])
result = ::Branches::DeleteService.new(project, current_user).execute(@branch_name)
result = ::Branches::DeleteService.new(project, current_user).execute(params[:id])
respond_to do |format|
format.html do

View file

@ -409,6 +409,7 @@ class ProjectsController < Projects::ApplicationController
show_default_award_emojis
squash_option
mr_default_target_self
warn_about_potentially_unwanted_characters
]
end

View file

@ -8,6 +8,8 @@ module Mutations
argument :severity, Types::IssuableSeverityEnum, required: true,
description: 'Set the incident severity level.'
authorize :admin_issue
def resolve(project_path:, iid:, severity:)
issue = authorized_find!(project_path: project_path, iid: iid)
project = issue.project

View file

@ -10,7 +10,14 @@ module LearnGitlabHelper
def onboarding_actions_data(project)
attributes = onboarding_progress(project).attributes.symbolize_keys
action_urls.to_h do |action, url|
urls_to_use = nil
experiment(:change_continuous_onboarding_link_urls) do |e|
e.use { urls_to_use = action_urls }
e.try { urls_to_use = new_action_urls(project) }
end
urls_to_use.to_h do |action, url|
[
action,
url: url,
@ -46,6 +53,17 @@ module LearnGitlabHelper
.merge(LearnGitlab::Onboarding::ACTION_DOC_URLS)
end
def new_action_urls(project)
action_urls.merge(
issue_created: project_issues_path(project),
git_write: project_path(project),
pipeline_created: project_pipelines_path(project),
merge_request_created: project_merge_requests_path(project),
user_added: project_members_url(project),
security_scan_enabled: project_security_configuration_path(project)
)
end
def learn_gitlab_project
@learn_gitlab_project ||= LearnGitlab::Project.new(current_user).project
end
@ -54,3 +72,5 @@ module LearnGitlabHelper
OnboardingProgress.find_by(namespace: project.namespace) # rubocop: disable CodeReuse/ActiveRecord
end
end
LearnGitlabHelper.prepend_mod_with('LearnGitlabHelper')

View file

@ -376,6 +376,12 @@ module ProjectsHelper
}
end
def project_classes(project)
return "project-highlight-puc" if project.warn_about_potentially_unwanted_characters?
""
end
private
def tab_ability_map
@ -532,6 +538,7 @@ module ProjectsHelper
metricsDashboardAccessLevel: feature.metrics_dashboard_access_level,
operationsAccessLevel: feature.operations_access_level,
showDefaultAwardEmojis: project.show_default_award_emojis?,
warnAboutPotentiallyUnwantedCharacters: project.warn_about_potentially_unwanted_characters?,
securityAndComplianceAccessLevel: project.security_and_compliance_access_level,
containerRegistryAccessLevel: feature.container_registry_access_level
}

View file

@ -23,6 +23,7 @@ module Ci
serialize :config_options, Serializers::SymbolizedJson # rubocop:disable Cop/ActiveRecordSerialize
serialize :config_variables, Serializers::SymbolizedJson # rubocop:disable Cop/ActiveRecordSerialize
serialize :runtime_runner_features, Serializers::SymbolizedJson # rubocop:disable Cop/ActiveRecordSerialize
chronic_duration_attr_reader :timeout_human_readable, :timeout
@ -47,6 +48,14 @@ module Ci
update(timeout: timeout.value, timeout_source: timeout.source)
end
def set_cancel_gracefully
runtime_runner_features.merge!( { cancel_gracefully: true } )
end
def cancel_gracefully?
runtime_runner_features[:cancel_gracefully] == true
end
private
def set_build_project

View file

@ -20,6 +20,8 @@ module Ci
delegate :interruptible, to: :metadata, prefix: false, allow_nil: true
delegate :has_exposed_artifacts?, to: :metadata, prefix: false, allow_nil: true
delegate :environment_auto_stop_in, to: :metadata, prefix: false, allow_nil: true
delegate :set_cancel_gracefully, to: :metadata, prefix: false, allow_nil: false
delegate :cancel_gracefully?, to: :metadata, prefix: false, allow_nil: false
before_create :ensure_metadata
end

View file

@ -81,8 +81,7 @@ module ResolvableDiscussion
return false unless current_user
return false unless resolvable?
current_user == self.noteable.try(:author) ||
current_user.can?(:resolve_note, self.project)
current_user.can?(:resolve_note, self.noteable)
end
def resolve!(current_user)

View file

@ -34,6 +34,8 @@ class Namespace < ApplicationRecord
SHARED_RUNNERS_SETTINGS = [SR_DISABLED_AND_UNOVERRIDABLE, SR_DISABLED_WITH_OVERRIDE, SR_ENABLED].freeze
URL_MAX_LENGTH = 255
PATH_TRAILING_VIOLATIONS = %w[.git .atom .].freeze
cache_markdown_field :description, pipeline: :description
has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@ -203,9 +205,14 @@ class Namespace < ApplicationRecord
# Remove everything that's not in the list of allowed characters.
path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
# Remove trailing violations ('.atom', '.git', or '.')
path.gsub!(/(\.atom|\.git|\.)*\z/, "")
loop do
orig = path
PATH_TRAILING_VIOLATIONS.each { |ext| path = path.chomp(ext) }
break if orig == path
end
# Remove leading violations ('-')
path.gsub!(/\A\-+/, "")
path.gsub!(/\A\-+/, "")
# Users with the great usernames of "." or ".." would end up with a blank username.
# Work around that by setting their username to "blank", followed by a counter.
@ -531,21 +538,23 @@ class Namespace < ApplicationRecord
# Until we compare the inconsistency rates of the new specialized worker and
# the old approach, we still run AuthorizedProjectsWorker
# but with some delay and lower urgency as a safety net.
Group
.joins(project_group_links: :project)
.where(projects: { namespace_id: id })
.distinct
.find_each do |group|
group.refresh_members_authorized_projects(
blocking: false,
priority: UserProjectAccessChangedService::LOW_PRIORITY
)
end
enqueue_jobs_for_groups_requiring_authorizations_refresh(priority: UserProjectAccessChangedService::LOW_PRIORITY)
else
Group
.joins(project_group_links: :project)
.where(projects: { namespace_id: id })
.find_each(&:refresh_members_authorized_projects)
enqueue_jobs_for_groups_requiring_authorizations_refresh(priority: UserProjectAccessChangedService::HIGH_PRIORITY)
end
end
def enqueue_jobs_for_groups_requiring_authorizations_refresh(priority:)
groups_requiring_authorizations_refresh = Group
.joins(project_group_links: :project)
.where(projects: { namespace_id: id })
.distinct
groups_requiring_authorizations_refresh.find_each do |group|
group.refresh_members_authorized_projects(
blocking: false,
priority: priority
)
end
end

View file

@ -423,8 +423,8 @@ class Project < ApplicationRecord
:container_registry_access_level, :container_registry_enabled?,
to: :project_feature, allow_nil: true
alias_method :container_registry_enabled, :container_registry_enabled?
delegate :show_default_award_emojis, :show_default_award_emojis=,
:show_default_award_emojis?,
delegate :show_default_award_emojis, :show_default_award_emojis=, :show_default_award_emojis?,
:warn_about_potentially_unwanted_characters, :warn_about_potentially_unwanted_characters=, :warn_about_potentially_unwanted_characters?,
to: :project_setting, allow_nil: true
delegate :scheduled?, :started?, :in_progress?, :failed?, :finished?,
prefix: :import, to: :import_state, allow_nil: true
@ -2724,8 +2724,23 @@ class Project < ApplicationRecord
self.errors.add(:base, _("Could not change HEAD: branch '%{branch}' does not exist") % { branch: branch })
end
def visible_group_links(for_user:)
user = for_user
links = project_group_links_with_preload
user.max_member_access_for_group_ids(links.map(&:group_id)) if user && links.any?
DeclarativePolicy.user_scope do
links.select { Ability.allowed?(user, :read_group, _1.group) }
end
end
private
# overridden in EE
def project_group_links_with_preload
project_group_links
end
def save_topics
return if @topic_list.nil?

View file

@ -15,6 +15,7 @@ class ProjectGroupLink < ApplicationRecord
validate :different_group
scope :non_guests, -> { where('group_access > ?', Gitlab::Access::GUEST) }
scope :in_group, -> (group_ids) { where(group_id: group_ids) }
alias_method :shared_with_group, :group

View file

@ -15,13 +15,21 @@ module Uploads
end
def delete_keys(keys)
keys.each do |key|
connection.delete_object(bucket_name, key)
end
keys.each { |key| delete_object(key) }
end
private
def delete_object(key)
connection.delete_object(bucket_name, key)
# So far, only GoogleCloudStorage raises an exception when the file is not found.
# Other providers support idempotent requests and does not raise an error
# when the file is missing.
rescue ::Google::Apis::ClientError => e
Gitlab::ErrorTracking.log_exception(e)
end
def object_store
Gitlab.config.uploads.object_store
end

View file

@ -1434,7 +1434,7 @@ class User < ApplicationRecord
name: name,
username: username,
avatar_url: avatar_url(only_path: false),
email: email
email: public_email.presence || _('[REDACTED]')
}
end

View file

@ -11,6 +11,8 @@ class IssuablePolicy < BasePolicy
@user && @subject.assignee_or_author?(@user)
end
condition(:is_author) { @subject&.author == @user }
rule { can?(:guest_access) & assignee_or_author }.policy do
enable :read_issue
enable :update_issue
@ -20,6 +22,10 @@ class IssuablePolicy < BasePolicy
enable :reopen_merge_request
end
rule { is_author }.policy do
enable :resolve_note
end
rule { locked & ~is_project_member }.policy do
prevent :create_note
prevent :admin_note

View file

@ -221,6 +221,7 @@ class ProjectPolicy < BasePolicy
enable :set_note_created_at
enable :set_emails_disabled
enable :set_show_default_award_emojis
enable :set_warn_about_potentially_unwanted_characters
end
rule { can?(:guest_access) }.policy do

View file

@ -1,13 +1,17 @@
# frozen_string_literal: true
module UpdateVisibilityLevel
# check that user is allowed to set specified visibility_level
def valid_visibility_level_change?(target, new_visibility)
# check that user is allowed to set specified visibility_level
if new_visibility && new_visibility.to_i != target.visibility_level
unless can?(current_user, :change_visibility_level, target) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
return true unless new_visibility
deny_visibility_level(target, new_visibility)
new_visibility_level = Gitlab::VisibilityLevel.level_value(new_visibility)
if new_visibility_level != target.visibility_level_value
unless can?(current_user, :change_visibility_level, target) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility_level)
deny_visibility_level(target, new_visibility_level)
return false
end
end

View file

@ -182,6 +182,14 @@ module Groups
# schedule refreshing projects for all the members of the group
@group.refresh_members_authorized_projects
# When a group is transferred, it also affects who gets access to the projects shared to
# the subgroups within its hierarchy, so we also schedule jobs that refresh authorizations for all such shared projects.
project_group_shares_within_the_hierarchy = ProjectGroupLink.in_group(group.self_and_descendants.select(:id))
project_group_shares_within_the_hierarchy.find_each do |project_group_link|
AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(project_group_link.project_id)
end
end
def raise_transfer_error(message)

View file

@ -15,7 +15,7 @@ module Groups
return false
end
return false unless valid_visibility_level_change?(group, params[:visibility_level])
return false unless valid_visibility_level_change?(group, group.visibility_attribute_value(params))
return false unless valid_share_with_group_lock_change?
@ -77,7 +77,7 @@ module Groups
end
def after_update
if group.previous_changes.include?(:visibility_level) && group.private?
if group.previous_changes.include?(group.visibility_level_field) && group.private?
# don't enqueue immediately to prevent todos removal in case of a mistake
TodosDestroyer::GroupPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, group.id)
end

View file

@ -142,6 +142,7 @@ class IssuableBaseService < ::BaseProjectService
def filter_severity(issuable)
severity = params.delete(:severity)
return unless severity && issuable.supports_severity?
return unless can_admin_issuable?(issuable)
severity = IssuableSeverity::DEFAULT unless IssuableSeverity.severities.key?(severity)
return if severity == issuable.severity

View file

@ -49,7 +49,7 @@ module Projects
private
def validate!
unless valid_visibility_level_change?(project, params[:visibility_level])
unless valid_visibility_level_change?(project, project.visibility_attribute_value(params))
raise ValidationError, s_('UpdateProject|New visibility level not allowed!')
end

View file

@ -11,7 +11,7 @@
'external-id' => @aws_role.role_external_id,
'role-arn' => @aws_role.role_arn,
'instance-types' => @instance_types,
'kubernetes-integration-help-path' => help_page_path('user/project/clusters/index'),
'kubernetes-integration-help-path' => help_page_path('user/infrastructure/clusters/index.md'),
'account-and-external-ids-help-path' => help_page_path('user/project/clusters/add_eks_clusters.md', anchor: 'how-to-create-a-new-cluster-on-eks-through-cluster-certificates-deprecated'),
'create-role-arn-help-path' => help_page_path('user/project/clusters/add_eks_clusters.md', anchor: 'how-to-create-a-new-cluster-on-eks-through-cluster-certificates-deprecated'),
'external-link-icon' => sprite_icon('external-link') } }

View file

@ -2,7 +2,7 @@
- zones_link_url = 'https://cloud.google.com/compute/docs/regions-zones/regions-zones'
- machine_type_link_url = 'https://cloud.google.com/compute/docs/machine-types'
- pricing_link_url = 'https://cloud.google.com/compute/pricing#machinetype'
- kubernetes_integration_url = help_page_path('user/project/clusters/index')
- kubernetes_integration_url = help_page_path('user/infrastructure/clusters/index.md')
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe
- help_link_end = ' %{external_link_icon}</a>'.html_safe % { external_link_icon: external_link_icon }

View file

@ -13,7 +13,7 @@
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
provider_type: @cluster.provider_type,
help_path: help_page_path('user/project/clusters/index.md'),
help_path: help_page_path('user/infrastructure/clusters/index.md'),
environments_help_path: help_page_path('ci/environments/index.md', anchor: 'create-a-static-environment'),
clusters_help_path: help_page_path('user/project/clusters/deploy_to_cluster.md'),
deploy_boards_help_path: help_page_path('user/project/deploy_boards.md', anchor: 'enabling-deploy-boards'),

View file

@ -1,4 +1,5 @@
- page_title _("Dependency Proxy")
- @content_class = "limit-container-width" unless fluid_layout
- dependency_proxy_available = Feature.enabled?(:dependency_proxy_for_private_groups, default_enabled: true) || @group.public?
#js-dependency-proxy{ data: { group_path: @group.full_path,

View file

@ -6,6 +6,7 @@
- display_subscription_banner!
- display_namespace_storage_limit_alert!
- @left_sidebar = true
- @content_class = [@content_class, project_classes(@project)].compact.join(" ")
- content_for :project_javascripts do
- project = @target_project || @project

View file

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/342808
milestone: '14.4'
type: development
group: group::integrations
default_enabled: false
default_enabled: true

View file

@ -0,0 +1,8 @@
---
name: change_continuous_onboarding_link_urls
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71408
rollout_issue_url:
milestone: '14.5'
type: experiment
group: group::conversion
default_enabled: false

View file

@ -176,8 +176,10 @@ production: &base
## Application settings cache expiry in seconds (default: 60)
# application_settings_cache_seconds: 60
## Print initial root password to stdout during initialization (default: true)
# display_initial_root_password: true
## Print initial root password to stdout during initialization (default: false)
# WARNING: setting this to true means that the root password will be printed in
# plaintext. This can be a security risk.
# display_initial_root_password: false
## Reply by email
# Allow users to comment on issues and merge requests by replying to notification emails.

View file

@ -218,8 +218,7 @@ Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config'
Settings.gitlab['impersonation_enabled'] ||= true if Settings.gitlab['impersonation_enabled'].nil?
Settings.gitlab['usage_ping_enabled'] = true if Settings.gitlab['usage_ping_enabled'].nil?
Settings.gitlab['max_request_duration_seconds'] ||= 57
Settings.gitlab['display_initial_root_password'] = true if Settings.gitlab['display_initial_root_password'].nil?
Settings.gitlab['display_initial_root_password'] = false if Settings.gitlab['display_initial_root_password'].nil?
Gitlab.ee do
Settings.gitlab['mirror_max_delay'] ||= 300

View file

@ -26,7 +26,7 @@ if user.persisted?
if ::Settings.gitlab['display_initial_root_password']
puts "password: #{user_args[:password]}".color(:green)
else
puts "password: *** - You opted not to display initial root password to STDOUT."
puts "password: ******".color(:green)
end
else
puts "password: You'll be prompted to create one on your first visit.".color(:green)

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddWarnAboutPotentiallyUnwantedCharactersToProjectSettings < Gitlab::Database::Migration[1.0]
enable_lock_retries!
def up
add_column :project_settings, :warn_about_potentially_unwanted_characters, :boolean, null: false, default: true
end
def down
remove_column :project_settings, :warn_about_potentially_unwanted_characters
end
end

View file

@ -0,0 +1 @@
0f808c27d19e6a38d4aa31f2dd820fe226681af84e05c4af47213409b2043e5a

View file

@ -18262,6 +18262,7 @@ CREATE TABLE project_settings (
cve_id_request_enabled boolean DEFAULT true NOT NULL,
mr_default_target_self boolean DEFAULT false NOT NULL,
previous_default_branch text,
warn_about_potentially_unwanted_characters boolean DEFAULT true NOT NULL,
CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)),
CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL))
);

View file

@ -23,6 +23,14 @@ We have two kinds of changes related to JH:
If needed, review the corresponding JH merge request located at [JH repository](https://gitlab.com/gitlab-jh/gitlab)
## When to merge files to the GitLab Inc. repository
Files that are added to the `gitlab-jh` repository outside of `jh/` must be mirrored in the GitLab Inc. repository.
If code that is added to the GitLab Inc. repository references (for example, `render_if_exists`) any `gitlab-jh` file that does not
exist in the GitLab Inc. codebase, add a comment with a link to the JiHu merge request or file. This is to prevent
the reference from being misidentified as a missing partial and subsequently deleted in the `gitlab` codebase.
## Process overview
See the [merge request process](https://about.gitlab.com/handbook/ceo/chief-of-staff-team/jihu-support/#merge-request-process)

View file

@ -6,62 +6,14 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Connect a cluster to GitLab **(FREE)**
You can create new or connect existing clusters to GitLab through different [levels](#cluster-levels),
using different [methods](#methods-to-connect-a-cluster-to-gitlab).
The [certificate-based Kubernetes integration with GitLab](../index.md)
was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8)
in GitLab 14.5. To connect your clusters, use the [GitLab Kubernetes Agent](../../../clusters/agent/index.md).
Before getting started:
1. Check the [supported Kubernetes cluster versions](#supported-cluster-versions).
1. Define the [cluster level](#cluster-levels) according to your case.
After that:
1. Choose the [method](#methods-to-connect-a-cluster-to-gitlab)
to connect your cluster according to your case.
1. [View your clusters](#view-your-clusters) connected to GitLab.
## Methods to connect a cluster to GitLab
GitLab offers three methods to connect existing and create new clusters:
- **GitLab Kubernetes Agent**: the best solution to
[connect existing clusters](#connect-existing-clusters-to-gitlab).
- **Infrastructure as Code**: it's a broader infrastructure management
toolset that includes managing your cluster. It's the recommended
solution to [create a new cluster](#create-new-clusters-from-gitlab)
from GitLab.
- **Certificate-based method**: our first and legacy solution uses
cluster certificates to connect your cluster to GitLab. It is no longer
recommended for [security implications](#security-implications-for-clusters-connected-with-certificates).
### Connect existing clusters to GitLab
To safely connect and configure an existing cluster on the **project level**,
we **recommend** using the [GitLab Kubernetes Agent](../../../clusters/agent/index.md).
We are working to support [the Agent for connecting a cluster at the group level](https://gitlab.com/groups/gitlab-org/-/epics/5784).
Alternatively, you can use [cluster certificates](../../../project/clusters/add_existing_cluster.md)
to connect clusters in all levels (projects, group, instance). However,
for [security implications](#security-implications-for-clusters-connected-with-certificates),
we don't recommend using this method.
### Create new clusters from GitLab
To safely create new clusters from GitLab, use
[Infrastructure as Code](../../iac/index.md#create-a-new-cluster-through-iac).
The [certificate-based method to create a new cluster](../../../project/clusters/add_remove_clusters.md)
is still available through the GitLab UI but was **deprecated** in GitLab 14.0.
If possible, we don't recommend using this method.
### Connect multiple clusters to a single project
To connect multiple clusters to a single project in GitLab,
we **recommend** using the [GitLab Kubernetes Agent](../../../clusters/agent/index.md).
You can also use the [certificate-based method](../../../project/clusters/multiple_kubernetes_clusters.md),
but, for [security implications](#security-implications-for-clusters-connected-with-certificates),
we don't recommend using this method.
<!-- TBA: (We need to resolve https://gitlab.com/gitlab-org/gitlab/-/issues/343660 before adding this line)
If you don't have a cluster yet, create one and connect it to GitLab through the Agent.
You can also create a new cluster from GitLab using [Infrastructure as Code](../../iac/index.md#create-a-new-cluster-through-iac).
-->
## Supported cluster versions
@ -85,7 +37,13 @@ Kubernetes version to any supported version at any time:
Some GitLab features may support versions outside the range provided here.
## Cluster levels
## Cluster levels (DEPRECATED)
> [Deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
WARNING:
The [concept of cluster levels was deprecated](../index.md#cluster-levels)
in GitLab 14.5.
Choose your cluster's level according to its purpose:
@ -118,6 +76,8 @@ your cluster's level.
## Security implications for clusters connected with certificates
> Connecting clusters to GitLab through cluster certificates was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
WARNING:
The whole cluster security is based on a model where [developers](../../../permissions.md)
are trusted, so **only trusted users should be allowed to control your clusters**.

View file

@ -14,7 +14,7 @@ This feature was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/e
in GitLab 14.5. To connect clusters to GitLab, use the
[GitLab Kubernetes Agent](../../clusters/agent/index.md).
[Project-level Kubernetes clusters](../../infrastructure/clusters/index.md#cluster-levels)
[Project-level](../../infrastructure/clusters/connect/index.md#cluster-levels-deprecated) Kubernetes clusters
allow you to connect a Kubernetes cluster to a project in GitLab.
You can also [connect multiple clusters](multiple_kubernetes_clusters.md)

View file

@ -15,7 +15,7 @@ module API
params do
requires :name, type: String, desc: 'Application name'
requires :redirect_uri, type: String, desc: 'Application redirect URI'
requires :scopes, type: String, desc: 'Application scopes'
requires :scopes, type: String, desc: 'Application scopes', allow_blank: false
optional :confidential, type: Boolean, default: true,
desc: 'Application will be used where the client secret is confidential'

View file

@ -100,7 +100,9 @@ module API
expose :build_coverage_regex
expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
expose :shared_with_groups do |project, options|
SharedGroupWithProject.represent(project.project_group_links, options)
user = options[:current_user]
SharedGroupWithProject.represent(project.visible_group_links(for_user: user), options)
end
expose :only_allow_merge_if_pipeline_succeeds
expose :allow_merge_on_skipped_pipeline

View file

@ -39,6 +39,7 @@ module API
optional :emails_disabled, type: Boolean, desc: 'Disable email notifications'
optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis'
optional :warn_about_potentially_unwanted_characters, type: Boolean, desc: 'Warn about Potentially Unwanted Characters'
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :remove_source_branch_after_merge, type: Boolean, desc: 'Remove the source branch by default after merge'

View file

@ -9,6 +9,8 @@ module API
feature_category :importers
before { authenticate! unless route.settings[:skip_authentication] }
helpers do
def import_params
declared_params(include_missing: false)
@ -109,6 +111,7 @@ module API
detail 'This feature was introduced in GitLab 10.6.'
success Entities::ProjectImportStatus
end
route_setting :skip_authentication, true
get ':id/import' do
present user_project, with: Entities::ProjectImportStatus
end

View file

@ -433,7 +433,7 @@ module API
authorize_admin_project
attrs = declared_params(include_missing: false)
authorize! :rename_project, user_project if attrs[:name].present?
authorize! :change_visibility_level, user_project if attrs[:visibility].present?
authorize! :change_visibility_level, user_project if user_project.visibility_attribute_present?(attrs)
attrs = translate_params_for_compatibility(attrs)
filter_attributes_using_license!(attrs)

View file

@ -160,16 +160,40 @@ module Banzai
def self.cacheless_render(text, context = {})
return text.to_s unless text.present?
Gitlab::Metrics.measure(:banzai_cacheless_render) do
result = render_result(text, context)
real_start = Gitlab::Metrics::System.monotonic_time
cpu_start = Gitlab::Metrics::System.cpu_time
output = result[:output]
if output.respond_to?(:to_html)
output.to_html
else
output.to_s
end
end
result = render_result(text, context)
output = result[:output]
rendered = if output.respond_to?(:to_html)
output.to_html
else
output.to_s
end
cpu_duration_histogram.observe({}, Gitlab::Metrics::System.cpu_time - cpu_start)
real_duration_histogram.observe({}, Gitlab::Metrics::System.monotonic_time - real_start)
rendered
end
def self.real_duration_histogram
Gitlab::Metrics.histogram(
:gitlab_banzai_cacheless_render_real_duration_seconds,
'Duration of Banzai pipeline rendering in real time',
{},
[0.01, 0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10.0, 50, 100]
)
end
def self.cpu_duration_histogram
Gitlab::Metrics.histogram(
:gitlab_banzai_cacheless_render_cpu_duration_seconds,
'Duration of Banzai pipeline rendering in cpu time',
{},
Gitlab::Metrics::EXECUTION_MEASUREMENT_BUCKETS
)
end
def self.full_cache_key(cache_key, pipeline_name)

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module BulkImports
module Projects
module Pipelines
class MergeRequestsPipeline
include NdjsonPipeline
relation_name 'merge_requests'
extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
def after_run(_)
context.portable.merge_requests.set_latest_merge_request_diff_ids!
end
end
end
end
end

View file

@ -17,10 +17,18 @@ module BulkImports
def load(context, data)
url = data['httpUrlToRepo']
url = url.sub("://", "://oauth2:#{context.configuration.access_token}@")
project = context.portable
Gitlab::UrlBlocker.validate!(url, allow_local_network: allow_local_requests?, allow_localhost: allow_local_requests?)
context.portable.repository.import_repository(url)
project.ensure_repository
project.repository.fetch_as_mirror(url)
end
# The initial fetch can bring in lots of loose refs and objects.
# Running a `git gc` will make importing merge requests faster.
def after_run(_)
::Repositories::HousekeepingService.new(context.portable, :gc).execute
end
private

View file

@ -27,6 +27,10 @@ module BulkImports
pipeline: BulkImports::Common::Pipelines::BoardsPipeline,
stage: 4
},
merge_requests: {
pipeline: BulkImports::Projects::Pipelines::MergeRequestsPipeline,
stage: 4
},
uploads: {
pipeline: BulkImports::Common::Pipelines::UploadsPipeline,
stage: 5

View file

@ -11,6 +11,10 @@ module Gitlab
has_one :saml_provider
def root_saml_provider
root_ancestor.saml_provider
end
def self.declarative_policy_class
"GroupPolicy"
end

View file

@ -368,9 +368,11 @@ excluded_attributes:
- :marked_for_deletion_by_user_id
- :compliance_framework_setting
- :show_default_award_emojis
- :warn_about_potentially_unwanted_characters
- :services
- :exported_protected_branches
- :repository_size_limit
- :external_webhook_token
namespaces:
- :runners_token
- :runners_token_encrypted
@ -583,6 +585,8 @@ excluded_attributes:
system_note_metadata:
- :description_version_id
- :note_id
pipeline_schedules:
- :active
methods:
notes:
- :type

View file

@ -84,6 +84,7 @@ module Gitlab
when :'Ci::Pipeline' then setup_pipeline
when *BUILD_MODELS then setup_build
when :issues then setup_issue
when :'Ci::PipelineSchedule' then setup_pipeline_schedule
end
update_project_references
@ -143,6 +144,10 @@ module Gitlab
@relation_hash['relative_position'] = compute_relative_position
end
def setup_pipeline_schedule
@relation_hash['active'] = false
end
def compute_relative_position
return unless max_relative_position

19
lib/gitlab/unicode.rb Normal file
View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Gitlab
class Unicode
# Regular expression for identifying bidirectional control
# characters in UTF-8 strings
#
# Documentation on how this works:
# https://idiosyncratic-ruby.com/41-proper-unicoding.html
BIDI_REGEXP = /\p{Bidi Control}/.freeze
class << self
# Warning message used to highlight bidi characters in the GUI
def bidi_warning
_("Potentially unwanted character detected: Unicode BiDi Control")
end
end
end
end

View file

@ -155,6 +155,14 @@ module Gitlab
false
end
def visibility_attribute_value(attributes)
visibility_level_attributes.each do |attr|
return attributes[attr] if attributes.has_key?(attr)
end
nil
end
def visibility_level_attributes
[visibility_level_field, visibility_level_field.to_s,
:visibility, 'visibility']

View file

@ -21,12 +21,24 @@ module Rouge
is_first = false
yield %(<span id="LC#{@line_number}" class="line" lang="#{@tag}">)
line.each { |token, value| yield span(token, value.chomp! || value) }
line.each do |token, value|
yield highlight_unicode_control_characters(span(token, value.chomp! || value))
end
yield %(</span>)
@line_number += 1
end
end
private
def highlight_unicode_control_characters(text)
text.gsub(Gitlab::Unicode::BIDI_REGEXP) do |char|
%(<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{Gitlab::Unicode.bidi_warning}">#{char}</span>)
end
end
end
end
end

View file

@ -9178,9 +9178,6 @@ msgstr ""
msgid "ContainerRegistry|You can add an image to this registry with the following commands:"
msgstr ""
msgid "Contains %{count} blobs of images (%{size})"
msgstr ""
msgid "Content parsed with %{link}."
msgstr ""
@ -9370,9 +9367,6 @@ msgstr ""
msgid "Copy link to chart"
msgstr ""
msgid "Copy prefix"
msgstr ""
msgid "Copy reference"
msgstr ""
@ -11230,27 +11224,39 @@ msgstr ""
msgid "Dependency Proxy"
msgstr ""
msgid "Dependency Proxy disabled. To enable it, contact the group owner."
msgstr ""
msgid "Dependency Proxy feature is limited to public groups for now."
msgstr ""
msgid "Dependency Proxy image prefix"
msgstr ""
msgid "Dependency Scanning"
msgstr ""
msgid "DependencyProxy|Cached %{time}"
msgstr ""
msgid "DependencyProxy|Contains %{count} blobs of images (%{size})"
msgstr ""
msgid "DependencyProxy|Copy prefix"
msgstr ""
msgid "DependencyProxy|Create a local proxy for storing frequently used upstream images. %{docLinkStart}Learn more%{docLinkEnd} about dependency proxies."
msgstr ""
msgid "DependencyProxy|Dependency Proxy"
msgstr ""
msgid "DependencyProxy|Dependency Proxy disabled. To enable it, contact the group owner."
msgstr ""
msgid "DependencyProxy|Dependency Proxy feature is limited to public groups for now."
msgstr ""
msgid "DependencyProxy|Dependency Proxy image prefix"
msgstr ""
msgid "DependencyProxy|Enable Proxy"
msgstr ""
msgid "DependencyProxy|Manifest list"
msgstr ""
msgid "Depends on %d merge request being merged"
msgid_plural "Depends on %d merge requests being merged"
msgstr[0] ""
@ -25828,6 +25834,9 @@ msgstr ""
msgid "Postman collection file path or URL"
msgstr ""
msgid "Potentially unwanted character detected: Unicode BiDi Control"
msgstr ""
msgid "Pre-defined push rules."
msgstr ""
@ -26953,6 +26962,9 @@ msgstr ""
msgid "ProjectSettings|Global"
msgstr ""
msgid "ProjectSettings|Highlight the usage of hidden unicode characters. These have innocent uses for right-to-left languages, but can also be used in potential exploits."
msgstr ""
msgid "ProjectSettings|Internal"
msgstr ""
@ -26965,6 +26977,9 @@ msgstr ""
msgid "ProjectSettings|LFS objects from this repository are available to forks. %{linkStart}How do I remove them?%{linkEnd}"
msgstr ""
msgid "ProjectSettings|Manage who can see the project in the public access directory."
msgstr ""
msgid "ProjectSettings|Manages large files such as audio, video, and graphics files."
msgstr ""
@ -27142,6 +27157,9 @@ msgstr ""
msgid "ProjectSettings|Visualize the project's performance metrics."
msgstr ""
msgid "ProjectSettings|Warn about Potentially Unwanted Characters"
msgstr ""
msgid "ProjectSettings|What are badges?"
msgstr ""
@ -39836,6 +39854,9 @@ msgstr ""
msgid "[No reason]"
msgstr ""
msgid "[REDACTED]"
msgstr ""
msgid "[Redacted]"
msgstr ""

View file

@ -159,7 +159,7 @@ module QA
}
end
it "push and pull a npm package via CI using a #{params[:token_name]}", testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1772' do
it "push and pull a npm package via CI using a #{params[:token_name]}" do
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project
commit.commit_message = 'Add .gitlab-ci.yml'

View file

@ -85,7 +85,7 @@ module QA
end
end
it "publishes a nuget package at the project level, installs and deletes it at the group level using a #{params[:token_name]}", testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1073' do
it "publishes a nuget package at the project level, installs and deletes it at the group level using a #{params[:token_name]}" do
Flow::Login.sign_in
Resource::Repository::Commit.fabricate_via_api! do |commit|

View file

@ -356,7 +356,7 @@ RSpec.describe Projects::BranchesController do
context "valid branch name with encoded slashes" do
let(:branch) { "improve%2Fawesome" }
it { expect(response).to have_gitlab_http_status(:ok) }
it { expect(response).to have_gitlab_http_status(:not_found) }
it { expect(response.body).to be_blank }
end
@ -396,10 +396,10 @@ RSpec.describe Projects::BranchesController do
let(:branch) { 'improve%2Fawesome' }
it 'returns JSON response with message' do
expect(json_response).to eql('message' => 'Branch was deleted')
expect(json_response).to eql('message' => 'No such branch')
end
it { expect(response).to have_gitlab_http_status(:ok) }
it { expect(response).to have_gitlab_http_status(:not_found) }
end
context 'invalid branch name, valid ref' do

View file

@ -323,6 +323,34 @@ RSpec.describe ProjectsController do
expect(response).to render_template('_files')
expect(response.body).to have_content('LICENSE') # would be 'MIT license' if stub not works
end
describe "PUC highlighting" do
render_views
before do
expect(controller).to receive(:find_routable!).and_return(public_project)
end
context "option is enabled" do
it "adds the highlighting class" do
expect(public_project).to receive(:warn_about_potentially_unwanted_characters?).and_return(true)
get_show
expect(response.body).to have_css(".project-highlight-puc")
end
end
context "option is disabled" do
it "doesn't add the highlighting class" do
expect(public_project).to receive(:warn_about_potentially_unwanted_characters?).and_return(false)
get_show
expect(response.body).not_to have_css(".project-highlight-puc")
end
end
end
end
context "when the url contains .atom" do

View file

@ -15,6 +15,10 @@ FactoryBot.define do
admin { true }
end
trait :public_email do
public_email { email }
end
trait :blocked do
after(:build) { |user, _| user.block! }
end

View file

@ -22,12 +22,16 @@ const safeUrls = {
const unsafeUrls = [
'/an/evil/url',
'../../../evil/url',
'https://evil.url/assets/icons-123a.svg',
'https://evil.url/assets/icons-123a.svg#test',
'https://evil.url/assets/icons-456b.svg',
`https://evil.url/${rootGon.sprite_icons}`,
`https://evil.url/${rootGon.sprite_file_icons}`,
`https://evil.url/${absoluteGon.sprite_icons}`,
`https://evil.url/${absoluteGon.sprite_file_icons}`,
`${rootGon.sprite_icons}/../evil/path`,
`${rootGon.sprite_file_icons}/../../evil/path`,
`${absoluteGon.sprite_icons}/../evil/path`,
`${absoluteGon.sprite_file_icons}/../../https://evil.url`,
];
const forbiddenDataAttrs = ['data-remote', 'data-url', 'data-type', 'data-method'];

View file

@ -607,6 +607,27 @@ describe('URL utility', () => {
});
});
describe('getNormalizedURL', () => {
it.each`
url | base | result
${'./foo'} | ${''} | ${'http://test.host/foo'}
${'../john.md'} | ${''} | ${'http://test.host/john.md'}
${'/images/img.png'} | ${'https://gitlab.com'} | ${'https://gitlab.com/images/img.png'}
${'/images/../img.png'} | ${'https://gitlab.com'} | ${'https://gitlab.com/img.png'}
${'/images/./img.png'} | ${'https://gitlab.com'} | ${'https://gitlab.com/images/img.png'}
${'./images/img.png'} | ${'https://gitlab.com/user/project'} | ${'https://gitlab.com/user/images/img.png'}
${'../images/../img.png'} | ${'https://gitlab.com/user/project'} | ${'https://gitlab.com/img.png'}
${'/images/img.png'} | ${'https://gitlab.com/user/project'} | ${'https://gitlab.com/images/img.png'}
${'/images/../img.png'} | ${'https://gitlab.com/user/project'} | ${'https://gitlab.com/img.png'}
${'/images/./img.png'} | ${'https://gitlab.com/user/project'} | ${'https://gitlab.com/images/img.png'}
`(
'converts url "$url" with base "$base" to normalized url => "expected"',
({ url, base, result }) => {
expect(urlUtils.getNormalizedURL(url, base)).toBe(result);
},
);
});
describe('getWebSocketProtocol', () => {
it.each`
protocol | expectation

View file

@ -0,0 +1,70 @@
import { GlKeysetPagination } from '@gitlab/ui';
import { stripTypenames } from 'helpers/graphql_helpers';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ManifestRow from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue';
import Component from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue';
import {
proxyManifests,
pagination,
} from 'jest/packages_and_registries/dependency_proxy/mock_data';
describe('Manifests List', () => {
let wrapper;
const defaultProps = {
manifests: proxyManifests(),
pagination: stripTypenames(pagination()),
};
const createComponent = (propsData = defaultProps) => {
wrapper = shallowMountExtended(Component, {
propsData,
});
};
const findRows = () => wrapper.findAllComponents(ManifestRow);
const findPagination = () => wrapper.findComponent(GlKeysetPagination);
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('has the correct title', () => {
expect(wrapper.text()).toContain(Component.i18n.listTitle);
});
it('shows a row for every manifest', () => {
expect(findRows().length).toBe(defaultProps.manifests.length);
});
it('binds a manifest to each row', () => {
expect(findRows().at(0).props()).toMatchObject({
manifest: defaultProps.manifests[0],
});
});
describe('pagination', () => {
it('has the correct props', () => {
expect(findPagination().props()).toMatchObject({
...defaultProps.pagination,
});
});
it('emits the next-page event', () => {
findPagination().vm.$emit('next');
expect(wrapper.emitted('next-page')).toEqual([[]]);
});
it('emits the prev-page event', () => {
findPagination().vm.$emit('prev');
expect(wrapper.emitted('prev-page')).toEqual([[]]);
});
});
});

View file

@ -0,0 +1,59 @@
import { GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import Component from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue';
import { proxyManifests } from 'jest/packages_and_registries/dependency_proxy/mock_data';
describe('Manifest Row', () => {
let wrapper;
const defaultProps = {
manifest: proxyManifests()[0],
};
const createComponent = (propsData = defaultProps) => {
wrapper = shallowMountExtended(Component, {
propsData,
stubs: {
GlSprintf,
TimeagoTooltip,
ListItem,
},
});
};
const findListItem = () => wrapper.findComponent(ListItem);
const findCachedMessages = () => wrapper.findByTestId('cached-message');
const findTimeAgoTooltip = () => wrapper.findComponent(TimeagoTooltip);
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('has a list item', () => {
expect(findListItem().exists()).toBe(true);
});
it('displays the name', () => {
expect(wrapper.text()).toContain('alpine');
});
it('displays the version', () => {
expect(wrapper.text()).toContain('latest');
});
it('displays the cached time', () => {
expect(findCachedMessages().text()).toContain('Cached');
});
it('has a time ago tooltip component', () => {
expect(findTimeAgoTooltip().props()).toMatchObject({
time: defaultProps.manifest.createdAt,
});
});
});

View file

@ -7,6 +7,20 @@ export const proxyData = () => ({
export const proxySettings = (extend = {}) => ({ enabled: true, ...extend });
export const proxyManifests = () => [
{ createdAt: '2021-09-22T09:45:28Z', imageName: 'alpine:latest' },
{ createdAt: '2021-09-21T09:45:28Z', imageName: 'alpine:stable' },
];
export const pagination = (extend) => ({
endCursor: 'eyJpZCI6IjIwNSIsIm5hbWUiOiJteS9jb21wYW55L2FwcC9teS1hcHAifQ',
hasNextPage: true,
hasPreviousPage: true,
startCursor: 'eyJpZCI6IjI0NyIsIm5hbWUiOiJ2ZXJzaW9uX3Rlc3QxIn0',
__typename: 'PageInfo',
...extend,
});
export const proxyDetailsQuery = ({ extendSettings = {} } = {}) => ({
data: {
group: {
@ -16,6 +30,10 @@ export const proxyDetailsQuery = ({ extendSettings = {} } = {}) => ({
...proxySettings(extendSettings),
__typename: 'DependencyProxySetting',
},
dependencyProxyManifests: {
nodes: proxyManifests(),
pageInfo: pagination(),
},
},
},
});

View file

@ -27,6 +27,7 @@ const defaultProps = {
emailsDisabled: false,
packagesEnabled: true,
showDefaultAwardEmojis: true,
warnAboutPotentiallyUnwantedCharacters: true,
},
isGitlabCom: true,
canDisableEmails: true,
@ -97,6 +98,10 @@ describe('Settings Panel', () => {
const findEmailSettings = () => wrapper.find({ ref: 'email-settings' });
const findShowDefaultAwardEmojis = () =>
wrapper.find('input[name="project[project_setting_attributes][show_default_award_emojis]"]');
const findWarnAboutPuc = () =>
wrapper.find(
'input[name="project[project_setting_attributes][warn_about_potentially_unwanted_characters]"]',
);
const findMetricsVisibilitySettings = () => wrapper.find({ ref: 'metrics-visibility-settings' });
const findOperationsSettings = () => wrapper.find({ ref: 'operations-settings' });
@ -539,6 +544,14 @@ describe('Settings Panel', () => {
});
});
describe('Warn about potentially unwanted characters', () => {
it('should have a "Warn about Potentially Unwanted Characters" input', () => {
wrapper = mountComponent();
expect(findWarnAboutPuc().exists()).toBe(true);
});
});
describe('Metrics dashboard', () => {
it('should show the metrics dashboard access toggle', () => {
wrapper = mountComponent();

View file

@ -7,6 +7,7 @@ RSpec.describe Mutations::Discussions::ToggleResolve do
described_class.new(object: nil, context: { current_user: user }, field: nil)
end
let_it_be(:author) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
describe '#resolve' do
@ -136,20 +137,37 @@ RSpec.describe Mutations::Discussions::ToggleResolve do
end
end
end
context 'when user is the author and discussion is locked' do
let(:user) { author }
before do
issuable.update!(discussion_locked: true)
end
it 'raises an error' do
expect { mutation.resolve(id: id_arg, resolve: resolve_arg) }.to raise_error(
Gitlab::Graphql::Errors::ResourceNotAvailable,
"The resource that you are attempting to access does not exist or you don't have permission to perform this action"
)
end
end
end
context 'when discussion is on a merge request' do
let_it_be(:noteable) { create(:merge_request, source_project: project) }
let_it_be(:noteable) { create(:merge_request, source_project: project, author: author) }
let(:discussion) { create(:diff_note_on_merge_request, noteable: noteable, project: project).to_discussion }
let(:issuable) { noteable }
it_behaves_like 'a working resolve method'
end
context 'when discussion is on a design' do
let_it_be(:noteable) { create(:design, :with_file, issue: create(:issue, project: project)) }
let_it_be(:noteable) { create(:design, :with_file, issue: create(:issue, project: project, author: author)) }
let(:discussion) { create(:diff_note_on_design, noteable: noteable, project: project).to_discussion }
let(:issuable) { noteable.issue }
it_behaves_like 'a working resolve method'
end

View file

@ -3,26 +3,58 @@
require 'spec_helper'
RSpec.describe Mutations::Issues::SetSeverity do
let_it_be(:user) { create(:user) }
let_it_be(:issue) { create(:incident) }
let_it_be(:project) { create(:project) }
let_it_be(:guest) { create(:user) }
let_it_be(:reporter) { create(:user) }
let_it_be(:issue) { create(:incident, project: project) }
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
specify { expect(described_class).to require_graphql_authorizations(:update_issue) }
specify { expect(described_class).to require_graphql_authorizations(:update_issue, :admin_issue) }
before_all do
project.add_guest(guest)
project.add_reporter(reporter)
end
describe '#resolve' do
let(:severity) { 'critical' }
let(:mutated_incident) { subject[:issue] }
subject(:resolve) { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, severity: severity) }
subject(:resolve) do
mutation.resolve(
project_path: issue.project.full_path,
iid: issue.iid,
severity: severity
)
end
it_behaves_like 'permission level for issue mutation is correctly verified'
context 'as guest' do
let(:user) { guest }
context 'when the user can update the issue' do
before do
issue.project.add_developer(user)
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
context 'and also author' do
let!(:issue) { create(:incident, project: project, author: user) }
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'and also assignee' do
let!(:issue) { create(:incident, project: project, assignee_ids: [user.id]) }
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
context 'as reporter' do
let(:user) { reporter }
context 'when issue type is incident' do
context 'when severity has a correct value' do
it 'updates severity' do
@ -48,9 +80,9 @@ RSpec.describe Mutations::Issues::SetSeverity do
end
context 'when issue type is not incident' do
let!(:issue) { create(:issue) }
let!(:issue) { create(:issue, project: project) }
it 'does not updates the issue' do
it 'does not update the issue' do
expect { resolve }.not_to change { issue.updated_at }
end
end

View file

@ -11,9 +11,6 @@ RSpec.describe LearnGitlabHelper do
let_it_be(:namespace) { project.namespace }
before do
project.add_developer(user)
allow(helper).to receive(:user).and_return(user)
allow_next_instance_of(LearnGitlab::Project) do |learn_gitlab|
allow(learn_gitlab).to receive(:project).and_return(project)
end
@ -22,38 +19,115 @@ RSpec.describe LearnGitlabHelper do
OnboardingProgress.register(namespace, :git_write)
end
describe '.onboarding_actions_data' do
describe '#onboarding_actions_data' do
subject(:onboarding_actions_data) { helper.onboarding_actions_data(project) }
it 'has all actions' do
expect(onboarding_actions_data.keys).to contain_exactly(
:issue_created,
:git_write,
:pipeline_created,
:merge_request_created,
:user_added,
:trial_started,
:required_mr_approvals_enabled,
:code_owners_enabled,
:security_scan_enabled
)
shared_examples 'has all actions' do
it 'has all actions' do
expect(onboarding_actions_data.keys).to contain_exactly(
:issue_created,
:git_write,
:pipeline_created,
:merge_request_created,
:user_added,
:trial_started,
:required_mr_approvals_enabled,
:code_owners_enabled,
:security_scan_enabled
)
end
end
it 'sets correct path and completion status' do
expect(onboarding_actions_data[:git_write]).to eq({
url: project_issue_url(project, LearnGitlab::Onboarding::ACTION_ISSUE_IDS[:git_write]),
completed: true,
svg: helper.image_path("learn_gitlab/git_write.svg")
it_behaves_like 'has all actions'
it 'sets correct paths' do
expect(onboarding_actions_data).to match({
trial_started: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/2\z})
),
issue_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/4\z})
),
git_write: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/6\z})
),
pipeline_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/7\z})
),
user_added: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/8\z})
),
merge_request_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/9\z})
),
code_owners_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/10\z})
),
required_mr_approvals_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/11\z})
),
security_scan_enabled: a_hash_including(
url: a_string_matching(%r{docs\.gitlab\.com/ee/user/application_security/security_dashboard/#gitlab-security-dashboard-security-center-and-vulnerability-reports\z})
)
})
expect(onboarding_actions_data[:pipeline_created]).to eq({
url: project_issue_url(project, LearnGitlab::Onboarding::ACTION_ISSUE_IDS[:pipeline_created]),
completed: false,
svg: helper.image_path("learn_gitlab/pipeline_created.svg")
end
it 'sets correct completion statuses' do
expect(onboarding_actions_data).to match({
issue_created: a_hash_including(completed: false),
git_write: a_hash_including(completed: true),
pipeline_created: a_hash_including(completed: false),
merge_request_created: a_hash_including(completed: false),
user_added: a_hash_including(completed: false),
trial_started: a_hash_including(completed: false),
required_mr_approvals_enabled: a_hash_including(completed: false),
code_owners_enabled: a_hash_including(completed: false),
security_scan_enabled: a_hash_including(completed: false)
})
end
context 'when in the new action URLs experiment' do
before do
stub_experiments(change_continuous_onboarding_link_urls: :candidate)
end
it_behaves_like 'has all actions'
it 'sets mostly new paths' do
expect(onboarding_actions_data).to match({
trial_started: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/2\z})
),
issue_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues\z})
),
git_write: a_hash_including(
url: a_string_matching(%r{/learn_gitlab\z})
),
pipeline_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/pipelines\z})
),
user_added: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/project_members\z})
),
merge_request_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/merge_requests\z})
),
code_owners_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/10\z})
),
required_mr_approvals_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/11\z})
),
security_scan_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/security/configuration\z})
)
})
end
end
end
describe '.learn_gitlab_enabled?' do
describe '#learn_gitlab_enabled?' do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user) }
@ -89,7 +163,7 @@ RSpec.describe LearnGitlabHelper do
end
end
describe '.onboarding_sections_data' do
describe '#onboarding_sections_data' do
subject(:sections) { helper.onboarding_sections_data }
it 'has the right keys' do

View file

@ -961,4 +961,26 @@ RSpec.describe ProjectsHelper do
)
end
end
describe '#project_classes' do
subject { helper.project_classes(project) }
it { is_expected.to be_a(String) }
context 'PUC highlighting enabled' do
before do
project.warn_about_potentially_unwanted_characters = true
end
it { is_expected.to include('project-highlight-puc') }
end
context 'PUC highlighting disabled' do
before do
project.warn_about_potentially_unwanted_characters = false
end
it { is_expected.not_to include('project-highlight-puc') }
end
end
end

View file

@ -0,0 +1,39 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::API::Entities::Project do
let(:project) { create(:project, :public) }
let(:current_user) { create(:user) }
let(:options) { { current_user: current_user } }
let(:entity) do
::API::Entities::Project.new(project, options)
end
subject(:json) { entity.as_json }
describe '.shared_with_groups' do
let(:group) { create(:group, :private) }
before do
project.project_group_links.create!(group: group)
end
context 'when the current user does not have access to the group' do
it 'is empty' do
expect(json[:shared_with_groups]).to be_empty
end
end
context 'when the current user has access to the group' do
before do
group.add_guest(current_user)
end
it 'contains information about the shared group' do
expect(json[:shared_with_groups]).to contain_exactly(include(group_id: group.id))
end
end
end
end

View file

@ -84,6 +84,24 @@ RSpec.describe Banzai::Renderer do
end
end
describe '#cacheless_render' do
context 'without cache' do
let(:object) { fake_object(fresh: false) }
let(:histogram) { double('prometheus histogram') }
it 'returns cacheless render field' do
allow(renderer).to receive(:render_result).and_return(output: 'test')
allow(renderer).to receive(:real_duration_histogram).and_return(histogram)
allow(renderer).to receive(:cpu_duration_histogram).and_return(histogram)
expect(renderer).to receive(:render_result).with('test', {})
expect(histogram).to receive(:observe).twice
renderer.cacheless_render('test')
end
end
end
describe '#post_process' do
let(:context_options) { {} }
let(:html) { 'Consequatur aperiam et nesciunt modi aut assumenda quo id. '}

View file

@ -0,0 +1,297 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BulkImports::Projects::Pipelines::MergeRequestsPipeline do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
let_it_be(:entity) do
create(
:bulk_import_entity,
:project_entity,
project: project,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
destination_name: 'My Destination Project',
destination_namespace: group.full_path
)
end
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:mr) do
{
'iid' => 7,
'author_id' => 22,
'source_project_id' => 1234,
'target_project_id' => 1234,
'title' => 'Imported MR',
'description' => 'Description',
'state' => 'opened',
'source_branch' => 'feature',
'target_branch' => 'main',
'source_branch_sha' => 'ABCD',
'target_branch_sha' => 'DCBA',
'created_at' => '2020-06-14T15:02:47.967Z',
'updated_at' => '2020-06-14T15:03:47.967Z',
'merge_request_diff' => {
'state' => 'collected',
'base_commit_sha' => 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f',
'head_commit_sha' => 'a97f74ddaa848b707bea65441c903ae4bf5d844d',
'start_commit_sha' => '9eea46b5c72ead701c22f516474b95049c9d9462',
'merge_request_diff_commits' => [
{
'sha' => 'COMMIT1',
'relative_order' => 0,
'message' => 'commit message',
'authored_date' => '2014-08-06T08:35:52.000+02:00',
'committed_date' => '2014-08-06T08:35:52.000+02:00',
'commit_author' => {
'name' => 'Commit Author',
'email' => 'gitlab@example.com'
},
'committer' => {
'name' => 'Committer',
'email' => 'committer@example.com'
}
}
],
'merge_request_diff_files' => [
{
'relative_order' => 0,
'utf8_diff' => '--- a/.gitignore\n+++ b/.gitignore\n@@ -1 +1 @@ test\n',
'new_path' => '.gitignore',
'old_path' => '.gitignore',
'a_mode' => '100644',
'b_mode' => '100644',
'new_file' => false,
'renamed_file' => false,
'deleted_file' => false,
'too_large' => false
}
]
}
}.merge(attributes)
end
let(:attributes) { {} }
let(:imported_mr) { project.merge_requests.find_by_title(mr['title']) }
subject(:pipeline) { described_class.new(context) }
describe '#run' do
before do
group.add_owner(user)
allow_next_instance_of(BulkImports::Common::Extractors::NdjsonExtractor) do |extractor|
allow(extractor).to receive(:remove_tmp_dir)
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [[mr, 0]]))
end
allow(project.repository).to receive(:fetch_source_branch!).and_return(true)
allow(project.repository).to receive(:branch_exists?).and_return(false)
allow(project.repository).to receive(:create_branch)
pipeline.run
end
it 'imports a merge request' do
expect(project.merge_requests.count).to eq(1)
expect(imported_mr.title).to eq(mr['title'])
expect(imported_mr.description).to eq(mr['description'])
expect(imported_mr.state).to eq(mr['state'])
expect(imported_mr.iid).to eq(mr['iid'])
expect(imported_mr.created_at).to eq(mr['created_at'])
expect(imported_mr.updated_at).to eq(mr['updated_at'])
expect(imported_mr.author).to eq(user)
end
context 'merge request state' do
context 'when mr is closed' do
let(:attributes) { { 'state' => 'closed' } }
it 'imported mr as closed' do
expect(imported_mr.state).to eq(attributes['state'])
end
end
context 'when mr is merged' do
let(:attributes) { { 'state' => 'merged' } }
it 'imported mr as merged' do
expect(imported_mr.state).to eq(attributes['state'])
end
end
end
context 'source & target project' do
it 'has the new project as target' do
expect(imported_mr.target_project).to eq(project)
end
it 'has the new project as source' do
expect(imported_mr.source_project).to eq(project)
end
context 'when source/target projects differ' do
let(:attributes) { { 'source_project_id' => 4321 } }
it 'has no source' do
expect(imported_mr.source_project).to be_nil
end
context 'when diff_head_sha is present' do
let(:attributes) { { 'diff_head_sha' => 'HEAD', 'source_project_id' => 4321 } }
it 'has the new project as source' do
expect(imported_mr.source_project).to eq(project)
end
end
end
end
context 'resource label events' do
let(:attributes) { { 'resource_label_events' => [{ 'action' => 'add', 'user_id' => 1 }] } }
it 'restores resource label events' do
expect(imported_mr.resource_label_events.first.action).to eq('add')
end
end
context 'award emoji' do
let(:attributes) { { 'award_emoji' => [{ 'name' => 'tada', 'user_id' => 22 }] } }
it 'has award emoji' do
expect(imported_mr.award_emoji.first.name).to eq(attributes['award_emoji'].first['name'])
end
end
context 'notes' do
let(:note) { imported_mr.notes.first }
let(:attributes) do
{
'notes' => [
{
'note' => 'Issue note',
'note_html' => '<p>something else entirely</p>',
'cached_markdown_version' => 917504,
'author_id' => 22,
'author' => { 'name' => 'User 22' },
'created_at' => '2016-06-14T15:02:56.632Z',
'updated_at' => '2016-06-14T15:02:47.770Z',
'award_emoji' => [{ 'name' => 'clapper', 'user_id' => 22 }]
}
]
}
end
it 'imports mr note' do
expect(note).to be_present
expect(note.note).to include('By User 22')
expect(note.note).to include(attributes['notes'].first['note'])
expect(note.author).to eq(user)
end
it 'has award emoji' do
emoji = note.award_emoji.first
expect(emoji.name).to eq('clapper')
expect(emoji.user).to eq(user)
end
it 'does not import note_html' do
expect(note.note_html).to match(attributes['notes'].first['note'])
expect(note.note_html).not_to match(attributes['notes'].first['note_html'])
end
end
context 'system note metadata' do
let(:attributes) do
{
'notes' => [
{
'note' => 'added 3 commits',
'system' => true,
'author_id' => 22,
'author' => { 'name' => 'User 22' },
'created_at' => '2016-06-14T15:02:56.632Z',
'updated_at' => '2016-06-14T15:02:47.770Z',
'system_note_metadata' => { 'action' => 'commit', 'commit_count' => 3 }
}
]
}
end
it 'restores system note metadata' do
note = imported_mr.notes.first
expect(note.system).to eq(true)
expect(note.noteable_type).to eq('MergeRequest')
expect(note.system_note_metadata.action).to eq('commit')
expect(note.system_note_metadata.commit_count).to eq(3)
end
end
context 'diffs' do
it 'imports merge request diff' do
expect(imported_mr.merge_request_diff).to be_present
end
it 'has the correct data for merge request latest_merge_request_diff' do
expect(imported_mr.latest_merge_request_diff_id).to eq(imported_mr.merge_request_diffs.maximum(:id))
end
it 'imports diff files' do
expect(imported_mr.merge_request_diff.merge_request_diff_files.count).to eq(1)
end
context 'diff commits' do
it 'imports diff commits' do
expect(imported_mr.merge_request_diff.merge_request_diff_commits.count).to eq(1)
end
it 'assigns committer and author details to diff commits' do
commit = imported_mr.merge_request_diff.merge_request_diff_commits.first
expect(commit.commit_author_id).not_to be_nil
expect(commit.committer_id).not_to be_nil
end
it 'assigns the correct commit users to diff commits' do
commit = MergeRequestDiffCommit.find_by(sha: 'COMMIT1')
expect(commit.commit_author.name).to eq('Commit Author')
expect(commit.commit_author.email).to eq('gitlab@example.com')
expect(commit.committer.name).to eq('Committer')
expect(commit.committer.email).to eq('committer@example.com')
end
end
end
context 'labels' do
let(:attributes) do
{
'label_links' => [
{ 'label' => { 'title' => 'imported label 1', 'type' => 'ProjectLabel' } },
{ 'label' => { 'title' => 'imported label 2', 'type' => 'ProjectLabel' } }
]
}
end
it 'imports labels' do
expect(imported_mr.labels.pluck(:title)).to contain_exactly('imported label 1', 'imported label 2')
end
end
context 'milestone' do
let(:attributes) { { 'milestone' => { 'title' => 'imported milestone' } } }
it 'imports milestone' do
expect(imported_mr.milestone.title).to eq(attributes.dig('milestone', 'title'))
end
end
end
end

View file

@ -3,71 +3,72 @@
require 'spec_helper'
RSpec.describe BulkImports::Projects::Pipelines::RepositoryPipeline do
describe '#run' do
let_it_be(:user) { create(:user) }
let_it_be(:parent) { create(:project) }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
let_it_be(:bulk_import_configuration) { create(:bulk_import_configuration, bulk_import: bulk_import) }
let_it_be(:user) { create(:user) }
let_it_be(:parent) { create(:project) }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
let_it_be(:bulk_import_configuration) { create(:bulk_import_configuration, bulk_import: bulk_import) }
let_it_be(:entity) do
create(
:bulk_import_entity,
:project_entity,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
destination_name: 'My Destination Repository',
destination_namespace: parent.full_path,
project: parent
)
let_it_be(:entity) do
create(
:bulk_import_entity,
:project_entity,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
destination_name: 'My Destination Repository',
destination_namespace: parent.full_path,
project: parent
)
end
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:extracted_data) { BulkImports::Pipeline::ExtractedData.new(data: project_data) }
subject(:pipeline) { described_class.new(context) }
before do
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
allow(extractor).to receive(:extract).and_return(extracted_data)
end
end
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
describe '#run' do
context 'successfully imports repository' do
let(:project_data) do
{
'httpUrlToRepo' => 'http://test.git'
}
end
subject { described_class.new(context) }
let(:project_data) { { 'httpUrlToRepo' => 'http://test.git' } }
it 'imports new repository into destination project' do
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: project_data))
end
url = project_data['httpUrlToRepo'].sub("://", "://oauth2:#{bulk_import_configuration.access_token}@")
expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
url = project_data['httpUrlToRepo'].sub("://", "://oauth2:#{bulk_import_configuration.access_token}@")
expect(repository_service).to receive(:import_repository).with(url).and_return 0
end
expect(context.portable).to receive(:ensure_repository)
expect(context.portable.repository).to receive(:fetch_as_mirror).with(url)
subject.run
pipeline.run
end
end
context 'blocked local networks' do
let(:project_data) do
{
'httpUrlToRepo' => 'http://localhost/foo.git'
}
end
before do
allow(Gitlab.config.gitlab).to receive(:host).and_return('notlocalhost.gitlab.com')
allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(false)
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: project_data))
end
end
subject { described_class.new(context) }
let(:project_data) { { 'httpUrlToRepo' => 'http://localhost/foo.git' } }
it 'imports new repository into destination project' do
subject.run
expect(context.entity.failed?).to be_truthy
allow(Gitlab.config.gitlab).to receive(:host).and_return('notlocalhost.gitlab.com')
allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(false)
pipeline.run
expect(context.entity.failed?).to eq(true)
end
end
end
describe '#after_run' do
it 'executes housekeeping service after import' do
service = instance_double(Repositories::HousekeepingService)
expect(Repositories::HousekeepingService).to receive(:new).with(context.portable, :gc).and_return(service)
expect(service).to receive(:execute)
pipeline.after_run(context)
end
end
end

View file

@ -10,6 +10,7 @@ RSpec.describe BulkImports::Projects::Stage do
[2, BulkImports::Common::Pipelines::LabelsPipeline],
[3, BulkImports::Projects::Pipelines::IssuesPipeline],
[4, BulkImports::Common::Pipelines::BoardsPipeline],
[4, BulkImports::Projects::Pipelines::MergeRequestsPipeline],
[5, BulkImports::Common::Pipelines::UploadsPipeline],
[6, BulkImports::Common::Pipelines::EntityFinisher]
]

View file

@ -280,7 +280,7 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
end
context 'metrics' do
let(:histogram) { double(:histogram) }
let(:histogram) { double(:histogram).as_null_object }
let(:counter) { double('counter', increment: true) }
before do
@ -315,7 +315,6 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
)
expect(counter).to receive(:increment)
allow(histogram).to receive(:observe).with({ importer: :bitbucket_server_importer }, anything)
subject.execute
end

View file

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::DataBuilder::Build do
let!(:tag_names) { %w(tag-1 tag-2) }
let(:runner) { create(:ci_runner, :instance, tag_list: tag_names.map { |n| ActsAsTaggableOn::Tag.create!(name: n)}) }
let(:user) { create(:user) }
let(:user) { create(:user, :public_email) }
let(:build) { create(:ci_build, :running, runner: runner, user: user) }
describe '.build' do

View file

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::DataBuilder::Pipeline do
let_it_be(:user) { create(:user) }
let_it_be(:user) { create(:user, :public_email) }
let_it_be(:project) { create(:project, :repository) }
let_it_be_with_reload(:pipeline) do
@ -46,7 +46,7 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
name: user.name,
username: user.username,
avatar_url: user.avatar_url(only_path: false),
email: user.email
email: user.public_email
})
end

View file

@ -267,6 +267,35 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory, :use_clean_rails_
end
end
context 'pipeline_schedule' do
let(:relation_sym) { :pipeline_schedules }
let(:relation_hash) do
{
"id": 3,
"created_at": "2016-07-22T08:55:44.161Z",
"updated_at": "2016-07-22T08:55:44.161Z",
"description": "pipeline schedule",
"ref": "main",
"cron": "0 4 * * 0",
"cron_timezone": "UTC",
"active": value,
"project_id": project.id
}
end
subject { created_object.active }
[true, false].each do |v|
context "when relation_hash has active set to #{v}" do
let(:value) { v }
it "the created object is not active" do
expect(created_object.active).to eq(false)
end
end
end
end
# `project_id`, `described_class.USER_REFERENCES`, noteable_id, target_id, and some project IDs are already
# re-assigned by described_class.
context 'Potentially hazardous foreign keys' do

View file

@ -375,7 +375,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
expect(pipeline_schedule.ref).to eq('master')
expect(pipeline_schedule.cron).to eq('0 4 * * 0')
expect(pipeline_schedule.cron_timezone).to eq('UTC')
expect(pipeline_schedule.active).to eq(true)
expect(pipeline_schedule.active).to eq(false)
end
end

View file

@ -556,7 +556,6 @@ Project:
- merge_requests_rebase_enabled
- jobs_cache_index
- external_authorization_classification_label
- external_webhook_token
- pages_https_only
- merge_requests_disable_committers_approval
- require_password_to_approve

View file

@ -0,0 +1,33 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Gitlab::Unicode do
describe described_class::BIDI_REGEXP do
using RSpec::Parameterized::TableSyntax
where(:bidi_string, :match) do
"\u2066" | true # left-to-right isolate
"\u2067" | true # right-to-left isolate
"\u2068" | true # first strong isolate
"\u2069" | true # pop directional isolate
"\u202a" | true # left-to-right embedding
"\u202b" | true # right-to-left embedding
"\u202c" | true # pop directional formatting
"\u202d" | true # left-to-right override
"\u202e" | true # right-to-left override
"\u2066foobar" | true
"" | false
"foo" | false
"\u2713" | false # checkmark
end
with_them do
let(:utf8_string) { bidi_string.encode("utf-8") }
it "matches only the bidi characters" do
expect(utf8_string.match?(subject)).to eq(match)
end
end
end
end

View file

@ -36,5 +36,26 @@ RSpec.describe Rouge::Formatters::HTMLGitlab do
is_expected.to eq(code)
end
end
context 'when unicode control characters are used' do
let(:lang) { 'javascript' }
let(:tokens) { lexer.lex(code, continue: false) }
let(:code) do
<<~JS
#!/usr/bin/env node
var accessLevel = "user";
if (accessLevel != "user // Check if admin ") {
console.log("You are an admin.");
}
JS
end
it 'highlights the control characters' do
message = "Potentially unwanted character detected: Unicode BiDi Control"
is_expected.to include(%{<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{message}">}).exactly(4).times
end
end
end
end

View file

@ -121,4 +121,16 @@ RSpec.describe Ci::BuildMetadata do
end
end
end
describe 'set_cancel_gracefully' do
it 'sets cancel_gracefully' do
build.set_cancel_gracefully
expect(build.cancel_gracefully?).to be true
end
it 'returns false' do
expect(build.cancel_gracefully?).to be false
end
end
end

View file

@ -35,6 +35,8 @@ RSpec.describe Ci::Build do
it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) }
it { is_expected.to respond_to(:set_cancel_gracefully) }
it { is_expected.to respond_to(:cancel_gracefully?) }
it { is_expected.to delegate_method(:merge_request?).to(:pipeline) }
it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) }
@ -5386,4 +5388,23 @@ RSpec.describe Ci::Build do
create(:ci_build)
end
end
describe '#runner_features' do
subject do
build.save!
build.cancel_gracefully?
end
let_it_be(:build) { create(:ci_build, pipeline: pipeline) }
it 'cannot cancel gracefully' do
expect(subject).to be false
end
it 'can cancel gracefully' do
build.set_cancel_gracefully
expect(subject).to be true
end
end
end

View file

@ -7,7 +7,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
include StubRequests
include Ci::SourcePipelineHelpers
let_it_be(:user) { create(:user) }
let_it_be(:user) { create(:user, :public_email) }
let_it_be(:namespace) { create_default(:namespace).freeze }
let_it_be(:project) { create_default(:project, :repository).freeze }

View file

@ -188,6 +188,16 @@ RSpec.describe Discussion, ResolvableDiscussion do
it "returns true" do
expect(subject.can_resolve?(current_user)).to be true
end
context 'when noteable is locked' do
before do
allow(subject.noteable).to receive(:discussion_locked?).and_return(true)
end
it 'returns false' do
expect(subject.can_resolve?(current_user)).to be_falsey
end
end
end
context "when the signed in user can push to the project" do
@ -200,8 +210,11 @@ RSpec.describe Discussion, ResolvableDiscussion do
end
context "when the noteable has no author" do
before do
subject.noteable.author = nil
end
it "returns true" do
expect(noteable).to receive(:author).and_return(nil)
expect(subject.can_resolve?(current_user)).to be true
end
end
@ -213,8 +226,11 @@ RSpec.describe Discussion, ResolvableDiscussion do
end
context "when the noteable has no author" do
before do
subject.noteable.author = nil
end
it "returns false" do
expect(noteable).to receive(:author).and_return(nil)
expect(subject.can_resolve?(current_user)).to be false
end
end

View file

@ -1341,6 +1341,7 @@ RSpec.describe Namespace do
context 'refreshing project access on updating share_with_group_lock' do
let(:group) { create(:group, share_with_group_lock: false) }
let(:project) { create(:project, :private, group: group) }
let(:another_project) { create(:project, :private, group: group) }
let_it_be(:shared_with_group_one) { create(:group) }
let_it_be(:shared_with_group_two) { create(:group) }
@ -1353,6 +1354,7 @@ RSpec.describe Namespace do
shared_with_group_one.add_developer(group_one_user)
shared_with_group_two.add_developer(group_two_user)
create(:project_group_link, group: shared_with_group_one, project: project)
create(:project_group_link, group: shared_with_group_one, project: another_project)
create(:project_group_link, group: shared_with_group_two, project: project)
end
@ -1360,6 +1362,9 @@ RSpec.describe Namespace do
expect(AuthorizedProjectUpdate::ProjectRecalculateWorker)
.to receive(:perform_async).with(project.id).once
expect(AuthorizedProjectUpdate::ProjectRecalculateWorker)
.to receive(:perform_async).with(another_project.id).once
execute_update
end
@ -1392,11 +1397,23 @@ RSpec.describe Namespace do
stub_feature_flags(specialized_worker_for_group_lock_update_auth_recalculation: false)
end
it 'refreshes the permissions of the members of the old and new namespace' do
it 'updates authorizations leading to users from shared groups losing access', :sidekiq_inline do
expect { execute_update }
.to change { group_one_user.authorized_projects.include?(project) }.from(true).to(false)
.and change { group_two_user.authorized_projects.include?(project) }.from(true).to(false)
end
it 'updates the authorizations in a non-blocking manner' do
expect(AuthorizedProjectsWorker).to(
receive(:bulk_perform_async)
.with([[group_one_user.id]])).once
expect(AuthorizedProjectsWorker).to(
receive(:bulk_perform_async)
.with([[group_two_user.id]])).once
execute_update
end
end
end

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