summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-10-28 18:14:18 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-28 18:14:18 +0000
commitb267f3a3acf3fe4cf2a9bb254d7abeb3b2823b21 (patch)
treea1c30c9a460a1c990311d47aae72d00368e1a1c1 /app
parent03cd4f8da4f848c7dfd0c0b88b4c095f69e56bb0 (diff)
downloadgitlab-ce-b267f3a3acf3fe4cf2a9bb254d7abeb3b2823b21.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/lib/dompurify.js19
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js18
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue22
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue49
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue46
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js1
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql19
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue17
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue22
-rw-r--r--app/assets/javascripts/snippets/components/show.vue8
-rw-r--r--app/assets/stylesheets/framework/highlight.scss6
-rw-r--r--app/controllers/jira_connect/application_controller.rb2
-rw-r--r--app/controllers/projects/branches_controller.rb3
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/graphql/mutations/issues/set_severity.rb2
-rw-r--r--app/helpers/learn_gitlab_helper.rb22
-rw-r--r--app/helpers/projects_helper.rb7
-rw-r--r--app/models/ci/build_metadata.rb9
-rw-r--r--app/models/concerns/ci/metadatable.rb2
-rw-r--r--app/models/concerns/resolvable_discussion.rb3
-rw-r--r--app/models/namespace.rb41
-rw-r--r--app/models/project.rb19
-rw-r--r--app/models/project_group_link.rb1
-rw-r--r--app/models/uploads/fog.rb14
-rw-r--r--app/models/user.rb2
-rw-r--r--app/policies/issuable_policy.rb6
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/services/concerns/update_visibility_level.rb12
-rw-r--r--app/services/groups/transfer_service.rb8
-rw-r--r--app/services/groups/update_service.rb4
-rw-r--r--app/services/issuable_base_service.rb1
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/views/clusters/clusters/aws/_new.html.haml2
-rw-r--r--app/views/clusters/clusters/gcp/_form.html.haml2
-rw-r--r--app/views/clusters/clusters/show.html.haml2
-rw-r--r--app/views/groups/dependency_proxies/show.html.haml1
-rw-r--r--app/views/layouts/project.html.haml1
37 files changed, 332 insertions, 65 deletions
diff --git a/app/assets/javascripts/lib/dompurify.js b/app/assets/javascripts/lib/dompurify.js
index d421d66981e..47ede8cb1bb 100644
--- a/app/assets/javascripts/lib/dompurify.js
+++ b/app/assets/javascripts/lib/dompurify.js
@@ -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');
};
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 1c22d21a313..c70d23d06ec 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -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');
}
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
index 73fb3656af1..13e5d7c3019 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
@@ -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"
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue
new file mode 100644
index 00000000000..78880b6e3f4
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue
@@ -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>
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue
new file mode 100644
index 00000000000..f3ac017268b
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue
@@ -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>
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js b/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js
new file mode 100644
index 00000000000..3c6ede6fdce
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js
@@ -0,0 +1 @@
+export const GRAPHQL_PAGE_SIZE = 20;
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
index 9058d349bf3..63d5469c955 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
@@ -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
+ }
+ }
}
}
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue
index b7546a6bed7..cc92a8cd476 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue
@@ -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>
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 261f7af7ef1..8deb955842c 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -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>
diff --git a/app/assets/javascripts/snippets/components/show.vue b/app/assets/javascripts/snippets/components/show.vue
index 46629a569ec..35d88d5ec8e 100644
--- a/app/assets/javascripts/snippets/components/show.vue
+++ b/app/assets/javascripts/snippets/components/show.vue
@@ -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>
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index b4a1d9f9977..122c605e603 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -85,3 +85,9 @@
td.line-numbers {
line-height: 1;
}
+
+.project-highlight-puc .unicode-bidi::before {
+ content: '�';
+ cursor: pointer;
+ text-decoration: underline wavy $red-500;
+}
diff --git a/app/controllers/jira_connect/application_controller.rb b/app/controllers/jira_connect/application_controller.rb
index ecb23c326fe..1d1575f3a05 100644
--- a/app/controllers/jira_connect/application_controller.rb
+++ b/app/controllers/jira_connect/application_controller.rb
@@ -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
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index b75effc52d1..3b9dde68ded 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -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
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 8e833a2f8dc..4888f40febc 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -409,6 +409,7 @@ class ProjectsController < Projects::ApplicationController
show_default_award_emojis
squash_option
mr_default_target_self
+ warn_about_potentially_unwanted_characters
]
end
diff --git a/app/graphql/mutations/issues/set_severity.rb b/app/graphql/mutations/issues/set_severity.rb
index 778563ba053..872a0e7b33d 100644
--- a/app/graphql/mutations/issues/set_severity.rb
+++ b/app/graphql/mutations/issues/set_severity.rb
@@ -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
diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb
index 4fb7a05a0e9..4f69d1bba0e 100644
--- a/app/helpers/learn_gitlab_helper.rb
+++ b/app/helpers/learn_gitlab_helper.rb
@@ -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')
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index a93fc188a3e..ef4bdfb46cf 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -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
}
diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb
index 56568c02d65..ca68989002c 100644
--- a/app/models/ci/build_metadata.rb
+++ b/app/models/ci/build_metadata.rb
@@ -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
diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index ec86746ae54..611b27c722b 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -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
diff --git a/app/models/concerns/resolvable_discussion.rb b/app/models/concerns/resolvable_discussion.rb
index 3e1e5faee54..60e1dde17b9 100644
--- a/app/models/concerns/resolvable_discussion.rb
+++ b/app/models/concerns/resolvable_discussion.rb
@@ -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)
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 1dfc0168ede..4b4e7f5cc1a 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -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
diff --git a/app/models/project.rb b/app/models/project.rb
index 490e0e15af4..031dbc3e9bb 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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?
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index d704f4c2c87..8394ebe1df4 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -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
diff --git a/app/models/uploads/fog.rb b/app/models/uploads/fog.rb
index b44e273e9ab..5d57b644dbe 100644
--- a/app/models/uploads/fog.rb
+++ b/app/models/uploads/fog.rb
@@ -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
diff --git a/app/models/user.rb b/app/models/user.rb
index 02011de8103..83558044c21 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -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
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index 61263e47d7c..39ce26526e6 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -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
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 59aa47beff9..87573c9ad13 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -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
diff --git a/app/services/concerns/update_visibility_level.rb b/app/services/concerns/update_visibility_level.rb
index b7a161f5089..4cd14a2fb53 100644
--- a/app/services/concerns/update_visibility_level.rb
+++ b/app/services/concerns/update_visibility_level.rb
@@ -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
+ return true unless 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)
+ Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility_level)
- deny_visibility_level(target, new_visibility)
+ deny_visibility_level(target, new_visibility_level)
return false
end
end
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index f0632780838..7e572cbb79b 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -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)
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 1ad43b051be..2d6334251ad 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -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
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 59e521853de..2daf098b94a 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -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
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index a32e80af4b1..b34ecf06e52 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -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
diff --git a/app/views/clusters/clusters/aws/_new.html.haml b/app/views/clusters/clusters/aws/_new.html.haml
index f6d50410e9a..7142dd83dce 100644
--- a/app/views/clusters/clusters/aws/_new.html.haml
+++ b/app/views/clusters/clusters/aws/_new.html.haml
@@ -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') } }
diff --git a/app/views/clusters/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml
index c274fb9c427..173456926a5 100644
--- a/app/views/clusters/clusters/gcp/_form.html.haml
+++ b/app/views/clusters/clusters/gcp/_form.html.haml
@@ -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 }
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index 2a09d8d8cc0..e4c8f225ed2 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -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'),
diff --git a/app/views/groups/dependency_proxies/show.html.haml b/app/views/groups/dependency_proxies/show.html.haml
index 8936c4dcbb4..83ab97f8e4f 100644
--- a/app/views/groups/dependency_proxies/show.html.haml
+++ b/app/views/groups/dependency_proxies/show.html.haml
@@ -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,
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 2df502d2899..a54e0351d2f 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -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