Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-02-20 06:11:19 +00:00
parent 1ea1a786c4
commit 1912008140
11 changed files with 250 additions and 76 deletions

View File

@ -140,7 +140,6 @@ Style/InlineDisableAnnotation:
- 'app/finders/fork_targets_finder.rb'
- 'app/finders/group_descendants_finder.rb'
- 'app/finders/group_finder.rb'
- 'app/finders/group_members_finder.rb'
- 'app/finders/groups/accepting_group_transfers_finder.rb'
- 'app/finders/groups/accepting_project_creations_finder.rb'
- 'app/finders/groups/accepting_project_shares_finder.rb'

View File

@ -2,7 +2,6 @@
# Cop supports --autocorrect.
Style/MutableConstant:
Exclude:
- 'app/finders/group_members_finder.rb'
- 'app/graphql/mutations/container_repositories/destroy_tags.rb'
- 'app/graphql/mutations/packages/bulk_destroy.rb'
- 'app/helpers/blame_helper.rb'

View File

@ -3,7 +3,8 @@
class GroupMembersFinder < UnionFinder
RELATIONS = %i[direct inherited descendants shared_from_groups].freeze
DEFAULT_RELATIONS = %i[direct inherited].freeze
INVALID_RELATION_TYPE_ERROR_MSG = "is not a valid relation type. Valid relation types are #{RELATIONS.join(', ')}."
INVALID_RELATION_TYPE_ERROR_MSG =
"is not a valid relation type. Valid relation types are #{RELATIONS.join(', ')}.".freeze
RELATIONS_DESCRIPTIONS = {
direct: 'Members in the group itself',
@ -100,39 +101,9 @@ class GroupMembersFinder < UnionFinder
shared_from_groups = groups[:shared_from_groups]
return members if shared_from_groups.nil?
shared_members = GroupMember.non_request.of_groups(shared_from_groups)
members_shared_with_group_access = members_shared_with_group_access(shared_members)
# `members` and `members_shared_with_group_access` should have even select values
find_union([members.select(group_member_columns), members_shared_with_group_access], GroupMember)
end
def members_shared_with_group_access(shared_members)
group_group_link_table = GroupGroupLink.arel_table
group_member_table = GroupMember.arel_table
member_columns = group_member_columns.map do |column_name|
if column_name == 'access_level'
args = [group_group_link_table[:group_access], group_member_table[:access_level]]
smallest_value_arel(args, 'access_level')
else
group_member_table[column_name]
end
end
# rubocop:disable CodeReuse/ActiveRecord
shared_members
.joins("LEFT OUTER JOIN group_group_links ON members.source_id = group_group_links.shared_with_group_id")
.select(member_columns)
# rubocop:enable CodeReuse/ActiveRecord
end
def group_member_columns
GroupMember.column_names
end
def smallest_value_arel(args, column_alias)
Arel::Nodes::As.new(Arel::Nodes::NamedFunction.new('LEAST', args), Arel::Nodes::SqlLiteral.new(column_alias))
shared_members = GroupMember.non_request.of_groups(shared_from_groups).with_group_group_sharing_access
# `members` and `shared_members` should have even select values
find_union([members.select(Member.column_names), shared_members], GroupMember)
end
def check_relation_arguments!(include_relations)

View File

@ -54,6 +54,12 @@ module Types
value 'JIRA_ASSOCIATION',
value: :jira_association_missing,
description: 'Either the title or description must reference a Jira issue.'
value 'CONFLICT',
value: :conflict,
description: 'There are conflicts between the source and target branches.'
value 'NEED_REBASE',
value: :need_rebase,
description: 'Merge request needs to be rebased.'
end
end
end

View File

@ -376,6 +376,28 @@ class Member < ApplicationRecord
def pluck_user_ids
pluck(:user_id)
end
def with_group_group_sharing_access
joins("LEFT OUTER JOIN group_group_links ON members.source_id = group_group_links.shared_with_group_id")
.select(member_columns_with_group_sharing_access)
end
def member_columns_with_group_sharing_access
group_group_link_table = GroupGroupLink.arel_table
column_names.map do |column_name|
if column_name == 'access_level'
args = [group_group_link_table[:group_access], arel_table[:access_level]]
smallest_value_arel(args, 'access_level')
else
arel_table[column_name]
end
end
end
def smallest_value_arel(args, column_alias)
Arel::Nodes::As.new(Arel::Nodes::NamedFunction.new('LEAST', args), Arel::Nodes::SqlLiteral.new(column_alias))
end
end
def real_source_type

View File

@ -33,7 +33,7 @@ module MergeRequests
attr_reader :merge_request, :checks, :ci_check
def preparing?
merge_request.preparing? && !merge_request.merge_request_diff.persisted?
merge_request.preparing?
end
def checking?
@ -48,7 +48,7 @@ module MergeRequests
strong_memoize(:check_results) do
merge_request
.execute_merge_checks(
MergeRequest.mergeable_state_checks,
MergeRequest.all_mergeability_checks,
params: { skip_ci_check: true }
)
end

View File

@ -31203,11 +31203,13 @@ Detailed representation of whether a GitLab merge request can be merged.
| <a id="detailedmergestatusci_must_pass"></a>`CI_MUST_PASS` | Pipeline must succeed before merging. |
| <a id="detailedmergestatusci_still_running"></a>`CI_STILL_RUNNING` | Pipeline is still running. |
| <a id="detailedmergestatuscommits_status"></a>`COMMITS_STATUS` | Source branch exists and contains commits. |
| <a id="detailedmergestatusconflict"></a>`CONFLICT` | There are conflicts between the source and target branches. |
| <a id="detailedmergestatusdiscussions_not_resolved"></a>`DISCUSSIONS_NOT_RESOLVED` | Discussions must be resolved before merging. |
| <a id="detailedmergestatusdraft_status"></a>`DRAFT_STATUS` | Merge request must not be draft before merging. |
| <a id="detailedmergestatusexternal_status_checks"></a>`EXTERNAL_STATUS_CHECKS` | Status checks must pass. |
| <a id="detailedmergestatusjira_association"></a>`JIRA_ASSOCIATION` | Either the title or description must reference a Jira issue. |
| <a id="detailedmergestatusmergeable"></a>`MERGEABLE` | Branch can be merged. |
| <a id="detailedmergestatusneed_rebase"></a>`NEED_REBASE` | Merge request needs to be rebased. |
| <a id="detailedmergestatusnot_approved"></a>`NOT_APPROVED` | Merge request must be approved before merging. |
| <a id="detailedmergestatusnot_open"></a>`NOT_OPEN` | Merge request must be open before merging. |
| <a id="detailedmergestatuspolicies_denied"></a>`POLICIES_DENIED` | There are denied policies for the merge request. |

View File

@ -1255,3 +1255,132 @@ environment variable due to a possible exploit documented by [CVE-2018-20225](ht
intended to obtain a private package from a private index. This only affects use of the `PIP_EXTRA_INDEX_URL` option, and exploitation
requires that the package does not already exist in the public index (and thus the attacker can put the package there with an arbitrary
version number).
### Version number parsing
In some cases it's not possible to determine if the version of a project dependency is in the affected range of a security advisory.
For example:
- The version is unknown.
- The version is invalid.
- Parsing the version or comparing it to the range fails.
- The version is a branch, like `dev-master` or `1.5.x`.
- The compared versions are ambiguous. For example, `1.0.0-20241502` can't be compared to `1.0.0-2`
because one version contains a timestamp while the other does not.
In these cases, the analyzer skips the dependency and outputs a message to the log.
The GitLab analyzers do not make assumptions as they could result in a false positive or false
negative. For a discussion, see [issue 442027](https://gitlab.com/gitlab-org/gitlab/-/issues/442027).
## Example vulnerability report
The following is an example vulnerability report output by dependency scanning:
```json
{
"version": "2.0",
"vulnerabilities": [
{
"id": "51e83874-0ff6-4677-a4c5-249060554eae",
"category": "dependency_scanning",
"name": "Regular Expression Denial of Service",
"message": "Regular Expression Denial of Service in debug",
"description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.",
"severity": "Unknown",
"solution": "Upgrade to latest versions.",
"scanner": {
"id": "gemnasium",
"name": "Gemnasium"
},
"location": {
"file": "yarn.lock",
"dependency": {
"package": {
"name": "debug"
},
"version": "1.0.5"
}
},
"identifiers": [
{
"type": "gemnasium",
"name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a",
"value": "37283ed4-0380-40d7-ada7-2d994afcc62a",
"url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories"
}
],
"links": [
{
"url": "https://nodesecurity.io/advisories/534"
},
{
"url": "https://github.com/visionmedia/debug/issues/501"
},
{
"url": "https://github.com/visionmedia/debug/pull/504"
}
]
},
{
"id": "5d681b13-e8fa-4668-957e-8d88f932ddc7",
"category": "dependency_scanning",
"name": "Authentication bypass via incorrect DOM traversal and canonicalization",
"message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
"description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment, therefore, has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
"severity": "Unknown",
"solution": "Upgrade to fixed version.\r\n",
"scanner": {
"id": "gemnasium",
"name": "Gemnasium"
},
"location": {
"file": "yarn.lock",
"dependency": {
"package": {
"name": "saml2-js"
},
"version": "1.5.0"
}
},
"identifiers": [
{
"type": "gemnasium",
"name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
"value": "9952e574-7b5b-46fa-a270-aeb694198a98",
"url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
},
{
"type": "cve",
"name": "CVE-2017-11429",
"value": "CVE-2017-11429",
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
}
],
"links": [
{
"url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
},
{
"url": "https://github.com/Clever/saml2/issues/127"
},
{
"url": "https://www.kb.cert.org/vuls/id/475445"
}
]
}
],
"remediations": [
{
"fixes": [
{
"id": "5d681b13-e8fa-4668-957e-8d88f932ddc7",
}
],
"summary": "Upgrade saml2-js",
"diff": "ZGlmZiAtLWdpdCBhL...OR0d1ZUc2THh3UT09Cg==" // some content is omitted for brevity
}
]
}
```

View File

@ -11,39 +11,48 @@ module QA
let(:project_name) { "api-basics-#{SecureRandom.hex(8)}" }
let(:sanitized_project_path) { CGI.escape("#{Runtime::User.username}/#{project_name}") }
let(:file_name) { 'bã®' }
# this file path deliberately includes a subdirectory which matches the file name to verify file/dir matching logic
let(:file_path) { CGI.escape("føo/#{file_name}/føo/#{file_name}") }
it 'user creates a project with a file and deletes them afterwards', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347745' do
create_project_request = Runtime::API::Request.new(@api_client, '/projects')
post create_project_request.url, path: project_name, name: project_name
expect_status(201)
expect(json_body).to match(
a_hash_including(name: project_name, path: project_name)
)
aggregate_failures do
expect_status(201)
expect(json_body).to match(
a_hash_including(name: project_name, path: project_name)
)
end
default_branch = json_body[:default_branch].to_s.empty? ? Runtime::Env.default_branch : json_body[:default_branch]
create_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md")
create_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/#{file_path}")
post create_file_request.url, branch: default_branch, content: 'Hello world', commit_message: 'Add README.md'
expect_status(201)
expect(json_body).to match(
a_hash_including(branch: default_branch, file_path: 'README.md')
)
aggregate_failures do
expect_status(201)
expect(json_body).to match(
a_hash_including(branch: default_branch, file_path: CGI.unescape(file_path))
)
end
get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md", ref: default_branch)
get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/#{file_path}", ref: default_branch)
get get_file_request.url
expect_status(200)
expect(json_body).to match(
a_hash_including(
ref: default_branch,
file_path: 'README.md', file_name: 'README.md',
encoding: 'base64', content: 'SGVsbG8gd29ybGQ='
aggregate_failures do
expect_status(200)
expect(json_body).to match(
a_hash_including(
ref: default_branch,
file_path: CGI.unescape(file_path), file_name: file_name,
encoding: 'base64', content: 'SGVsbG8gd29ybGQ='
)
)
)
end
delete_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md", branch: default_branch, commit_message: 'Remove README.md')
delete_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/#{file_path}", branch: default_branch, commit_message: 'Remove README.md')
delete delete_file_request.url
expect_status(204)
@ -51,16 +60,20 @@ module QA
get_tree_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/tree")
get get_tree_request.url
expect_status(200)
expect(json_body).to eq([])
aggregate_failures do
expect_status(200)
expect(json_body).to eq([])
end
delete_project_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}")
delete delete_project_request.url
expect_status(202)
expect(json_body).to match(
a_hash_including(message: '202 Accepted')
)
aggregate_failures do
expect_status(202)
expect(json_body).to match(
a_hash_including(message: '202 Accepted')
)
end
end
describe 'raw file access' do
@ -111,10 +124,12 @@ module QA
delete_project_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}")
delete delete_project_request.url
expect_status(202)
expect(json_body).to match(
a_hash_including(message: '202 Accepted')
)
aggregate_failures do
expect_status(202)
expect(json_body).to match(
a_hash_including(message: '202 Accepted')
)
end
end
end
end

View File

@ -787,6 +787,37 @@ RSpec.describe Member, feature_category: :groups_and_projects do
end
end
describe '.with_group_group_sharing_access' do
let_it_be(:shared_group) { create(:group) }
let_it_be(:invited_group) { create(:group) }
where(:member_access_in_invited_group, :group_sharing_access) do
Gitlab::Access::REPORTER | Gitlab::Access::DEVELOPER
Gitlab::Access::DEVELOPER | Gitlab::Access::REPORTER
end
with_them do
before do
create(:group_group_link,
shared_group: shared_group,
shared_with_group: invited_group,
group_access: group_sharing_access)
end
let(:member) { create(:group_member, source: invited_group, access_level: member_access_in_invited_group) }
it 'returns the minimum of member access level and group sharing access level' do
access_level = invited_group
.members
.with_group_group_sharing_access
.find(member.id)
.access_level
expect(access_level).to eq(Gitlab::Access::REPORTER)
end
end
end
describe '#accept_request' do
let(:member) { create(:project_member, requested_at: Time.current.utc) }

View File

@ -5,6 +5,16 @@ require 'spec_helper'
RSpec.describe ::MergeRequests::Mergeability::DetailedMergeStatusService, feature_category: :code_review_workflow do
subject(:detailed_merge_status) { described_class.new(merge_request: merge_request).execute }
let(:merge_request) { create(:merge_request) }
it 'calls every mergeability check' do
expect(merge_request).to receive(:execute_merge_checks)
.with(MergeRequest.all_mergeability_checks, any_args)
.and_call_original
detailed_merge_status
end
context 'when merge status is cannot_be_merged_rechecking' do
let(:merge_request) { create(:merge_request, merge_status: :cannot_be_merged_rechecking) }
@ -23,16 +33,6 @@ RSpec.describe ::MergeRequests::Mergeability::DetailedMergeStatusService, featur
end
end
context 'when merge status is preparing and merge request diff is persisted' do
let(:merge_request) { create(:merge_request, merge_status: :preparing) }
it 'returns :checking' do
allow(merge_request.merge_request_diff).to receive(:persisted?).and_return(true)
expect(detailed_merge_status).to eq(:mergeable)
end
end
context 'when merge status is checking' do
let(:merge_request) { create(:merge_request, merge_status: :checking) }