diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-28 18:14:18 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-28 18:14:18 +0000 |
commit | b267f3a3acf3fe4cf2a9bb254d7abeb3b2823b21 (patch) | |
tree | a1c30c9a460a1c990311d47aae72d00368e1a1c1 /app | |
parent | 03cd4f8da4f848c7dfd0c0b88b4c095f69e56bb0 (diff) | |
download | gitlab-ce-b267f3a3acf3fe4cf2a9bb254d7abeb3b2823b21.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
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> </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 |