Commit fc03cb11 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents 6f83af4d ecb05e59
...@@ -162,7 +162,8 @@ export default { ...@@ -162,7 +162,8 @@ export default {
removeWIPPath: store.removeWIPPath, removeWIPPath: store.removeWIPPath,
sourceBranchPath: store.sourceBranchPath, sourceBranchPath: store.sourceBranchPath,
ciEnvironmentsStatusPath: store.ciEnvironmentsStatusPath, ciEnvironmentsStatusPath: store.ciEnvironmentsStatusPath,
statusPath: store.statusPath, mergeRequestBasicPath: store.mergeRequestBasicPath,
mergeRequestWidgetPath: store.mergeRequestWidgetPath,
mergeActionsContentPath: store.mergeActionsContentPath, mergeActionsContentPath: store.mergeActionsContentPath,
rebasePath: store.rebasePath, rebasePath: store.rebasePath,
}; };
......
...@@ -30,11 +30,11 @@ export default class MRWidgetService { ...@@ -30,11 +30,11 @@ export default class MRWidgetService {
} }
poll() { poll() {
return axios.get(`${this.endpoints.statusPath}?serializer=basic`); return axios.get(this.endpoints.mergeRequestBasicPath);
} }
checkStatus() { checkStatus() {
return axios.get(`${this.endpoints.statusPath}?serializer=widget`); return axios.get(this.endpoints.mergeRequestWidgetPath);
} }
fetchMergeActionsContent() { fetchMergeActionsContent() {
......
...@@ -86,7 +86,8 @@ export default class MergeRequestStore { ...@@ -86,7 +86,8 @@ export default class MergeRequestStore {
this.mergePath = data.merge_path; this.mergePath = data.merge_path;
this.ffOnlyEnabled = data.ff_only_enabled; this.ffOnlyEnabled = data.ff_only_enabled;
this.shouldBeRebased = Boolean(data.should_be_rebased); this.shouldBeRebased = Boolean(data.should_be_rebased);
this.statusPath = data.status_path; this.mergeRequestBasicPath = data.merge_request_basic_path;
this.mergeRequestWidgetPath = data.merge_request_widget_path;
this.emailPatchesPath = data.email_patches_path; this.emailPatchesPath = data.email_patches_path;
this.plainDiffPath = data.plain_diff_path; this.plainDiffPath = data.plain_diff_path;
this.newBlobPath = data.new_blob_path; this.newBlobPath = data.new_blob_path;
......
...@@ -51,6 +51,13 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont ...@@ -51,6 +51,13 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
Ci::Pipeline.none Ci::Pipeline.none
end end
end end
def close_merge_request_if_no_source_project
return if @merge_request.source_project
return unless @merge_request.open?
@merge_request.close
end
end end
Projects::MergeRequests::ApplicationController.prepend(EE::Projects::MergeRequests::ApplicationController) Projects::MergeRequests::ApplicationController.prepend(EE::Projects::MergeRequests::ApplicationController)
# frozen_string_literal: true
class Projects::MergeRequests::ContentController < Projects::MergeRequests::ApplicationController
# @merge_request.check_mergeability is not executed here since
# widget serializer calls it via mergeable? method
# but we might want to call @merge_request.check_mergeability
# for other types of serialization
before_action :close_merge_request_if_no_source_project
around_action :allow_gitaly_ref_name_caching
def widget
respond_to do |format|
format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
serializer = MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
render json: serializer.represent(merge_request, serializer: 'widget')
end
end
end
end
...@@ -235,12 +235,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -235,12 +235,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
params[:auto_merge_strategy].present? || params[:merge_when_pipeline_succeeds].present? params[:auto_merge_strategy].present? || params[:merge_when_pipeline_succeeds].present?
end end
def close_merge_request_if_no_source_project
if !@merge_request.source_project && @merge_request.open?
@merge_request.close
end
end
private private
def ci_environments_status_on_merge_result? def ci_environments_status_on_merge_result?
......
# frozen_string_literal: true
module Types
class CommitType < BaseObject
graphql_name 'Commit'
authorize :download_code
present_using CommitPresenter
field :id, type: GraphQL::ID_TYPE, null: false
field :sha, type: GraphQL::STRING_TYPE, null: false
field :title, type: GraphQL::STRING_TYPE, null: true
field :description, type: GraphQL::STRING_TYPE, null: true
field :message, type: GraphQL::STRING_TYPE, null: true
field :authored_date, type: Types::TimeType, null: true
field :web_url, type: GraphQL::STRING_TYPE, null: false
# models/commit lazy loads the author by email
field :author, type: Types::UserType, null: true
field :latest_pipeline,
type: Types::Ci::PipelineType,
null: true,
description: "Latest pipeline for this commit",
resolve: -> (obj, ctx, args) do
Gitlab::Graphql::Loaders::PipelineForShaLoader.new(obj.project, obj.sha).find_last
end
end
end
...@@ -4,6 +4,11 @@ module Types ...@@ -4,6 +4,11 @@ module Types
class TreeType < BaseObject class TreeType < BaseObject
graphql_name 'Tree' graphql_name 'Tree'
# Complexity 10 as it triggers a Gitaly call on each render
field :last_commit, Types::CommitType, null: true, complexity: 10, resolve: -> (tree, args, ctx) do
tree.repository.last_commit_for_path(tree.sha, tree.path)
end
field :trees, Types::Tree::TreeEntryType.connection_type, null: false, resolve: -> (obj, args, ctx) do field :trees, Types::Tree::TreeEntryType.connection_type, null: false, resolve: -> (obj, args, ctx) do
Gitlab::Graphql::Representation::TreeEntry.decorate(obj.trees, obj.repository) Gitlab::Graphql::Representation::TreeEntry.decorate(obj.trees, obj.repository)
end end
......
...@@ -295,6 +295,11 @@ module Ci ...@@ -295,6 +295,11 @@ module Ci
end end
end end
def self.latest_for_shas(shas)
max_id_per_sha = for_sha(shas).group(:sha).select("max(id)")
where(id: max_id_per_sha)
end
def self.latest_successful_ids_per_project def self.latest_successful_ids_per_project
success.group(:project_id).select('max(id) as id') success.group(:project_id).select('max(id) as id')
end end
......
...@@ -25,7 +25,7 @@ module RelativePositioning ...@@ -25,7 +25,7 @@ module RelativePositioning
relative_position = position_between(max_relative_position, MAX_POSITION) relative_position = position_between(max_relative_position, MAX_POSITION)
object.relative_position = relative_position object.relative_position = relative_position
max_relative_position = relative_position max_relative_position = relative_position
object.save object.save(touch: false)
end end
end end
end end
...@@ -159,7 +159,7 @@ module RelativePositioning ...@@ -159,7 +159,7 @@ module RelativePositioning
def save_positionable_neighbours def save_positionable_neighbours
return unless @positionable_neighbours return unless @positionable_neighbours
status = @positionable_neighbours.all?(&:save) status = @positionable_neighbours.all? { |issue| issue.save(touch: false) }
@positionable_neighbours = nil @positionable_neighbours = nil
status status
......
# frozen_string_literal: true # frozen_string_literal: true
class CommitPresenter < Gitlab::View::Presenter::Simple class CommitPresenter < Gitlab::View::Presenter::Delegated
include GlobalID::Identification
presents :commit presents :commit
def status_for(ref) def status_for(ref)
...@@ -10,4 +12,8 @@ class CommitPresenter < Gitlab::View::Presenter::Simple ...@@ -10,4 +12,8 @@ class CommitPresenter < Gitlab::View::Presenter::Simple
def any_pipelines? def any_pipelines?
can?(current_user, :read_pipeline, commit.project) && commit.pipelines.any? can?(current_user, :read_pipeline, commit.project) && commit.pipelines.any?
end end
def web_url
Gitlab::UrlBuilder.new(commit).url
end
end end
...@@ -219,8 +219,12 @@ class MergeRequestWidgetEntity < IssuableEntity ...@@ -219,8 +219,12 @@ class MergeRequestWidgetEntity < IssuableEntity
project_merge_request_path(merge_request.project, merge_request, format: :diff) project_merge_request_path(merge_request.project, merge_request, format: :diff)
end end
expose :status_path do |merge_request| expose :merge_request_basic_path do |merge_request|
project_merge_request_path(merge_request.target_project, merge_request, format: :json) project_merge_request_path(merge_request.target_project, merge_request, serializer: :basic, format: :json)
end
expose :merge_request_widget_path do |merge_request|
widget_project_json_merge_request_path(merge_request.target_project, merge_request, format: :json)
end end
expose :ci_environments_status_path do |merge_request| expose :ci_environments_status_path do |merge_request|
......
...@@ -205,7 +205,7 @@ class IssuableBaseService < BaseService ...@@ -205,7 +205,7 @@ class IssuableBaseService < BaseService
end end
if issuable.changed? || params.present? if issuable.changed? || params.present?
issuable.assign_attributes(params.merge(updated_by: current_user)) issuable.assign_attributes(params)
if has_title_or_description_changed?(issuable) if has_title_or_description_changed?(issuable)
issuable.assign_attributes(last_edited_at: Time.now, last_edited_by: current_user) issuable.assign_attributes(last_edited_at: Time.now, last_edited_by: current_user)
...@@ -213,11 +213,16 @@ class IssuableBaseService < BaseService ...@@ -213,11 +213,16 @@ class IssuableBaseService < BaseService
before_update(issuable) before_update(issuable)
# Do not touch when saving the issuable if only changes position within a list. We should call
# this method at this point to capture all possible changes.
should_touch = update_timestamp?(issuable)
issuable.updated_by = current_user if should_touch
# We have to perform this check before saving the issuable as Rails resets # We have to perform this check before saving the issuable as Rails resets
# the changed fields upon calling #save. # the changed fields upon calling #save.
update_project_counters = issuable.project && update_project_counter_caches?(issuable) update_project_counters = issuable.project && update_project_counter_caches?(issuable)
if issuable.with_transaction_returning_status { issuable.save } if issuable.with_transaction_returning_status { issuable.save(touch: should_touch) }
# We do not touch as it will affect a update on updated_at field # We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do ActiveRecord::Base.no_touching do
Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels]) Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels])
...@@ -402,6 +407,10 @@ class IssuableBaseService < BaseService ...@@ -402,6 +407,10 @@ class IssuableBaseService < BaseService
def ensure_milestone_available(issuable) def ensure_milestone_available(issuable)
issuable.milestone_id = nil unless issuable.milestone_available? issuable.milestone_id = nil unless issuable.milestone_available?
end end
def update_timestamp?(issuable)
issuable.changes.keys != ["relative_position"]
end
end end
IssuableBaseService.prepend(EE::IssuableBaseService) IssuableBaseService.prepend(EE::IssuableBaseService)
---
title: Will not update issue timestamps when changing positions in a list
merge_request: 29677
author:
type: changed
---
title: Added commit type to tree GraphQL response
merge_request: 29412
author:
type: added
---
title: Add a separate endpoint for fetching MRs serialized as widgets
merge_request: 29979
author:
type: performance
---
title: Support CIDR notation in IP rate limiter
merge_request: 30146
author:
type: changed
...@@ -261,6 +261,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -261,6 +261,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :commits get :commits
get :pipelines get :pipelines
get :diffs, to: 'merge_requests/diffs#show' get :diffs, to: 'merge_requests/diffs#show'
get :widget, to: 'merge_requests/content#widget'
end end
get :diff_for_path, controller: 'merge_requests/diffs' get :diff_for_path, controller: 'merge_requests/diffs'
......
...@@ -53,8 +53,9 @@ For more information on how to use these options check out ...@@ -53,8 +53,9 @@ For more information on how to use these options check out
The following settings can be configured: The following settings can be configured:
- `enabled`: By default this is set to `false`. Set this to `true` to enable Rack Attack. - `enabled`: By default this is set to `false`. Set this to `true` to enable Rack Attack.
- `ip_whitelist`: Whitelist any IPs from being blocked. They must be formatted as strings within a ruby array. - `ip_whitelist`: Whitelist any IPs from being blocked. They must be formatted as strings within a Ruby array.
For example, `["127.0.0.1", "127.0.0.2", "127.0.0.3"]`. CIDR notation is supported in GitLab v12.1 and up.
For example, `["127.0.0.1", "127.0.0.2", "127.0.0.3", "192.168.0.1/24"]`.
- `maxretry`: The maximum amount of times a request can be made in the - `maxretry`: The maximum amount of times a request can be made in the
specified time. specified time.
- `findtime`: The maximum amount of time that failed requests can count against an IP - `findtime`: The maximum amount of time that failed requests can count against an IP
......
...@@ -206,6 +206,11 @@ vulnerabilities in your groups and projects. Read more about the ...@@ -206,6 +206,11 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities). [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
## Vulnerabilities database update
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
## Troubleshooting ## Troubleshooting
### docker: Error response from daemon: failed to copy xattrs ### docker: Error response from daemon: failed to copy xattrs
......
...@@ -259,3 +259,8 @@ vulnerabilities in your groups and projects. Read more about the ...@@ -259,3 +259,8 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities). [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
## Vulnerabilities database update
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
...@@ -404,6 +404,11 @@ vulnerabilities in your groups and projects. Read more about the ...@@ -404,6 +404,11 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities). [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
## Vulnerabilities database update
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
## Dependency List ## Dependency List
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10075) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10075) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
......
...@@ -10,7 +10,7 @@ high-level view on projects and groups, and start remediation processes when nee ...@@ -10,7 +10,7 @@ high-level view on projects and groups, and start remediation processes when nee
GitLab can scan and report any vulnerabilities found in your project. GitLab can scan and report any vulnerabilities found in your project.
| Secure scanning tools | Description | | Secure scanning tool | Description |
|:-----------------------------------------------------------------------------|:-----------------------------------------------------------------------| |:-----------------------------------------------------------------------------|:-----------------------------------------------------------------------|
| [Container Scanning](container_scanning/index.md) **[ULTIMATE]** | Scan Docker containers for known vulnerabilities. | | [Container Scanning](container_scanning/index.md) **[ULTIMATE]** | Scan Docker containers for known vulnerabilities. |
| [Dependency Scanning](dependency_scanning/index.md) **[ULTIMATE]** | Analyze your dependencies for known vulnerabilities. | | [Dependency Scanning](dependency_scanning/index.md) **[ULTIMATE]** | Analyze your dependencies for known vulnerabilities. |
...@@ -19,6 +19,29 @@ GitLab can scan and report any vulnerabilities found in your project. ...@@ -19,6 +19,29 @@ GitLab can scan and report any vulnerabilities found in your project.
| [Security Dashboard](security_dashboard/index.md) **[ULTIMATE]** | View vulnerabilities in all your projects and groups. | | [Security Dashboard](security_dashboard/index.md) **[ULTIMATE]** | View vulnerabilities in all your projects and groups. |
| [Static Application Security Testing (SAST)](sast/index.md) **[ULTIMATE]** | Analyze source code for known vulnerabilities. | | [Static Application Security Testing (SAST)](sast/index.md) **[ULTIMATE]** | Analyze source code for known vulnerabilities. |
## Maintenance and update of the vulnerabilities database
The various scanning tools and the vulnerabilities database are updated regularly.
| Secure scanning tool | Vulnerabilities database updates |
|:-------------------------------------------------------------|-------------------------------------------|
| [Container Scanning](container_scanning/index.md) | Uses `clair` underneath and the latest `clair-db` version is used for each job run by running the [`latest` docker image tag](https://gitlab.com/gitlab-org/gitlab-ee/blob/438a0a56dc0882f22bdd82e700554525f552d91b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml#L37). The `clair-db` database [is updated daily according to the author](https://github.com/arminc/clair-local-scan#clair-server-or-local). |
| [Dependency Scanning](dependency_scanning/index.md) | Relies on `bundler-audit` (for Rubygems), `retire.js` (for NPM packages) and `gemnasium` (GitLab's own tool for all libraries). `bundler-audit` and `retire.js` both fetch their vulnerabilities data from GitHub repositories, so vulnerabilities added to `ruby-advisory-db` and `retire.js` are immediately available. The tools themselves are updated once per month if there's a new version. The [Gemnasium DB](https://gitlab.com/gitlab-org/security-products/gemnasium-db) is updated at least once a week. |
| [Dynamic Application Security Testing (DAST)](dast/index.md) | Updated weekly on Sundays. The underlying tool, `zaproxy`, downloads fresh rules at startup. |
| [Static Application Security Testing (SAST)](sast/index.md) | Relies exclusively on [the tools GitLab is wrapping](sast/index.md#supported-languages-and-frameworks). The underlying analyzers are updated at least once per month if a relevant update is available. The vulnerabilities database is updated by the upstream tools. |
You don't have to update GitLab to benefit from the latest vulnerabilities definitions,
but you may have to in the future.
The security tools are released as Docker images, and the vendored job definitions
to enable them are using the `x-y-stable` image tags that get overridden each time a new
release of the tools is pushed. The Docker images are updated to match the
previous GitLab releases, so they automatically get the latest versions of the
scanning tools without the user having to do anything.
This workflow comes with some drawbacks and there's a
[plan to change this](https://gitlab.com/gitlab-org/gitlab-ee/issues/9725).
## Interacting with the vulnerabilities ## Interacting with the vulnerabilities
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 10.8. > Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 10.8.
......
...@@ -269,7 +269,7 @@ it highlighted: ...@@ -269,7 +269,7 @@ it highlighted:
"url": "https://cwe.mitre.org/data/definitions/330.html" "url": "https://cwe.mitre.org/data/definitions/330.html"
} }
] ]
}, },
{ {
"category": "sast", "category": "sast",
"message": "Probable insecure usage of temp file/directory.", "message": "Probable insecure usage of temp file/directory.",
...@@ -296,7 +296,7 @@ it highlighted: ...@@ -296,7 +296,7 @@ it highlighted:
"url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html" "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
} }
] ]
}, },
], ],
"remediations": [] "remediations": []
} }
...@@ -320,7 +320,7 @@ the report JSON unless stated otherwise. Presence of optional fields depends on ...@@ -320,7 +320,7 @@ the report JSON unless stated otherwise. Presence of optional fields depends on
| `vulnerabilities[].scanner` | A node that describes the analyzer used to find this vulnerability. | | `vulnerabilities[].scanner` | A node that describes the analyzer used to find this vulnerability. |
| `vulnerabilities[].scanner.id` | Id of the scanner as a snake_case string. | | `vulnerabilities[].scanner.id` | Id of the scanner as a snake_case string. |
| `vulnerabilities[].scanner.name` | Name of the scanner, for display purposes. | | `vulnerabilities[].scanner.name` | Name of the scanner, for display purposes. |
| `vulnerabilities[].location` | A node that tells where the vulnerability is located. | | `vulnerabilities[].location` | A node that tells where the vulnerability is located. |
| `vulnerabilities[].location.file` | Path to the file where the vulnerability is located. Optional. | | `vulnerabilities[].location.file` | Path to the file where the vulnerability is located. Optional. |
| `vulnerabilities[].location.start_line` | The first line of the code affected by the vulnerability. Optional. | | `vulnerabilities[].location.start_line` | The first line of the code affected by the vulnerability. Optional. |
| `vulnerabilities[].location.end_line` | The last line of the code affected by the vulnerability. Optional. | | `vulnerabilities[].location.end_line` | The last line of the code affected by the vulnerability. Optional. |
...@@ -330,7 +330,7 @@ the report JSON unless stated otherwise. Presence of optional fields depends on ...@@ -330,7 +330,7 @@ the report JSON unless stated otherwise. Presence of optional fields depends on
| `vulnerabilities[].identifiers[].type` | Type of the identifier. Possible values: common identifier types (among `cve`, `cwe`, `osvdb`, and `usn`) or analyzer-dependent ones (e.g., `bandit_test_id` for [Bandit analyzer](https://wiki.openstack.org/wiki/Security/Projects/Bandit)). | | `vulnerabilities[].identifiers[].type` | Type of the identifier. Possible values: common identifier types (among `cve`, `cwe`, `osvdb`, and `usn`) or analyzer-dependent ones (e.g., `bandit_test_id` for [Bandit analyzer](https://wiki.openstack.org/wiki/Security/Projects/Bandit)). |
| `vulnerabilities[].identifiers[].name` | Name of the identifier for display purposes. | | `vulnerabilities[].identifiers[].name` | Name of the identifier for display purposes. |
| `vulnerabilities[].identifiers[].value` | Value of the identifier for matching purposes. | | `vulnerabilities[].identifiers[].value` | Value of the identifier for matching purposes. |
| `vulnerabilities[].identifiers[].url` | URL to identifier's documentation. Optional. | | `vulnerabilities[].identifiers[].url` | URL to identifier's documentation. Optional. |
## Secret detection ## Secret detection
...@@ -363,3 +363,8 @@ vulnerabilities in your groups and projects. Read more about the ...@@ -363,3 +363,8 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities). [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
## Vulnerabilities database update
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
module Gitlab module Gitlab
module Auth module Auth
class IpRateLimiter class IpRateLimiter
include ::Gitlab::Utils::StrongMemoize
attr_reader :ip attr_reader :ip
def initialize(ip) def initialize(ip)
...@@ -37,7 +39,20 @@ module Gitlab ...@@ -37,7 +39,20 @@ module Gitlab
end end
def ip_can_be_banned? def ip_can_be_banned?
config.ip_whitelist.exclude?(ip) !trusted_ip?
end
def trusted_ip?
trusted_ips.any? { |netmask| netmask.include?(ip) }
end
def trusted_ips
strong_memoize(:trusted_ips) do
config.ip_whitelist.map do |proxy|
IPAddr.new(proxy)
rescue IPAddr::InvalidAddressError
end.compact
end
end end
end end
end end
......
# frozen_string_literal: true
module Gitlab
module Graphql
module Loaders
class PipelineForShaLoader
attr_accessor :project, :sha
def initialize(project, sha)
@project, @sha = project, sha
end
def find_last
BatchLoader.for(sha).batch(key: project) do |shas, loader, args|
pipelines = args[:key].ci_pipelines.latest_for_shas(shas)
pipelines.each do |pipeline|
loader.call(pipeline.sha, pipeline)
end
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Projects::MergeRequests::ContentController do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request, target_project: project, source_project: project) }
before do
sign_in(user)
end
def do_request
get :widget, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.iid,
format: :json
}
end
describe 'GET widget' do
context 'user has access to the project' do
before do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
project.add_maintainer(user)
end
it 'renders widget MR entity as json' do
do_request
expect(response).to match_response_schema('entities/merge_request_widget')
end
it 'checks whether the MR can be merged' do
controller.instance_variable_set(:@merge_request, merge_request)
expect(merge_request).to receive(:check_mergeability)
do_request
end
it 'closes an MR with moved source project' do
merge_request.update_column(:source_project_id, nil)
expect { do_request }.to change { merge_request.reload.open? }.from(true).to(false)
end
end
context 'user does not have access to the project' do
it 'renders widget MR entity as json' do
do_request
expect(response).to have_http_status(:not_found)
end
end
end
end
...@@ -99,7 +99,8 @@ ...@@ -99,7 +99,8 @@
"revert_in_fork_path": { "type": ["string", "null"] }, "revert_in_fork_path": { "type": ["string", "null"] },
"email_patches_path": { "type": "string" }, "email_patches_path": { "type": "string" },
"plain_diff_path": { "type": "string" }, "plain_diff_path": { "type": "string" },
"status_path": { "type": "string" }, "merge_request_basic_path": { "type": "string" },
"merge_request_widget_path": { "type": "string" },
"new_blob_path": { "type": ["string", "null"] }, "new_blob_path": { "type": ["string", "null"] },
"merge_check_path": { "type": "string" }, "merge_check_path": { "type": "string" },
"ci_environments_status_path": { "type": "string" }, "ci_environments_status_path": { "type": "string" },
......
...@@ -113,7 +113,7 @@ describe GitlabSchema do ...@@ -113,7 +113,7 @@ describe GitlabSchema do
end end
it "raises a meaningful error if a global id couldn't be generated" do it "raises a meaningful error if a global id couldn't be generated" do
expect { described_class.id_from_object(build(:commit)) } expect { described_class.id_from_object(build(:wiki_directory)) }
.to raise_error(RuntimeError, /include `GlobalID::Identification` into/i) .to raise_error(RuntimeError, /include `GlobalID::Identification` into/i)
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['Commit'] do
it { expect(described_class.graphql_name).to eq('Commit') }
it { expect(described_class).to require_graphql_authorizations(:download_code) }
it { expect(described_class).to have_graphql_fields(:id, :sha, :title, :description, :message, :authored_date, :author, :web_url, :latest_pipeline) }
end
...@@ -5,5 +5,5 @@ require 'spec_helper' ...@@ -5,5 +5,5 @@ require 'spec_helper'
describe Types::Tree::TreeType do describe Types::Tree::TreeType do
it { expect(described_class.graphql_name).to eq('Tree') } it { expect(described_class.graphql_name).to eq('Tree') }
it { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs) } it { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs, :last_commit) }
end end
...@@ -218,7 +218,8 @@ export default { ...@@ -218,7 +218,8 @@ export default {
'/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1', '/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
email_patches_path: '/root/acets-app/merge_requests/22.patch', email_patches_path: '/root/acets-app/merge_requests/22.patch',
plain_diff_path: '/root/acets-app/merge_requests/22.diff', plain_diff_path: '/root/acets-app/merge_requests/22.diff',
status_path: '/root/acets-app/merge_requests/22.json', merge_request_basic_path: '/root/acets-app/merge_requests/22.json?serializer=basic',
merge_request_widget_path: '/root/acets-app/merge_requests/22/widget.json',
merge_check_path: '/root/acets-app/merge_requests/22/merge_check', merge_check_path: '/root/acets-app/merge_requests/22/merge_check',
ci_environments_status_url: '/root/acets-app/merge_requests/22/ci_environments_status', ci_environments_status_url: '/root/acets-app/merge_requests/22/ci_environments_status',
project_archived: false, project_archived: false,
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_caching do
let(:ip) { '10.2.2.3' }
let(:whitelist) { ['127.0.0.1'] }
let(:options) do
{
enabled: true,
ip_whitelist: whitelist,
bantime: 1.minute,
findtime: 1.minute,
maxretry: 2
}
end
subject { described_class.new(ip) }
before do
stub_rack_attack_setting(options)
end
after do
subject.reset!
end
describe '#register_fail!' do
it 'bans after 3 consecutive failures' do
expect(subject.banned?).to be_falsey
3.times { subject.register_fail! }
expect(subject.banned?).to be_truthy
end
shared_examples 'whitelisted IPs' do
it 'does not ban after max retry limit' do
expect(subject.banned?).to be_falsey
3.times { subject.register_fail! }
expect(subject.banned?).to be_falsey
end
end
context 'with a whitelisted netmask' do
before do
options[:ip_whitelist] = ['127.0.0.1', '10.2.2.0/24', 'bad']
stub_rack_attack_setting(options)
end
it_behaves_like 'whitelisted IPs'
end
context 'with a whitelisted IP' do
before do
options[:ip_whitelist] = ['10.2.2.3']
stub_rack_attack_setting(options)
end
it_behaves_like 'whitelisted IPs'
end
end
end
require 'spec_helper'
describe Gitlab::Graphql::Loaders::PipelineForShaLoader do
include GraphqlHelpers
describe '#find_last' do
it 'batch-resolves latest pipeline' do
project = create(:project, :repository)
pipeline1 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha)
pipeline2 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha)
pipeline3 = create(:ci_pipeline, project: project, ref: 'improve/awesome', sha: project.commit('improve/awesome').sha)
result = batch(max_queries: 1) do
[pipeline1.sha, pipeline3.sha].map { |sha| described_class.new(project, sha).find_last }
end
expect(result).to contain_exactly(pipeline2, pipeline3)
end
end
end
...@@ -1886,6 +1886,17 @@ describe Ci::Pipeline, :mailer do ...@@ -1886,6 +1886,17 @@ describe Ci::Pipeline, :mailer do
end end
end end
describe '.latest_for_shas' do
let(:sha) { 'abc' }
it 'returns latest pipeline for sha' do
create(:ci_pipeline, sha: sha)
pipeline2 = create(:ci_pipeline, sha: sha)
expect(described_class.latest_for_shas(sha)).to contain_exactly(pipeline2)
end
end
describe '.latest_successful_ids_per_project' do describe '.latest_successful_ids_per_project' do
let(:projects) { create_list(:project, 2) } let(:projects) { create_list(:project, 2) }
let!(:pipeline1) { create(:ci_pipeline, :success, project: projects[0]) } let!(:pipeline1) { create(:ci_pipeline, :success, project: projects[0]) }
......
...@@ -33,6 +33,12 @@ describe 'getting a tree in a project' do ...@@ -33,6 +33,12 @@ describe 'getting a tree in a project' do
expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([]) expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([])
expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([]) expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([])
end end
it 'returns null commit' do
post_graphql(query, current_user: current_user)
expect(graphql_data['project']['repository']['last_commit']).to be_nil
end
end end
context 'when ref does not exist' do context 'when ref does not exist' do
...@@ -45,6 +51,12 @@ describe 'getting a tree in a project' do ...@@ -45,6 +51,12 @@ describe 'getting a tree in a project' do
expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([]) expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([])
expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([]) expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([])
end end
it 'returns null commit' do
post_graphql(query, current_user: current_user)
expect(graphql_data['project']['repository']['last_commit']).to be_nil
end
end end
context 'when ref and path exist' do context 'when ref and path exist' do
...@@ -61,6 +73,12 @@ describe 'getting a tree in a project' do ...@@ -61,6 +73,12 @@ describe 'getting a tree in a project' do
expect(graphql_data['project']['repository']['tree']['blobs']['edges'].size).to be > 0 expect(graphql_data['project']['repository']['tree']['blobs']['edges'].size).to be > 0
expect(graphql_data['project']['repository']['tree']['submodules']['edges'].size).to be > 0 expect(graphql_data['project']['repository']['tree']['submodules']['edges'].size).to be > 0
end end
it 'returns tree latest commit' do
post_graphql(query, current_user: current_user)
expect(graphql_data['project']['repository']['tree']['lastCommit']).to be_present
end
end end
context 'when current user is nil' do context 'when current user is nil' do
......
...@@ -95,6 +95,11 @@ module StubConfiguration ...@@ -95,6 +95,11 @@ module StubConfiguration
allow(Gitlab.config.gitlab_shell).to receive_messages(to_settings(messages)) allow(Gitlab.config.gitlab_shell).to receive_messages(to_settings(messages))
end end
def stub_rack_attack_setting(messages)
allow(Gitlab.config.rack_attack).to receive(:git_basic_auth).and_return(messages)
allow(Gitlab.config.rack_attack.git_basic_auth).to receive_messages(to_settings(messages))
end
private private
# Modifies stubbed messages to also stub possible predicate versions # Modifies stubbed messages to also stub possible predicate versions
......
shared_examples 'issues move service' do |group| shared_examples 'issues move service' do |group|
shared_examples 'updating timestamps' do
it 'updates updated_at' do
expect {described_class.new(parent, user, params).execute(issue)}
.to change {issue.reload.updated_at}
end
end
context 'when moving an issue between lists' do context 'when moving an issue between lists' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } } let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
it_behaves_like 'updating timestamps'
it 'delegates the label changes to Issues::UpdateService' do it 'delegates the label changes to Issues::UpdateService' do
service = double(:service) service = double(:service)
expect(Issues::UpdateService).to receive(:new).and_return(service) expect(Issues::UpdateService).to receive(:new).and_return(service)
...@@ -24,6 +33,8 @@ shared_examples 'issues move service' do |group| ...@@ -24,6 +33,8 @@ shared_examples 'issues move service' do |group|
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) } let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) }
let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } } let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } }
it_behaves_like 'updating timestamps'
it 'delegates the close proceedings to Issues::CloseService' do it 'delegates the close proceedings to Issues::CloseService' do
expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once
...@@ -46,6 +57,8 @@ shared_examples 'issues move service' do |group| ...@@ -46,6 +57,8 @@ shared_examples 'issues move service' do |group|
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression], milestone: milestone) } let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression], milestone: milestone) }
let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: backlog.id } } let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: backlog.id } }
it_behaves_like 'updating timestamps'
it 'keeps labels and milestone' do it 'keeps labels and milestone' do
described_class.new(parent, user, params).execute(issue) described_class.new(parent, user, params).execute(issue)
issue.reload issue.reload
...@@ -59,6 +72,8 @@ shared_examples 'issues move service' do |group| ...@@ -59,6 +72,8 @@ shared_examples 'issues move service' do |group|
let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) }
let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } } let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } }
it_behaves_like 'updating timestamps'
it 'delegates the re-open proceedings to Issues::ReopenService' do it 'delegates the re-open proceedings to Issues::ReopenService' do
expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once
...@@ -75,10 +90,13 @@ shared_examples 'issues move service' do |group| ...@@ -75,10 +90,13 @@ shared_examples 'issues move service' do |group|
end end
context 'when moving to same list' do context 'when moving to same list' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } let(:assignee) { create(:user) }
let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) } let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) } let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } } let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:issue) do
create(:labeled_issue, project: project, labels: [bug, development], assignees: [assignee])
end
it 'returns false' do it 'returns false' do
expect(described_class.new(parent, user, params).execute(issue)).to eq false expect(described_class.new(parent, user, params).execute(issue)).to eq false
...@@ -90,18 +108,36 @@ shared_examples 'issues move service' do |group| ...@@ -90,18 +108,36 @@ shared_examples 'issues move service' do |group|
expect(issue.reload.labels).to contain_exactly(bug, development) expect(issue.reload.labels).to contain_exactly(bug, development)
end end
it 'sorts issues' do it 'keeps issues assignees' do
[issue, issue1, issue2].each do |issue| described_class.new(parent, user, params).execute(issue)
issue.move_to_end && issue.save!
end expect(issue.reload.assignees).to contain_exactly(assignee)
end
params.merge!(move_after_id: issue1.id, move_before_id: issue2.id) it 'sorts issues' do
reorder_issues(params, issues: [issue, issue1, issue2])
described_class.new(parent, user, params).execute(issue) described_class.new(parent, user, params).execute(issue)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
end end
it 'does not update updated_at' do
reorder_issues(params, issues: [issue, issue1, issue2])
updated_at = issue.updated_at
updated_at1 = issue1.updated_at
updated_at2 = issue2.updated_at
Timecop.travel(1.minute.from_now) do
described_class.new(parent, user, params).execute(issue)
end
expect(issue.reload.updated_at.change(usec: 0)).to eq updated_at.change(usec: 0)
expect(issue1.reload.updated_at.change(usec: 0)).to eq updated_at1.change(usec: 0)
expect(issue2.reload.updated_at.change(usec: 0)).to eq updated_at2.change(usec: 0)
end
if group if group
context 'when on a group board' do context 'when on a group board' do
it 'sends the board_group_id parameter' do it 'sends the board_group_id parameter' do
...@@ -114,5 +150,13 @@ shared_examples 'issues move service' do |group| ...@@ -114,5 +150,13 @@ shared_examples 'issues move service' do |group|
end end
end end
end end
def reorder_issues(params, issues: [])
issues.each do |issue|
issue.move_to_end && issue.save!
end
params.merge!(move_after_id: issues[1].id, move_before_id: issues[2].id)
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment