Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-06-30 03:08:59 +00:00
parent 7b5ff5df50
commit eddf359962
41 changed files with 507 additions and 300 deletions

View file

@ -2,5 +2,4 @@
Gitlab/DelegatePredicateMethods:
Exclude:
- app/models/clusters/cluster.rb
- app/models/project.rb
- ee/app/models/concerns/ee/ci/metadatable.rb

View file

@ -12,11 +12,11 @@ export default () => {
phrase,
buttonText,
buttonClass = '',
buttonTestid = null,
buttonVariant = null,
buttonTestid,
buttonVariant,
confirmDangerMessage,
confirmButtonText = null,
disabled = false,
disabled,
additionalInformation,
htmlConfirmationMessage,
} = el.dataset;

View file

@ -38,7 +38,7 @@ initMembersApp(document.querySelector('.js-project-members-list-app'), {
},
},
[MEMBER_TYPES.group]: {
tableFields: SHARED_FIELDS.concat('granted'),
tableFields: SHARED_FIELDS.concat(['source', 'granted']),
tableAttrs: {
table: { 'data-qa-selector': 'groups_list' },
tr: { 'data-qa-selector': 'group_row' },
@ -46,7 +46,7 @@ initMembersApp(document.querySelector('.js-project-members-list-app'), {
requestFormatter: groupLinkRequestFormatter,
filteredSearchBar: {
show: true,
tokens: [],
tokens: ['groups_with_inherited_permissions'],
searchParam: 'search_groups',
placeholder: s__('Members|Search groups'),
recentSearchesStorageKey: 'project_group_links',

View file

@ -121,27 +121,21 @@ export default {
@selectRevision="onSelectRevision"
/>
</div>
<div class="gl-mt-6">
<div class="gl-display-flex gl-mt-6 gl-gap-3">
<gl-button category="primary" variant="confirm" @click="onSubmit">
{{ s__('CompareRevisions|Compare') }}
</gl-button>
<gl-button data-testid="swapRevisionsButton" class="btn btn-default" @click="onSwapRevision">
<gl-button data-testid="swapRevisionsButton" @click="onSwapRevision">
{{ s__('CompareRevisions|Swap revisions') }}
</gl-button>
<gl-button
v-if="projectMergeRequestPath"
:href="projectMergeRequestPath"
data-testid="projectMrButton"
class="btn btn-default gl-button"
>
{{ s__('CompareRevisions|View open merge request') }}
</gl-button>
<gl-button
v-else-if="createMrPath"
:href="createMrPath"
data-testid="createMrButton"
class="btn btn-default gl-button"
>
<gl-button v-else-if="createMrPath" :href="createMrPath" data-testid="createMrButton">
{{ s__('CompareRevisions|Create merge request') }}
</gl-button>
</div>

View file

@ -276,28 +276,33 @@ const bindEvents = () => {
);
let isProjectImportUrlDirty = false;
$projectImportUrl.addEventListener('blur', () => {
isProjectImportUrlDirty = true;
debouncedUpdateUrlPathWarningVisibility();
});
$projectImportUrl.addEventListener('keyup', () => {
deriveProjectPathFromUrl($projectImportUrl);
});
if ($projectImportUrl) {
$projectImportUrl.addEventListener('blur', () => {
isProjectImportUrlDirty = true;
debouncedUpdateUrlPathWarningVisibility();
});
$projectImportUrl.addEventListener('keyup', () => {
deriveProjectPathFromUrl($projectImportUrl);
});
}
[$projectImportUrl, $projectImportUrlUser, $projectImportUrlPassword].forEach(($f) => {
if ($f?.on) {
$f.on('input', () => {
if (isProjectImportUrlDirty) {
debouncedUpdateUrlPathWarningVisibility();
}
});
} else {
$f.addEventListener('input', () => {
if (!$f) return false;
if ($f.on) {
return $f.on('input', () => {
if (isProjectImportUrlDirty) {
debouncedUpdateUrlPathWarningVisibility();
}
});
}
return $f.addEventListener('input', () => {
if (isProjectImportUrlDirty) {
debouncedUpdateUrlPathWarningVisibility();
}
});
});
$projectImportForm.on('submit', async (e) => {

View file

@ -13,9 +13,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def index
@sort = params[:sort].presence || sort_value_name
@group_links = @project.project_group_links
@group_links = @group_links.search(params[:search_groups]) if params[:search_groups].present?
@include_relations ||= requested_relations(:groups_with_inherited_permissions)
if can?(current_user, :admin_project_member, @project)
@invited_members = present_members(invited_members)

View file

@ -1,10 +1,10 @@
# frozen_string_literal: true
module Projects::ProjectMembersHelper
def project_members_app_data_json(project, members:, group_links:, invited:, access_requests:)
def project_members_app_data_json(project, members:, invited:, access_requests:, include_relations:, search:)
{
user: project_members_list_data(project, members, { param_name: :page, params: { search_groups: nil } }),
group: project_group_links_list_data(project, group_links),
group: project_group_links_list_data(project, include_relations, search),
invite: project_members_list_data(project, invited.nil? ? [] : invited),
access_request: project_members_list_data(project, access_requests.nil? ? [] : access_requests),
source_id: project.id,
@ -57,10 +57,29 @@ module Projects::ProjectMembersHelper
}
end
def project_group_links_list_data(project, group_links)
def project_group_links_list_data(project, include_relations, search)
members = []
if include_relations.include?(:direct)
project_group_links = project.project_group_links
project_group_links = project_group_links.search(search) if search
members += project_group_links_serialized(project, project_group_links)
end
if include_relations.include?(:inherited)
group_group_links = project.group_group_links.distinct_on_shared_with_group_id_with_group_access
group_group_links = group_group_links.search(search) if search
members += group_group_links_serialized(project, group_group_links)
end
if project_group_links.present? && group_group_links.present?
members = members.sort_by { |m| -m.dig(:access_level, :integer_value).to_i }
.uniq { |m| m.dig(:shared_with_group, :id) }
end
{
members: project_group_links_serialized(project, group_links),
pagination: members_pagination_data(group_links),
members: members,
pagination: members_pagination_data(members),
member_path: project_group_link_path(project, ':id')
}
end

View file

@ -441,33 +441,29 @@ class Project < ApplicationRecord
accepts_nested_attributes_for :prometheus_integration, update_only: true
accepts_nested_attributes_for :alerting_setting, update_only: true
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
:merge_requests_enabled?, :forking_enabled?, :issues_enabled?,
:pages_enabled?, :analytics_enabled?, :snippets_enabled?, :public_pages?, :private_pages?,
:merge_requests_access_level, :forking_access_level, :issues_access_level,
:wiki_access_level, :snippets_access_level, :builds_access_level,
:repository_access_level, :package_registry_access_level, :pages_access_level, :metrics_dashboard_access_level, :analytics_access_level,
:operations_enabled?, :operations_access_level, :security_and_compliance_access_level,
: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?,
:enforce_auth_checks_on_uploads, :enforce_auth_checks_on_uploads=, :enforce_auth_checks_on_uploads?,
: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
delegate :merge_requests_access_level, :forking_access_level, :issues_access_level,
:wiki_access_level, :snippets_access_level, :builds_access_level,
:repository_access_level, :package_registry_access_level, :pages_access_level,
:metrics_dashboard_access_level, :analytics_access_level,
:operations_access_level, :security_and_compliance_access_level,
:container_registry_access_level,
to: :project_feature, allow_nil: true
delegate :show_default_award_emojis, :show_default_award_emojis=,
:enforce_auth_checks_on_uploads, :enforce_auth_checks_on_uploads=,
:warn_about_potentially_unwanted_characters, :warn_about_potentially_unwanted_characters=,
to: :project_setting, allow_nil: true
delegate :squash_always?, :squash_never?, :squash_enabled_by_default?, :squash_readonly?, to: :project_setting
delegate :squash_option, :squash_option=, to: :project_setting
delegate :mr_default_target_self, :mr_default_target_self=, to: :project_setting
delegate :previous_default_branch, :previous_default_branch=, to: :project_setting
delegate :no_import?, to: :import_state, allow_nil: true
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
delegate :add_user, :add_users, to: :team
delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_owner, :add_role, to: :team
delegate :group_runners_enabled, :group_runners_enabled=, to: :ci_cd_settings, allow_nil: true
delegate :root_ancestor, :certificate_based_clusters_enabled?, to: :namespace, allow_nil: true
delegate :root_ancestor, to: :namespace, allow_nil: true
delegate :last_pipeline, to: :commit, allow_nil: true
delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true
delegate :dashboard_timezone, to: :metrics_setting, allow_nil: true, prefix: true
@ -483,7 +479,6 @@ class Project < ApplicationRecord
delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?,
:allow_merge_on_skipped_pipeline=, :has_confluence?, :has_shimo?,
to: :project_setting
delegate :active?, to: :prometheus_integration, allow_nil: true, prefix: true
delegate :merge_commit_template, :merge_commit_template=, to: :project_setting, allow_nil: true
delegate :squash_commit_template, :squash_commit_template=, to: :project_setting, allow_nil: true
@ -916,6 +911,14 @@ class Project < ApplicationRecord
association(:namespace).loaded?
end
def certificate_based_clusters_enabled?
!!namespace&.certificate_based_clusters_enabled?
end
def prometheus_integration_active?
!!prometheus_integration&.active?
end
def personal_namespace_holder?(user)
return false unless personal?
return false unless user
@ -932,6 +935,42 @@ class Project < ApplicationRecord
super.presence || build_project_setting
end
def show_default_award_emojis?
!!project_setting&.show_default_award_emojis?
end
def enforce_auth_checks_on_uploads?
!!project_setting&.enforce_auth_checks_on_uploads?
end
def warn_about_potentially_unwanted_characters?
!!project_setting&.warn_about_potentially_unwanted_characters?
end
def no_import?
!!import_state&.no_import?
end
def import_scheduled?
!!import_state&.scheduled?
end
def import_started?
!!import_state&.started?
end
def import_in_progress?
!!import_state&.in_progress?
end
def import_failed?
!!import_state&.failed?
end
def import_finished?
!!import_state&.finished?
end
def all_pipelines
if builds_enabled?
super
@ -1839,6 +1878,59 @@ class Project < ApplicationRecord
end
end
def feature_available?(feature, user = nil)
!!project_feature&.feature_available?(feature, user)
end
def builds_enabled?
!!project_feature&.builds_enabled?
end
def wiki_enabled?
!!project_feature&.wiki_enabled?
end
def merge_requests_enabled?
!!project_feature&.merge_requests_enabled?
end
def forking_enabled?
!!project_feature&.forking_enabled?
end
def issues_enabled?
!!project_feature&.issues_enabled?
end
def pages_enabled?
!!project_feature&.pages_enabled?
end
def analytics_enabled?
!!project_feature&.analytics_enabled?
end
def snippets_enabled?
!!project_feature&.snippets_enabled?
end
def public_pages?
!!project_feature&.public_pages?
end
def private_pages?
!!project_feature&.private_pages?
end
def operations_enabled?
!!project_feature&.operations_enabled?
end
def container_registry_enabled?
!!project_feature&.container_registry_enabled?
end
alias_method :container_registry_enabled, :container_registry_enabled?
def enable_ci
project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end
@ -2902,6 +2994,10 @@ class Project < ApplicationRecord
build_artifacts_size_refresh&.started?
end
def group_group_links
group&.shared_with_group_links&.of_ancestors_and_self || GroupGroupLink.none
end
def security_training_available?
licensed_feature_available?(:security_training)
end

View file

@ -16,6 +16,8 @@ module Packages
raise ActiveRecord::RecordInvalid, meta
end
params.delete(:md5_digest) if Gitlab::FIPS.enabled?
Packages::Pypi::Metadatum.upsert(meta.attributes)
::Packages::CreatePackageFileService.new(created_package, file_params).execute

View file

@ -39,7 +39,8 @@
.js-project-members-list-app{ data: { members_data: project_members_app_data_json(@project,
members: @project_members,
group_links: @group_links,
invited: @invited_members,
access_requests: @requesters) } }
access_requests: @requesters,
include_relations: @include_relations,
search: params[:search_groups]) } }
= gl_loading_icon(css_class: 'gl-my-5', size: 'md')

View file

@ -20,6 +20,8 @@ module Gitlab
config.view_component.preview_route = "/-/view_component/previews"
config.active_support.hash_digest_class = ::OpenSSL::Digest::SHA256
# This section contains configuration from Rails upgrades to override the new defaults so that we
# keep existing behavior.
#
@ -38,7 +40,6 @@ module Gitlab
# Rails 5.2
config.action_dispatch.use_authenticated_cookie_encryption = false
config.active_support.use_authenticated_message_encryption = false
config.active_support.hash_digest_class = ::Digest::MD5 # New default is ::Digest::SHA1
config.action_controller.default_protect_from_forgery = false
config.action_view.form_with_generates_ids = false

View file

@ -1,8 +0,0 @@
---
name: active_support_hash_digest_sha256
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90098
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365314
milestone: '15.1'
type: development
group: group::sharding
default_enabled: false

View file

@ -8,7 +8,8 @@
# and it's used only as the last resort. In such case this termination is
# logged and we should fix the potential timeout issue in the code itself.
if Gitlab::Runtime.puma? && !Rails.env.test?
if Gitlab::Runtime.puma? && !Rails.env.test? &&
Gitlab::Utils.to_boolean(ENV['GITLAB_RAILS_RACK_TIMEOUT_ENABLE'], default: true)
Rack::Timeout::Logger.level = Logger::ERROR
Gitlab::Application.configure do |config|

View file

@ -1,11 +0,0 @@
# frozen_string_literal: true
Rails.application.configure do
# We set ActiveSupport::Digest.hash_digest_class directly copying
# See https://github.com/rails/rails/blob/6-1-stable/activesupport/lib/active_support/railtie.rb#L96-L98
#
# Note that is the only usage of config.active_support.hash_digest_class
config.after_initialize do
ActiveSupport::Digest.hash_digest_class = Gitlab::HashDigest::Facade
end
end

View file

@ -169,6 +169,20 @@ mutation {
The header is created if the returned `errors` object is empty.
### Update with the API
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/361964) in GitLab 15.2.
Group owners can update a HTTP header using the GraphQL `auditEventsStreamingHeadersCreate` mutation.
```graphql
mutation {
auditEventsStreamingHeadersUpdate(input: { headerId: "gid://gitlab/AuditEvents::Streaming::Header/24255", key: "new-foo", value: "new-bar" }) {
errors
}
}
```
### Delete with the API
Group owners can remove a HTTP header using the GraphQL `auditEventsStreamingHeadersDestroy` mutation. You can retrieve the header ID

View file

@ -42,11 +42,6 @@ The process also updates the following user information:
- SSH public keys (if `sync_ssh_keys` is set)
- Kerberos identity (if Kerberos is enabled)
The LDAP sync process:
- Updates existing users.
- Creates new users on first sign in.
### Adjust LDAP user sync schedule
By default, GitLab runs a worker once per day at 01:30 a.m. server time to

View file

@ -778,6 +778,27 @@ Input type: `AuditEventsStreamingHeadersDestroyInput`
| <a id="mutationauditeventsstreamingheadersdestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationauditeventsstreamingheadersdestroyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.auditEventsStreamingHeadersUpdate`
Input type: `AuditEventsStreamingHeadersUpdateInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationauditeventsstreamingheadersupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationauditeventsstreamingheadersupdateheaderid"></a>`headerId` | [`AuditEventsStreamingHeaderID!`](#auditeventsstreamingheaderid) | Header to update. |
| <a id="mutationauditeventsstreamingheadersupdatekey"></a>`key` | [`String!`](#string) | Header key. |
| <a id="mutationauditeventsstreamingheadersupdatevalue"></a>`value` | [`String!`](#string) | Header value. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationauditeventsstreamingheadersupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationauditeventsstreamingheadersupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationauditeventsstreamingheadersupdateheader"></a>`header` | [`AuditEventStreamingHeader`](#auditeventstreamingheader) | Updates header. |
### `Mutation.awardEmojiAdd`
Input type: `AwardEmojiAddInput`

View file

@ -692,7 +692,7 @@ Example of response
"finished_at": null,
"duration": 8,
"queued_duration": 0.010,
"id": 42,
"id": 1,
"name": "rubocop",
"ref": "main",
"artifacts": [],
@ -742,7 +742,7 @@ Example of response
"finished_at": null,
"duration": null,
"queued_duration": 0.010,
"id": 42,
"id": 1,
"name": "rubocop",
"ref": "main",
"artifacts": [],
@ -792,7 +792,7 @@ Example of response
"coverage": null,
"allow_failure": false,
"download_url": null,
"id": 42,
"id": 1,
"name": "rubocop",
"ref": "main",
"artifacts": [],
@ -873,7 +873,7 @@ Example response:
"finished_at": null,
"duration": null,
"queued_duration": 0.010,
"id": 42,
"id": 1,
"name": "rubocop",
"ref": "main",
"artifacts": [],

View file

@ -20,6 +20,10 @@ These endpoints do not adhere to the standard API authentication methods.
See the [PyPI package registry documentation](../../user/packages/pypi_repository/index.md)
for details on which headers and token types are supported.
NOTE:
[Twine 3.4.2](https://twine.readthedocs.io/en/stable/changelog.html?highlight=FIPS#id28) or greater
is recommended when [FIPS mode](../../development/fips_compliance.md) is enabled.
## Download a package file from a group
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/225545) in GitLab 13.12.

View file

@ -393,7 +393,7 @@ is updated in a major version GitLab release.
### Add metrics
Every CI/CD template must also have metrics defined to track their use.
Every CI/CD template must also have metrics defined to track their use. The CI/CD template monthly usage report can be found in [Sisense (GitLab team members only)](https://app.periscopedata.com/app/gitlab/785953/Pipeline-Authoring-Dashboard?widget=14910475&udv=0).
To add a metric definition for a new template:

View file

@ -217,6 +217,8 @@ module API
track_package_event('push_package', :pypi, project: authorized_user_project, user: current_user, namespace: authorized_user_project.namespace)
unprocessable_entity! if Gitlab::FIPS.enabled? && declared_params[:md5_digest].present?
::Packages::Pypi::CreatePackageService
.new(authorized_user_project, current_user, declared_params.merge(build: current_authenticated_job))
.execute

View file

@ -7,6 +7,8 @@ module Gitlab
form_action frame_ancestors frame_src img_src manifest_src
media_src object_src report_uri script_src style_src worker_src).freeze
DEFAULT_FALLBACK_VALUE = '<default_value>'
def self.default_enabled
Rails.env.development? || Rails.env.test?
end
@ -62,8 +64,10 @@ module Gitlab
end
def initialize(csp_directives)
# Using <default_value> falls back to the default values.
directives = csp_directives.reject { |_, value| value == DEFAULT_FALLBACK_VALUE }
@merged_csp_directives =
HashWithIndifferentAccess.new(csp_directives)
HashWithIndifferentAccess.new(directives)
.reverse_merge(::Gitlab::ContentSecurityPolicy::ConfigLoader.default_directives)
end

View file

@ -1,29 +0,0 @@
# frozen_string_literal: true
module Gitlab
module HashDigest
# Used for rolling out to use OpenSSL::Digest::SHA256
# for ActiveSupport::Digest
class Facade
class << self
def hexdigest(...)
hash_digest_class.hexdigest(...)
end
def hash_digest_class
if use_sha256?
::OpenSSL::Digest::SHA256
else
::Digest::MD5 # rubocop:disable Fips/MD5
end
end
def use_sha256?
return false unless Feature.feature_flags_available?
Feature.enabled?(:active_support_hash_digest_sha256)
end
end
end
end
end

View file

@ -79,7 +79,10 @@ module QA
def delete_issue
click_element(:issue_actions_ellipsis_dropdown)
click_element(:delete_issue_button, Page::Modal::DeleteIssue)
click_element(:delete_issue_button,
Page::Modal::DeleteIssue,
wait: Support::Repeater::DEFAULT_MAX_WAIT_TIME)
Page::Modal::DeleteIssue.perform(&:confirm_delete_issue)

View file

@ -1,94 +0,0 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Manage' do
describe 'Personal project permissions', :reliable do
let!(:owner) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let!(:owner_api_client) { Runtime::API::Client.new(:gitlab, user: owner) }
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
project.api_client = owner_api_client
project.name = 'qa-owner-personal-project'
project.personal_namespace = owner.username
end
end
after do
project&.remove_via_api!
end
context 'when user is added as Owner' do
let(:issue) do
Resource::Issue.fabricate_via_api! do |issue|
issue.api_client = owner_api_client
issue.project = project
issue.title = 'Test Owner deletes issue'
end
end
before do
Flow::Login.sign_in(as: owner)
end
it "has Owner role with Owner permissions", testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352542' do
Page::Dashboard::Projects.perform do |projects|
projects.filter_by_name(project.name)
expect(projects).to have_project_with_access_role(project.name, 'Owner')
end
expect_owner_permissions_allow_delete_issue
end
end
context 'when user is added as Maintainer' do
let(:maintainer) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) }
let(:issue) do
Resource::Issue.fabricate_via_api! do |issue|
issue.api_client = owner_api_client
issue.project = project
issue.title = 'Test Maintainer deletes issue'
end
end
before do
project.add_member(maintainer, Resource::Members::AccessLevel::MAINTAINER)
Flow::Login.sign_in(as: maintainer)
end
it "has Maintainer role without Owner permissions", testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352607' do
Page::Dashboard::Projects.perform do |projects|
projects.filter_by_name(project.name)
expect(projects).to have_project_with_access_role(project.name, 'Maintainer')
end
expect_maintainer_permissions_do_not_allow_delete_issue
end
end
private
def expect_owner_permissions_allow_delete_issue
issue.visit!
Page::Project::Issue::Show.perform(&:delete_issue)
Page::Project::Issue::Index.perform do |index|
expect(index).not_to have_issue(issue)
end
end
def expect_maintainer_permissions_do_not_allow_delete_issue
issue.visit!
Page::Project::Issue::Show.perform do |issue|
expect(issue).not_to have_delete_issue_button
end
end
end
end
end

View file

@ -0,0 +1,104 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Manage' do
describe 'Project owner permissions' do
let!(:owner) do
Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
end
let!(:owner_api_client) { Runtime::API::Client.new(:gitlab, user: owner) }
let!(:maintainer) do
Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2)
end
shared_examples 'when user is added as owner' do |project_type, testcase|
let!(:issue) do
Resource::Issue.fabricate_via_api! do |issue|
issue.api_client = owner_api_client
issue.project = project
issue.title = 'Test Owner Deletes Issue'
end
end
before do
project.add_member(owner, Resource::Members::AccessLevel::OWNER) if project_type == :group_project
Flow::Login.sign_in(as: owner)
end
it "has owner role with owner permissions", testcase: testcase do
Page::Dashboard::Projects.perform do |projects|
projects.filter_by_name(project.name)
expect(projects).to have_project_with_access_role(project.name, 'Owner')
end
issue.visit!
Page::Project::Issue::Show.perform(&:delete_issue)
Page::Project::Issue::Index.perform do |index|
expect(index).not_to have_issue(issue)
end
end
end
shared_examples 'when user is added as maintainer' do |testcase|
let!(:issue) do
Resource::Issue.fabricate_via_api! do |issue|
issue.api_client = owner_api_client
issue.project = project
issue.title = 'Test Maintainer Deletes Issue'
end
end
before do
project.add_member(maintainer, Resource::Members::AccessLevel::MAINTAINER)
Flow::Login.sign_in(as: maintainer)
end
it "has maintainer role without owner permissions", testcase: testcase do
Page::Dashboard::Projects.perform do |projects|
projects.filter_by_name(project.name)
expect(projects).to have_project_with_access_role(project.name, 'Maintainer')
end
issue.visit!
Page::Project::Issue::Show.perform do |issue|
expect(issue).not_to have_delete_issue_button
end
end
end
context 'for personal projects' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
project.api_client = owner_api_client
project.name = 'qa-owner-personal-project'
project.personal_namespace = owner.username
end
end
it_behaves_like 'when user is added as owner', :personal_project, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352542'
it_behaves_like 'when user is added as maintainer', 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352607'
end
context 'for group projects' do
let!(:group) { Resource::Group.fabricate_via_api! }
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
project.group = group
project.name = 'qa-owner-group-project'
end
end
it_behaves_like 'when user is added as owner', :group_project, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/366436'
it_behaves_like 'when user is added as maintainer', 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/366435'
end
end
end
end

View file

@ -41,7 +41,6 @@ module Glfm
h1_regex = /\A# / # new logic compared to original Python code
h2_regex = /\A## / # new logic compared to original Python code
h3_regex = /\A### / # new logic compared to original Python code
header_regex = /\A#+ / # Added beginning of line anchor to original Python code
spec_txt_lines.each do |line|
@ -103,19 +102,12 @@ module Glfm
# reset the headers array if we found a new H1
headers = [] if line =~ h1_regex
if headers.length == 2 && line =~ h2_regex
# pop the last entry from the headers array if we are in an H2 and found a new H2
headers.pop
elsif headers.length == 3 && line =~ h3_regex
# pop the last entry from the headers array if we are in an H3 and found a new H3
headers.pop
elsif headers.length == 3 && line =~ h2_regex
# pop the last two entries from the headers array if we are in an H3 and found a new H2
headers.pop(2)
end
# headers should be size 2 or less [<H1_headertext>, <H2_headertext>]
# pop the last entry from the headers array if we are in an H2 and found a new H2
headers.pop if headers.length == 2 && line =~ h2_regex
# push the new header text to the headers array
headers << headertext # New logic compared to original Python code
headers << headertext if line =~ h1_regex || line =~ h2_regex
else
# Else if we are in regular text...

View file

@ -68,27 +68,6 @@ RSpec.describe Projects::ProjectMembersController do
end
end
context 'group links' do
let_it_be(:project_group_link) { create(:project_group_link, project: project, group: group) }
it 'lists group links' do
get :index, params: { namespace_id: project.namespace, project_id: project }
expect(assigns(:group_links).map(&:id)).to contain_exactly(project_group_link.id)
end
context 'when `search_groups` param is present' do
let(:group_2) { create(:group, :public, name: 'group_2') }
let!(:project_group_link_2) { create(:project_group_link, project: project, group: group_2) }
it 'lists group links that match search' do
get :index, params: { namespace_id: project.namespace, project_id: project, search_groups: 'group_2' }
expect(assigns(:group_links).map(&:id)).to contain_exactly(project_group_link_2.id)
end
end
end
context 'invited members' do
let_it_be(:invited_member) { create(:project_member, :invited, project: project) }

View file

@ -298,6 +298,20 @@ RSpec.describe 'New project', :js do
end
end
context 'Import project options without any sources', :js do
before do
stub_application_setting(import_sources: [])
visit new_project_path
click_link 'Import project'
end
it 'displays the no import options message' do
expect(page).to have_text s_('ProjectsNew|No import options available')
expect(page).to have_text s_('ProjectsNew|Contact an administrator to enable options for importing your project.')
end
end
context 'Import project options', :js do
before do
visit new_project_path

View file

@ -14,7 +14,6 @@ RSpec.describe Projects::ProjectMembersHelper do
describe 'project members' do
let_it_be(:members) { create_list(:project_member, 2, project: project) }
let_it_be(:group_links) { create_list(:project_group_link, 1, project: project) }
let_it_be(:invited) { create_list(:project_member, 2, :invited, project: project) }
let_it_be(:access_requests) { create_list(:project_member, 2, :access_request, project: project) }
@ -26,9 +25,10 @@ RSpec.describe Projects::ProjectMembersHelper do
helper.project_members_app_data_json(
project,
members: present_members(members_collection),
group_links: group_links,
invited: present_members(invited),
access_requests: present_members(access_requests)
access_requests: present_members(access_requests),
include_relations: [:inherited, :direct],
search: nil
)
)
end
@ -84,6 +84,70 @@ RSpec.describe Projects::ProjectMembersHelper do
expect(subject['user']['pagination']).to match(expected)
end
end
context 'group links' do
let_it_be(:shared_with_group) { create(:group) }
let_it_be(:group_link) { create(:project_group_link, project: project, group: shared_with_group) }
before do
allow(helper).to receive(:project_group_link_path).with(project, ':id').and_return('/foo-group/foo-project/-/group_links/:id')
end
it 'sets `group.members` property that matches json schema' do
expect(subject['group']['members'].to_json).to match_schema('group_link/project_group_links')
end
it 'sets `member_path` property' do
expect(subject['group']['member_path']).to eq('/foo-group/foo-project/-/group_links/:id')
end
context 'inherited' do
let_it_be(:shared_with_group_1) { create(:group) }
let_it_be(:shared_with_group_2) { create(:group) }
let_it_be(:shared_with_group_3) { create(:group) }
let_it_be(:shared_with_group_4) { create(:group) }
let_it_be(:shared_with_group_5) { create(:group) }
let_it_be(:top_group) { create(:group) }
let_it_be(:sub_group) { create(:group, parent: top_group) }
let_it_be(:project) { create(:project, group: sub_group) }
let_it_be(:group_link_1) { create(:group_group_link, shared_group: top_group, shared_with_group: shared_with_group_1, group_access: Gitlab::Access::GUEST) }
let_it_be(:group_link_2) { create(:group_group_link, shared_group: top_group, shared_with_group: shared_with_group_4, group_access: Gitlab::Access::GUEST) }
let_it_be(:group_link_3) { create(:group_group_link, shared_group: top_group, shared_with_group: shared_with_group_5, group_access: Gitlab::Access::DEVELOPER) }
let_it_be(:group_link_4) { create(:group_group_link, shared_group: sub_group, shared_with_group: shared_with_group_2, group_access: Gitlab::Access::DEVELOPER) }
let_it_be(:group_link_5) { create(:group_group_link, shared_group: sub_group, shared_with_group: shared_with_group_4, group_access: Gitlab::Access::DEVELOPER) }
let_it_be(:group_link_6) { create(:group_group_link, shared_group: sub_group, shared_with_group: shared_with_group_5, group_access: Gitlab::Access::GUEST) }
let_it_be(:group_link_7) { create(:project_group_link, project: project, group: shared_with_group_1, group_access: Gitlab::Access::DEVELOPER) }
let_it_be(:group_link_8) { create(:project_group_link, project: project, group: shared_with_group_2, group_access: Gitlab::Access::GUEST) }
let_it_be(:group_link_9) { create(:project_group_link, project: project, group: shared_with_group_3, group_access: Gitlab::Access::REPORTER) }
subject do
Gitlab::Json.parse(
helper.project_members_app_data_json(
project,
members: present_members(members_collection),
invited: present_members(invited),
access_requests: present_members(access_requests),
include_relations: include_relations,
search: nil
)
)
end
using RSpec::Parameterized::TableSyntax
where(:include_relations, :result) do
[:inherited, :direct] | lazy { [group_link_7, group_link_4, group_link_9, group_link_5, group_link_3].map(&:id) }
[:inherited] | lazy { [group_link_1, group_link_4, group_link_5, group_link_3].map(&:id) }
[:direct] | lazy { [group_link_7, group_link_8, group_link_9].map(&:id) }
end
with_them do
it 'returns correct group links' do
expect(subject['group']['members'].map { |link| link['id'] }).to match_array(result)
end
end
end
end
end
end

View file

@ -1,9 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'setting ActiveSupport::Digest.hash_digest_class' do
it 'sets overrides config.active_support.hash_digest_class' do
expect(ActiveSupport::Digest.hash_digest_class).to eq(Gitlab::HashDigest::Facade)
end
end

View file

@ -71,6 +71,13 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do
expect(Ci::Build.tagged_with('tag3')).to include(other_job)
end
it 'strips tags' do
job.tag_list = [' taga', 'tagb ', ' tagc ']
service.insert!
expect(job.tags.map(&:name)).to match_array(%w[taga tagb tagc])
end
context 'when batching inserts for tags' do
before do
stub_const("#{described_class}::TAGS_BATCH_SIZE", 2)

View file

@ -220,10 +220,11 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do
expect(policy.directives['base-uri']).to be_nil
end
it 'returns default values for directives not defined by the user' do
it 'returns default values for directives not defined by the user or with <default_value> and disables directives set to false' do
# Explicitly disabling script_src and setting report_uri
csp_config[:directives] = {
script_src: false,
style_src: '<default_value>',
report_uri: 'https://example.org'
}

View file

@ -1,36 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::HashDigest::Facade do
describe '.hexdigest' do
let(:plaintext) { 'something that is plaintext' }
let(:sha256_hash) { OpenSSL::Digest::SHA256.hexdigest(plaintext) }
let(:md5_hash) { Digest::MD5.hexdigest(plaintext) } # rubocop:disable Fips/MD5
it 'uses SHA256' do
expect(described_class.hexdigest(plaintext)).to eq(sha256_hash)
end
context 'when feature flags is not available' do
before do
allow(Feature).to receive(:feature_flags_available?).and_return(false)
end
it 'uses MD5' do
expect(described_class.hexdigest(plaintext)).to eq(md5_hash)
end
end
context 'when active_support_hash_digest_sha256 FF is disabled' do
before do
stub_feature_flags(active_support_hash_digest_sha256: false)
end
it 'uses MD5' do
expect(described_class.hexdigest(plaintext)).to eq(md5_hash)
end
end
end
end

View file

@ -2073,6 +2073,13 @@ RSpec.describe Ci::Build do
expect(build.tags.first.name).to eq('tag')
end
it 'strips tags' do
build.tag_list = [' taga', 'tagb ', ' tagc ']
build.save!
expect(build.tags.map(&:name)).to match_array(%w[taga tagb tagc])
end
context 'with BulkInsertableTags.with_bulk_insert_tags' do
it 'does not save_tags' do
Ci::BulkInsertableTags.with_bulk_insert_tags do

View file

@ -1190,6 +1190,13 @@ RSpec.describe Ci::Runner do
expect(runner.tags.first.name).to eq('tag')
end
it 'strips tags' do
runner.tag_list = [' taga', 'tagb ', ' tagc ']
runner.save!
expect(runner.tags.map(&:name)).to match_array(%w[taga tagb tagc])
end
context 'with BulkInsertableTags.with_bulk_insert_tags' do
it 'does not save_tags' do
Ci::BulkInsertableTags.with_bulk_insert_tags do

View file

@ -8390,6 +8390,27 @@ RSpec.describe Project, factory_default: :keep do
end
end
describe '#group_group_links' do
context 'with group project' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
it 'returns group links of group' do
expect(group).to receive_message_chain(:shared_with_group_links, :of_ancestors_and_self)
project.group_group_links
end
end
context 'with personal project' do
let_it_be(:project) { create(:project) }
it 'returns none' do
expect(project.group_group_links).to eq(GroupGroupLink.none)
end
end
end
describe '#security_training_available?' do
subject { build(:project) }

View file

@ -197,7 +197,7 @@ RSpec.describe API::PypiPackages do
let(:url) { "/projects/#{project.id}/packages/pypi" }
let(:headers) { {} }
let(:requires_python) { '>=3.7' }
let(:base_params) { { requires_python: requires_python, version: '1.0.0', name: 'sample-project', sha256_digest: '1' * 64 } }
let(:base_params) { { requires_python: requires_python, version: '1.0.0', name: 'sample-project', sha256_digest: '1' * 64, md5_digest: '1' * 32 } }
let(:params) { base_params.merge(content: temp_file(file_name)) }
let(:send_rewritten_field) { true }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
@ -254,6 +254,19 @@ RSpec.describe API::PypiPackages do
it_behaves_like 'PyPI package creation', :developer, :created, true
end
context 'without md5_digest' do
let(:token) { personal_access_token.token }
let(:user_headers) { basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_headers) }
let(:params) { base_params.merge(content: temp_file(file_name)) }
before do
params.delete(:md5_digest)
end
it_behaves_like 'PyPI package creation', :developer, :created, true, false
end
end
context 'with required_python too big' do

View file

@ -42,6 +42,21 @@ RSpec.describe Packages::Pypi::CreatePackageService, :aggregate_failures do
end
end
context 'with FIPS mode', :fips_mode do
it 'does not generate file_md5' do
expect { subject }.to change { Packages::Package.pypi.count }.by(1)
expect(created_package.name).to eq 'foo'
expect(created_package.version).to eq '1.0'
expect(created_package.pypi_metadatum.required_python).to eq '>=2.7'
expect(created_package.package_files.size).to eq 1
expect(created_package.package_files.first.file_name).to eq 'foo.tgz'
expect(created_package.package_files.first.file_sha256).to eq sha256
expect(created_package.package_files.first.file_md5).to be_nil
end
end
context 'without required_python' do
before do
params.delete(:requires_python)

View file

@ -49,7 +49,7 @@ RSpec.describe Projects::CreateFromTemplateService do
end
it 'is not scheduled' do
expect(project.import_scheduled?).to be_nil
expect(project.import_scheduled?).to be(false)
end
it 'repository is empty' do

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true
RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member = true|
RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member = true, md5_digest = true|
RSpec.shared_examples 'creating pypi package files' do
it 'creates package files' do
expect { subject }
@ -14,6 +14,17 @@ RSpec.shared_examples 'PyPI package creation' do |user_type, status, add_member
expect(package.name).to eq params[:name]
expect(package.version).to eq params[:version]
expect(package.pypi_metadatum.required_python).to eq params[:requires_python]
if md5_digest
expect(package.package_files.first.file_md5).not_to be_nil
else
expect(package.package_files.first.file_md5).to be_nil
end
end
context 'with FIPS mode', :fips_mode do
it_behaves_like 'returning response status', :unprocessable_entity if md5_digest
it_behaves_like 'returning response status', status unless md5_digest
end
end