Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1ea1a786c4
commit
1912008140
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue