Commit 21abe22f authored by Steve Azzopardi's avatar Steve Azzopardi

Merge branch 'master' into ce-to-ee-2018-11-29

parents 0cd09ba2 5db991b8
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 11.5.1 (2018-11-26)
### Security (6 changes)
- Sanitize tracing external_urls before saving to DB and when displaying the URL to prevent XSS issues.
- Prevent reporter roles from viewing the Jaeger tracing settings page.
- Fix IDOR at /drafts/publish.
- Authorize users when listing board users and milestones.
- Resolve: Guest can set weight of a new issue.
- Fixes XSS with merge request approvers selection.
## 11.5.0 (2018-11-22) ## 11.5.0 (2018-11-22)
### Security (2 changes) ### Security (2 changes)
...@@ -103,6 +115,17 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -103,6 +115,17 @@ Please view this file on the master branch, on stable branches it's out of date.
- Geo: Clarify Geo HA documentation. - Geo: Clarify Geo HA documentation.
## 11.4.8 (2018-11-27)
### Security (5 changes)
- Escape entity title while autocomplete template rendering to prevent XSS. !707
- Authorize users when listing board users and milestones.
- Fix IDOR at /drafts/publish.
- Resolve: Guest can set weight of a new issue.
- Fixes XSS with merge request approvers selection.
## 11.4.7 (2018-11-20) ## 11.4.7 (2018-11-20)
### Fixed (1 change) ### Fixed (1 change)
...@@ -236,6 +259,19 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -236,6 +259,19 @@ Please view this file on the master branch, on stable branches it's out of date.
- API: Allow issue weight parameter to be greater than or equal to zero. - API: Allow issue weight parameter to be greater than or equal to zero.
## 11.3.11 (2018-11-26)
### Security (7 changes)
- Escape entity title while autocomplete template rendering to prevent XSS. !697
- Properly filter private references from system notes.
- Authorize users when listing board users and milestones.
- Project groups approvers no longer leak private groups info.
- Resolve: Guest can set weight of a new issue.
- Fixes XSS with merge request approvers selection.
- Protect against CSRF attacks when adding Slack app.
## 11.3.10 (2018-11-18) ## 11.3.10 (2018-11-18)
- No changes. - No changes.
......
...@@ -2,6 +2,28 @@ ...@@ -2,6 +2,28 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 11.5.1 (2018-11-26)
### Security (16 changes)
- Fix CRLF vulnerability in Project hooks.
- Fix possible XSS attack in Markdown urls with spaces.
- Redact sensitive information on gitlab-workhorse log.
- Do not follow redirects in Prometheus service when making http requests to the configured api url.
- Don't expose confidential information in commit message list.
- Provide email notification when a user changes their email address.
- Restrict Personal Access Tokens to API scope on web requests.
- Resolve reflected XSS in Ouath authorize window.
- Fix SSRF in project integrations.
- Fixed ability to comment on locked/confidential issues.
- Fixed ability of guest users to edit/delete comments on locked or confidential issues.
- Fix milestone promotion authorization check.
- Configure mermaid to not render HTML content in diagrams.
- Fix a possible symlink time of check to time of use race condition in GitLab Pages.
- Removed ability to see private group names when the group id is entered in the url.
- Fix stored XSS for Environments.
## 11.5.0 (2018-11-22) ## 11.5.0 (2018-11-22)
### Security (10 changes, 1 of them is from the community) ### Security (10 changes, 1 of them is from the community)
...@@ -264,6 +286,36 @@ entry. ...@@ -264,6 +286,36 @@ entry.
- Disables stop environment button while the deploy is in progress. - Disables stop environment button while the deploy is in progress.
## 11.4.8 (2018-11-27)
### Security (24 changes)
- Escape entity title while autocomplete template rendering to prevent XSS. !2571
- Resolve reflected XSS in Ouath authorize window.
- Fix XSS in merge request source branch name.
- Escape user fullname while rendering autocomplete template to prevent XSS.
- Fix CRLF vulnerability in Project hooks.
- Fix possible XSS attack in Markdown urls with spaces.
- Redact sensitive information on gitlab-workhorse log.
- Do not follow redirects in Prometheus service when making http requests to the configured api url.
- Persist only SHA digest of PersonalAccessToken#token.
- Don't expose confidential information in commit message list.
- Provide email notification when a user changes their email address.
- Restrict Personal Access Tokens to API scope on web requests.
- Redact personal tokens in unsubscribe links.
- Fix SSRF in project integrations.
- Fixed ability to comment on locked/confidential issues.
- Fixed ability of guest users to edit/delete comments on locked or confidential issues.
- Fix milestone promotion authorization check.
- Monkey kubeclient to not follow any redirects.
- Configure mermaid to not render HTML content in diagrams.
- Fix a possible symlink time of check to time of use race condition in GitLab Pages.
- Removed ability to see private group names when the group id is entered in the url.
- Fix stored XSS for Environments.
- Prevent SSRF attacks in HipChat integration.
- Validate Wiki attachments are valid temporary files.
## 11.4.7 (2018-11-20) ## 11.4.7 (2018-11-20)
- No changes. - No changes.
...@@ -544,6 +596,44 @@ entry. ...@@ -544,6 +596,44 @@ entry.
- Check frozen string in style builds. (gfyoung) - Check frozen string in style builds. (gfyoung)
## 11.3.11 (2018-11-26)
### Security (32 changes)
- Filter user sensitive data from discussions JSON. !2537
- Escape entity title while autocomplete template rendering to prevent XSS. !2557
- Resolve reflected XSS in Ouath authorize window.
- Fix XSS in merge request source branch name.
- Fix CRLF vulnerability in Project hooks.
- Fix possible XSS attack in Markdown urls with spaces.
- Redact sensitive information on gitlab-workhorse log.
- Set timeout for syntax highlighting.
- Do not follow redirects in Prometheus service when making http requests to the configured api url.
- Persist only SHA digest of PersonalAccessToken#token.
- Sanitize JSON data properly to fix XSS on Issue details page.
- Don't expose confidential information in commit message list.
- Markdown API no longer displays confidential title references unless authorized.
- Provide email notification when a user changes their email address.
- Properly filter private references from system notes.
- Restrict Personal Access Tokens to API scope on web requests.
- Redact personal tokens in unsubscribe links.
- Fix SSRF in project integrations.
- Fix stored XSS in merge requests from imported repository.
- Fixed ability to comment on locked/confidential issues.
- Fixed ability of guest users to edit/delete comments on locked or confidential issues.
- Fix milestone promotion authorization check.
- Monkey kubeclient to not follow any redirects.
- Configure mermaid to not render HTML content in diagrams.
- Redact confidential events in the API.
- Fix xss vulnerability sourced from package.json.
- Fix a possible symlink time of check to time of use race condition in GitLab Pages.
- Removed ability to see private group names when the group id is entered in the url.
- Fix stored XSS for Environments.
- Block loopback addresses in UrlBlocker.
- Prevent SSRF attacks in HipChat integration.
- Validate Wiki attachments are valid temporary files.
## 11.3.10 (2018-11-18) ## 11.3.10 (2018-11-18)
### Security (1 change) ### Security (1 change)
......
...@@ -85,3 +85,5 @@ module BoardsResponses ...@@ -85,3 +85,5 @@ module BoardsResponses
end end
end end
end end
BoardsResponses.prepend(EE::BoardsResponses)
...@@ -11,10 +11,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -11,10 +11,6 @@ class Projects::IssuesController < Projects::ApplicationController
prepend ::EE::Projects::IssuesController prepend ::EE::Projects::IssuesController
def self.authenticate_user_only_actions
%i[new]
end
def self.issue_except_actions def self.issue_except_actions
%i[index calendar new create bulk_update] %i[index calendar new create bulk_update]
end end
......
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
# active: boolean # active: boolean
# blocked: boolean # blocked: boolean
# external: boolean # external: boolean
# skip_ldap: boolean
# #
class UsersFinder class UsersFinder
include CreatedAtFilter include CreatedAtFilter
...@@ -38,7 +37,6 @@ class UsersFinder ...@@ -38,7 +37,6 @@ class UsersFinder
users = by_2fa(users) users = by_2fa(users)
users = by_created_at(users) users = by_created_at(users)
users = by_custom_attributes(users) users = by_custom_attributes(users)
users = by_non_ldap(users)
users users
end end
...@@ -86,12 +84,6 @@ class UsersFinder ...@@ -86,12 +84,6 @@ class UsersFinder
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def by_non_ldap(users)
return users unless params[:skip_ldap]
users.non_ldap
end
def by_2fa(users) def by_2fa(users)
case params[:two_factor] case params[:two_factor]
when 'enabled' when 'enabled'
...@@ -103,3 +95,5 @@ class UsersFinder ...@@ -103,3 +95,5 @@ class UsersFinder
end end
end end
end end
UsersFinder.prepend(EE::UsersFinder)
---
title: Add a rebase API endpoint for merge requests
merge_request: 23296
author:
type: added
...@@ -3,6 +3,12 @@ comments: false ...@@ -3,6 +3,12 @@ comments: false
description: 'Learn how to use and administer GitLab, the most scalable Git-based fully integrated platform for software development.' description: 'Learn how to use and administer GitLab, the most scalable Git-based fully integrated platform for software development.'
--- ---
<div class="display-none">
<em>Visit <a href="https://docs.gitlab.com/ee/">docs.gitlab.com</a> for optimized
navigation, discoverability, and readability.</em>
</div>
<!-- the div above will not display on the docs site but will display on /help -->
# GitLab Documentation # GitLab Documentation
Welcome to [GitLab](https://about.gitlab.com/) Documentation. Welcome to [GitLab](https://about.gitlab.com/) Documentation.
......
...@@ -411,6 +411,7 @@ Parameters: ...@@ -411,6 +411,7 @@ Parameters:
- `merge_request_iid` (required) - The internal ID of the merge request - `merge_request_iid` (required) - The internal ID of the merge request
- `render_html` (optional) - If `true` response includes rendered HTML for title and description - `render_html` (optional) - If `true` response includes rendered HTML for title and description
- `include_diverged_commits_count` (optional) - If `true` response includes the commits behind the target branch - `include_diverged_commits_count` (optional) - If `true` response includes the commits behind the target branch
- `include_rebase_in_progress` (optional) - If `true` response includes whether a rebase operation is in progress
```json ```json
{ {
...@@ -464,6 +465,7 @@ Parameters: ...@@ -464,6 +465,7 @@ Parameters:
}, },
"merge_when_pipeline_succeeds": true, "merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"merge_error": null,
"sha": "8888888888888888888888888888888888888888", "sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null, "merge_commit_sha": null,
"user_notes_count": 1, "user_notes_count": 1,
...@@ -509,6 +511,7 @@ Parameters: ...@@ -509,6 +511,7 @@ Parameters:
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
}, },
"diverged_commits_count": 2, "diverged_commits_count": 2,
"rebase_in_progress": false,
"approvals_before_merge": null "approvals_before_merge": null
} }
``` ```
...@@ -786,6 +789,7 @@ order for it to take effect: ...@@ -786,6 +789,7 @@ order for it to take effect:
}, },
"merge_when_pipeline_succeeds": true, "merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"merge_error": null,
"sha": "8888888888888888888888888888888888888888", "sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null, "merge_commit_sha": null,
"user_notes_count": 1, "user_notes_count": 1,
...@@ -914,6 +918,7 @@ Must include at least one non-required attribute from above. ...@@ -914,6 +918,7 @@ Must include at least one non-required attribute from above.
}, },
"merge_when_pipeline_succeeds": true, "merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"merge_error": null,
"sha": "8888888888888888888888888888888888888888", "sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null, "merge_commit_sha": null,
"user_notes_count": 1, "user_notes_count": 1,
...@@ -1058,6 +1063,7 @@ Parameters: ...@@ -1058,6 +1063,7 @@ Parameters:
}, },
"merge_when_pipeline_succeeds": true, "merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"merge_error": null,
"sha": "8888888888888888888888888888888888888888", "sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null, "merge_commit_sha": null,
"user_notes_count": 1, "user_notes_count": 1,
...@@ -1174,6 +1180,7 @@ Parameters: ...@@ -1174,6 +1180,7 @@ Parameters:
}, },
"merge_when_pipeline_succeeds": false, "merge_when_pipeline_succeeds": false,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"merge_error": null,
"sha": "8888888888888888888888888888888888888888", "sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null, "merge_commit_sha": null,
"user_notes_count": 1, "user_notes_count": 1,
...@@ -1223,6 +1230,62 @@ Parameters: ...@@ -1223,6 +1230,62 @@ Parameters:
} }
``` ```
## Rebase a merge request
Automatically rebase the `source_branch` of the merge request against its
`target_branch`.
If you don't have permissions to push to the merge request's source branch -
you'll get a `403 Forbidden` response.
```
PUT /projects/:id/merge_requests/:merge_request_iid/rebase
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/76/merge_requests/1/rebase
```
This is an asynchronous request. The API will return an empty `202 Accepted`
response if the request is enqueued successfully.
You can poll the [Get single MR](#get-single-mr) endpoint with the
`include_rebase_in_progress` parameter to check the status of the
asynchronous request.
If the rebase operation is ongoing, the response will include the following:
```json
{
"rebase_in_progress": true
"merge_error": null
}
```
Once the rebase operation has completed successfully, the response will include
the following:
```json
{
"rebase_in_progress": false,
"merge_error": null,
}
```
If the rebase operation fails, the response will include the following:
```json
{
"rebase_in_progress": false,
"merge_error": "Rebase failed. Please rebase locally",
}
```
## Comments on merge requests ## Comments on merge requests
Comments are done via the [notes](notes.md) resource. Comments are done via the [notes](notes.md) resource.
......
...@@ -114,7 +114,7 @@ export default class ApproversSelect { ...@@ -114,7 +114,7 @@ export default class ApproversSelect {
} }
static formatSelection(group) { static formatSelection(group) {
return group.full_name || group.name; return _.escape(group.full_name || group.name);
} }
static formatResult({ static formatResult({
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
module Boards module Boards
class MilestonesController < Boards::ApplicationController class MilestonesController < Boards::ApplicationController
include BoardsResponses
before_action :authorize_read_milestone, only: [:index]
def index def index
milestones_finder = Boards::MilestonesFinder.new(board, current_user) milestones_finder = Boards::MilestonesFinder.new(board, current_user)
......
...@@ -7,6 +7,11 @@ module Boards ...@@ -7,6 +7,11 @@ module Boards
# If board parent is a group it enumerates all members of current group, # If board parent is a group it enumerates all members of current group,
# ancestors, and descendants # ancestors, and descendants
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
include BoardsResponses
before_action :authorize_read_parent, only: [:index]
def index def index
user_ids = user_finder.execute.select(:user_id) user_ids = user_finder.execute.select(:user_id)
......
module EE
module BoardsResponses
extend ActiveSupport::Concern
def authorize_read_parent
ability = board.group_board? ? :read_group : :read_project
authorize_action_for!(board.parent, ability)
end
def authorize_read_milestone
ability = board.group_board? ? :read_group : :read_milestone
authorize_action_for!(board.parent, ability)
end
end
end
...@@ -6,6 +6,7 @@ module EE ...@@ -6,6 +6,7 @@ module EE
extend ActiveSupport::Concern extend ActiveSupport::Concern
prepended do prepended do
before_action :authenticate_user!, only: [:export_csv]
before_action :check_export_issues_available!, only: [:export_csv] before_action :check_export_issues_available!, only: [:export_csv]
before_action :check_service_desk_available!, only: [:service_desk] before_action :check_service_desk_available!, only: [:service_desk]
before_action :whitelist_query_limiting_ee, only: [:update] before_action :whitelist_query_limiting_ee, only: [:update]
...@@ -14,11 +15,6 @@ module EE ...@@ -14,11 +15,6 @@ module EE
class_methods do class_methods do
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
override :authenticate_user_only_actions
def authenticate_user_only_actions
super + %i[export_csv]
end
override :issue_except_actions override :issue_except_actions
def issue_except_actions def issue_except_actions
super + %i[export_csv service_desk] super + %i[export_csv service_desk]
......
...@@ -5,6 +5,10 @@ class Groups::Epics::NotesController < Groups::ApplicationController ...@@ -5,6 +5,10 @@ class Groups::Epics::NotesController < Groups::ApplicationController
include NotesHelper include NotesHelper
include ToggleAwardEmoji include ToggleAwardEmoji
# Re-defining the before_action set in NotesActions here without prepending pushes it farther down the callback stack
# and we do this here since we need variables instantiated in other before_actions
before_action :normalize_create_params, only: [:create]
before_action :epic before_action :epic
before_action :authorize_create_note!, only: [:create] before_action :authorize_create_note!, only: [:create]
...@@ -41,4 +45,11 @@ class Groups::Epics::NotesController < Groups::ApplicationController ...@@ -41,4 +45,11 @@ class Groups::Epics::NotesController < Groups::ApplicationController
def note_serializer def note_serializer
EpicNoteSerializer.new(project: nil, noteable: noteable, current_user: current_user) EpicNoteSerializer.new(project: nil, noteable: noteable, current_user: current_user)
end end
def normalize_create_params
params[:note].try do |note|
note[:noteable_id] = epic.id
note[:noteable_type] = 'Epic'
end
end
end end
...@@ -8,6 +8,7 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli ...@@ -8,6 +8,7 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
before_action :check_draft_notes_available!, except: [:index] before_action :check_draft_notes_available!, except: [:index]
before_action :authorize_create_draft!, only: [:create] before_action :authorize_create_draft!, only: [:create]
before_action :authorize_admin_draft!, only: [:update, :destroy] before_action :authorize_admin_draft!, only: [:update, :destroy]
before_action :authorize_admin_draft!, only: [:publish], if: -> { params[:id].present? }
def index def index
drafts = prepare_notes_for_rendering(draft_notes) drafts = prepare_notes_for_rendering(draft_notes)
...@@ -40,7 +41,7 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli ...@@ -40,7 +41,7 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
end end
def publish def publish
DraftNotes::PublishService.new(merge_request, current_user).execute(params[:id]) DraftNotes::PublishService.new(merge_request, current_user).execute(draft_note(allow_nil: true))
head :ok head :ok
end end
...@@ -53,10 +54,13 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli ...@@ -53,10 +54,13 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
private private
def draft_note def draft_note(allow_nil: false)
strong_memoize(:draft_note) do strong_memoize(:draft_note) do
draft_notes.try(:find, params[:id]) draft_notes.find(params[:id])
end end
rescue ActiveRecord::RecordNotFound => ex
# draft_note is allowed to be nil in #publish
raise ex unless allow_nil
end end
def draft_notes def draft_notes
......
...@@ -4,8 +4,7 @@ module Projects ...@@ -4,8 +4,7 @@ module Projects
module Settings module Settings
class OperationsController < Projects::ApplicationController class OperationsController < Projects::ApplicationController
before_action :check_license before_action :check_license
before_action :authorize_update_environment!, only: [:create, :update] before_action :authorize_update_environment!
before_action :authorize_read_environment!, only: [:show]
def show def show
@tracing_settings ||= ProjectTracingSetting.for_project(@project) @tracing_settings ||= ProjectTracingSetting.for_project(@project)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
class Projects::TracingsController < Projects::ApplicationController class Projects::TracingsController < Projects::ApplicationController
before_action :check_license before_action :check_license
before_action :authorize_read_environment!, only: [:show] before_action :authorize_update_environment!
def show def show
end end
......
# frozen_string_literal: true
module EE
module UsersFinder
extend ::Gitlab::Utils::Override
override :execute
def execute
by_non_ldap(super)
end
def by_non_ldap(users)
return users unless params[:skip_ldap]
users.non_ldap
end
end
end
...@@ -5,6 +5,8 @@ class ProjectTracingSetting < ActiveRecord::Base ...@@ -5,6 +5,8 @@ class ProjectTracingSetting < ActiveRecord::Base
validates :external_url, length: { maximum: 255 }, public_url: true validates :external_url, length: { maximum: 255 }, public_url: true
before_validation :sanitize_external_url
def self.create_or_update(project, params) def self.create_or_update(project, params)
self.transaction(requires_new: true) do self.transaction(requires_new: true) do
tracing_setting = self.for_project(project) tracing_setting = self.for_project(project)
...@@ -17,4 +19,10 @@ class ProjectTracingSetting < ActiveRecord::Base ...@@ -17,4 +19,10 @@ class ProjectTracingSetting < ActiveRecord::Base
def self.for_project(project) def self.for_project(project)
self.where(project: project).first_or_initialize self.where(project: project).first_or_initialize
end end
private
def sanitize_external_url
self.external_url = ActionController::Base.helpers.sanitize(self.external_url, tags: [])
end
end end
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
module DraftNotes module DraftNotes
class PublishService < DraftNotes::BaseService class PublishService < DraftNotes::BaseService
def execute(draft_id = nil) def execute(draft = nil)
if draft_id if draft
publish_draft_note(draft_id) publish_draft_note(draft)
else else
publish_draft_notes publish_draft_notes
end end
...@@ -12,9 +12,7 @@ module DraftNotes ...@@ -12,9 +12,7 @@ module DraftNotes
private private
def publish_draft_note(draft_id) def publish_draft_note(draft)
draft = DraftNote.find(draft_id)
create_note_from_draft(draft) create_note_from_draft(draft)
draft.delete draft.delete
......
...@@ -3,7 +3,13 @@ module EE ...@@ -3,7 +3,13 @@ module EE
private private
def filter_params(issuable) def filter_params(issuable)
params.delete(:weight) unless issuable.supports_weight? # This security check is repeated here to avoid multiple backports,
# this should be refactored to be reused from the base class.
ability_name = :"admin_#{issuable.to_ability_name}"
unless issuable.supports_weight? && can?(current_user, ability_name, issuable)
params.delete(:weight)
end
super super
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- if project_nav_tab? :settings - if project_nav_tab? :settings
= nav_link(controller: :tracings, action: [:show]) do = nav_link(controller: :tracings, action: [:show]) do
- if @project.tracing_external_url.present? - if @project.tracing_external_url.present?
= link_to @project.tracing_external_url, target: "_blank", rel: 'noopener noreferrer' do = link_to sanitize(@project.tracing_external_url, tags: []), target: "_blank", rel: 'noopener noreferrer' do
%span %span
= _('Tracing') = _('Tracing')
%i.strong.ml-1.fa.fa-external-link %i.strong.ml-1.fa.fa-external-link
......
...@@ -8,12 +8,16 @@ ...@@ -8,12 +8,16 @@
%h4 %h4
= _("Jaeger tracing") = _("Jaeger tracing")
%p %p
- tracing_url = has_jaeger_url ? @project.tracing_external_url : project_tracing_path(@project) - if has_jaeger_url
- meta = has_jaeger_url ? 'rel="noopener noreferrer" target="_blank"' : '' - tracing_link = link_to sanitize(@project.tracing_external_url, tags: []), target: "_blank", rel: 'noopener noreferrer' do
- icon = has_jaeger_url ? sprite_icon('external-link', size: 16, css_class: 'ml-1 vertical-align-middle') : '' %span
- tracing_start_tag = "<a href='#{tracing_url}' #{meta}>".html_safe = _('Tracing')
- tracing_end_tag = "#{icon}</a>".html_safe = sprite_icon('external-link', size: 16, css_class: 'ml-1 vertical-align-middle')
= _("To open Jaeger and easily view tracing from GitLab, link the %{start_tag}Tracing%{end_tag} page to your server").html_safe % { start_tag: tracing_start_tag, end_tag: tracing_end_tag } - else
- tracing_link = link_to project_tracing_path(@project) do
%span
= _('Tracing')
= _("To open Jaeger and easily view tracing from GitLab, link the %{link} page to your server").html_safe % { link: tracing_link }
= form_for @tracing_settings, as: :tracing_settings, url: project_settings_operations_path(@project) do |f| = form_for @tracing_settings, as: :tracing_settings, url: project_settings_operations_path(@project) do |f|
= form_errors(@tracing_settings) = form_errors(@tracing_settings)
.form-group .form-group
......
---
title: Move EE only differences for finders
merge_request: 8629
author: George Tsiolis
type: other
---
title: Sanitize tracing external_urls before saving to DB and when displaying the URL to prevent XSS issues
merge_request:
author:
type: security
---
title: Prevent reporter roles from viewing the Jaeger tracing settings page
merge_request:
author:
type: security
---
title: Fix IDOR at /drafts/publish
merge_request:
author:
type: security
---
title: Authorize users when listing board users and milestones.
merge_request:
author:
type: security
---
title: 'Resolve: Guest can set weight of a new issue'
merge_request:
author:
type: security
---
title: Fixes XSS with merge request approvers selection
merge_request:
author:
type: security
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class SanitizeTracingExternalUrl < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
class ProjectTracingSetting < ActiveRecord::Base
include ::EachBatch
self.table_name = 'project_tracing_settings'
def sanitize_external_url
self.external_url = ActionController::Base.helpers.sanitize(self.external_url, tags: [])
end
end
def up
ProjectTracingSetting.each_batch(of: 50) do |batch|
batch.each do |rec|
rec.sanitize_external_url
rec.save! if rec.changed?
end
end
end
def down
# no-op
end
end
...@@ -5,23 +5,50 @@ describe Boards::MilestonesController do ...@@ -5,23 +5,50 @@ describe Boards::MilestonesController do
let(:board) { create(:board, project: project) } let(:board) { create(:board, project: project) }
let(:user) { create(:user) } let(:user) { create(:user) }
before do describe 'GET index' do
create(:milestone, project: project) context 'with authorized user' do
before do
create(:milestone, project: project)
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
end end
describe 'GET index' do it 'returns a list of all milestones of board parent' do
it 'returns a list of all milestones of board parent' do get :index, board_id: board.to_param, format: :json
get :index, board_id: board.to_param, format: :json
parsed_response = JSON.parse(response.body)
expect(response).to have_gitlab_http_status(200)
expect(response.content_type).to eq('application/json')
expect(parsed_response).to all(match_schema('entities/milestone', dir: 'ee'))
expect(parsed_response.size).to eq(1)
end
end
context 'with unauthorized user' do
before do
sign_in(user)
end
shared_examples 'unauthorized board milestone listing' do
it 'returns a forbidden 403 response' do
get :index, board_id: board.to_param, format: :json
expect(response).to have_gitlab_http_status(403)
end
end
context 'with private group board' do
let(:group) { create(:group, :private) }
let(:board) { create(:board, group: group) }
parsed_response = JSON.parse(response.body) it_behaves_like 'unauthorized board milestone listing'
end
expect(response).to have_gitlab_http_status(200) context 'with private project board' do
expect(response.content_type).to eq('application/json') it_behaves_like 'unauthorized board milestone listing'
expect(parsed_response).to all(match_schema('entities/milestone', dir: 'ee')) end
expect(parsed_response.size).to eq(1)
end end
end end
end end
require 'spec_helper' require 'spec_helper'
describe Boards::UsersController do describe Boards::UsersController do
let(:group) { create(:group) } let(:group) { create(:group, :private) }
let(:board) { create(:board, group: group) } let(:board) { create(:board, group: group) }
let(:guest) { create(:user) } let(:guest) { create(:user) }
let(:user) { create(:user) } let(:user) { create(:user) }
before do describe 'GET index' do
group.add_maintainer(user) context 'with authorized user' do
group.add_guest(guest) before do
group.add_maintainer(user)
group.add_guest(guest)
sign_in(user) sign_in(user)
end end
describe 'GET index' do it 'returns a list of all members of board parent' do
it 'returns a list of all members of board parent' do get :index, namespace_id: group.to_param,
get :index, namespace_id: group.to_param, board_id: board.to_param,
board_id: board.to_param, format: :json
format: :json
parsed_response = JSON.parse(response.body)
expect(response).to have_gitlab_http_status(200)
expect(response.content_type).to eq 'application/json'
expect(parsed_response).to all(match_schema('entities/user'))
expect(parsed_response.length).to eq 2
end
end
context 'with unauthorized user' do
before do
sign_in(user)
end
shared_examples 'unauthorized board user listing' do
it 'returns a forbidden 403 response' do
get :index, board_id: board.to_param, format: :json
expect(response).to have_gitlab_http_status(403)
end
end
context 'with private group board' do
it_behaves_like 'unauthorized board user listing'
end
parsed_response = JSON.parse(response.body) context 'with private project board' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
expect(response).to have_gitlab_http_status(200) it_behaves_like 'unauthorized board user listing'
expect(response.content_type).to eq 'application/json' end
expect(parsed_response).to all(match_schema('entities/user'))
expect(parsed_response.length).to eq 2
end end
end end
end end
...@@ -153,6 +153,7 @@ describe Projects::MergeRequests::DraftsController do ...@@ -153,6 +153,7 @@ describe Projects::MergeRequests::DraftsController do
context 'without permissions' do context 'without permissions' do
before do before do
sign_in(user2) sign_in(user2)
project.add_developer(user2)
end end
it 'does not allow editing draft note belonging to someone else' do it 'does not allow editing draft note belonging to someone else' do
...@@ -176,6 +177,22 @@ describe Projects::MergeRequests::DraftsController do ...@@ -176,6 +177,22 @@ describe Projects::MergeRequests::DraftsController do
end end
describe 'POST #publish' do describe 'POST #publish' do
context 'without permissions' do
before do
sign_in(user2)
project.add_developer(user2)
end
it 'does not allow publishing draft note belonging to someone else' do
draft = create(:draft_note, merge_request: merge_request, author: user)
expect { post :publish, params.merge(id: draft.id) }.to change { Note.count }.by(0)
.and change { DraftNote.count }.by(0)
expect(response).to have_gitlab_http_status(404)
end
end
it 'publishes draft notes with position' do it 'publishes draft notes with position' do
diff_refs = project.commit(RepoHelpers.sample_commit.id).try(:diff_refs) diff_refs = project.commit(RepoHelpers.sample_commit.id).try(:diff_refs)
......
...@@ -10,21 +10,27 @@ describe Projects::Settings::OperationsController do ...@@ -10,21 +10,27 @@ describe Projects::Settings::OperationsController do
end end
describe 'GET show' do describe 'GET show' do
shared_examples 'user without access to project' do |project_visibility| shared_examples 'user without read access' do |project_visibility|
let(:project) { create(:project, project_visibility) } let(:project) { create(:project, project_visibility) }
it 'returns 404' do %w[guest reporter developer].each do |role|
get :show, namespace_id: project.namespace, project_id: project before do
project.public_send("add_#{role}", user)
end
expect(response).to have_gitlab_http_status(:not_found) it 'returns 404' do
get :show, namespace_id: project.namespace, project_id: project
expect(response).to have_gitlab_http_status(:not_found)
end
end end
end end
shared_examples 'user with access to project' do |project_visibility| shared_examples 'user with read access' do |project_visibility|
let(:project) { create(:project, project_visibility) } let(:project) { create(:project, project_visibility) }
before do before do
project.add_reporter(user) project.add_maintainer(user)
end end
it 'renders ok' do it 'renders ok' do
...@@ -50,16 +56,16 @@ describe Projects::Settings::OperationsController do ...@@ -50,16 +56,16 @@ describe Projects::Settings::OperationsController do
stub_licensed_features(tracing: true) stub_licensed_features(tracing: true)
end end
context 'when logged in with correct permission' do context 'with maintainer role' do
it_behaves_like 'user with access to project', :public it_behaves_like 'user with read access', :public
it_behaves_like 'user with access to project', :private it_behaves_like 'user with read access', :private
it_behaves_like 'user with access to project', :internal it_behaves_like 'user with read access', :internal
end end
context 'when logged in without correct permission' do context 'without maintainer role' do
it_behaves_like 'user without access to project', :public it_behaves_like 'user without read access', :public
it_behaves_like 'user without access to project', :private it_behaves_like 'user without read access', :private
it_behaves_like 'user without access to project', :internal it_behaves_like 'user without read access', :internal
end end
context 'when user not logged in' do context 'when user not logged in' do
...@@ -67,7 +73,7 @@ describe Projects::Settings::OperationsController do ...@@ -67,7 +73,7 @@ describe Projects::Settings::OperationsController do
sign_out(user) sign_out(user)
end end
it_behaves_like 'user without access to project', :public it_behaves_like 'user without read access', :public
it_behaves_like 'user needs to login', :private it_behaves_like 'user needs to login', :private
it_behaves_like 'user needs to login', :internal it_behaves_like 'user needs to login', :internal
...@@ -79,9 +85,9 @@ describe Projects::Settings::OperationsController do ...@@ -79,9 +85,9 @@ describe Projects::Settings::OperationsController do
stub_licensed_features(tracing: false) stub_licensed_features(tracing: false)
end end
it_behaves_like 'user without access to project', :public it_behaves_like 'user without read access', :public
it_behaves_like 'user without access to project', :private it_behaves_like 'user without read access', :private
it_behaves_like 'user without access to project', :internal it_behaves_like 'user without read access', :internal
end end
end end
...@@ -99,10 +105,16 @@ describe Projects::Settings::OperationsController do ...@@ -99,10 +105,16 @@ describe Projects::Settings::OperationsController do
shared_examples 'user without write access' do |project_visibility| shared_examples 'user without write access' do |project_visibility|
let(:project) { create(:project, project_visibility) } let(:project) { create(:project, project_visibility) }
it 'does not update tracing external_url' do %w[guest reporter developer].each do |role|
update_project(project, external_url: 'https://gitlab.com') before do
project.public_send("add_#{role}", user)
end
it 'does not update tracing external_url' do
update_project(project, external_url: 'https://gitlab.com')
expect(project.tracing_setting).to be_nil expect(project.tracing_setting).to be_nil
end
end end
end end
...@@ -125,13 +137,13 @@ describe Projects::Settings::OperationsController do ...@@ -125,13 +137,13 @@ describe Projects::Settings::OperationsController do
end end
end end
context 'with authorized user' do context 'with maintainer role' do
it_behaves_like 'user with write access', :public, 'https://gitlab.com', 'https://gitlab.com' it_behaves_like 'user with write access', :public, 'https://gitlab.com', 'https://gitlab.com'
it_behaves_like 'user with write access', :private, 'https://gitlab.com', 'https://gitlab.com' it_behaves_like 'user with write access', :private, 'https://gitlab.com', 'https://gitlab.com'
it_behaves_like 'user with write access', :internal, 'https://gitlab.com', 'https://gitlab.com' it_behaves_like 'user with write access', :internal, 'https://gitlab.com', 'https://gitlab.com'
end end
context 'with unauthorized user' do context 'with non maintainer roles' do
it_behaves_like 'user without write access', :public it_behaves_like 'user without write access', :public
it_behaves_like 'user without write access', :private it_behaves_like 'user without write access', :private
it_behaves_like 'user without write access', :internal it_behaves_like 'user without write access', :internal
......
...@@ -6,36 +6,27 @@ describe Projects::TracingsController do ...@@ -6,36 +6,27 @@ describe Projects::TracingsController do
set(:user) { create(:user) } set(:user) { create(:user) }
describe 'GET show' do describe 'GET show' do
describe 'with valid license' do shared_examples 'user with read access' do |visibility_level|
let(:project) { create(:project, visibility_level) }
before do before do
stub_licensed_features(tracing: true) project.add_maintainer(user)
end end
shared_examples 'authorized user' do |visibility_level| it 'renders OK' do
let(:project) { create(:project, visibility_level) } get :show, namespace_id: project.namespace, project_id: project
before do
project.add_reporter(user)
sign_in(user)
end
it 'renders OK' do
get :show, namespace_id: project.namespace, project_id: project
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to render_template(:show) expect(response).to render_template(:show)
end
end end
end
it_behaves_like 'authorized user', :public shared_examples 'user without read access' do |visibility_level|
it_behaves_like 'authorized user', :internal let(:project) { create(:project, visibility_level) }
it_behaves_like 'authorized user', :private
shared_examples 'unauthorized user' do |visibility_level|
let(:project) { create(:project, visibility_level) }
%w[guest reporter developer].each do |role|
before do before do
sign_in(user) project.public_send("add_#{role}", user)
end end
it 'returns 404' do it 'returns 404' do
...@@ -44,37 +35,36 @@ describe Projects::TracingsController do ...@@ -44,37 +35,36 @@ describe Projects::TracingsController do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
end end
it_behaves_like 'unauthorized user', :public
it_behaves_like 'unauthorized user', :internal
it_behaves_like 'unauthorized user', :private
end end
context 'with invalid license' do describe 'with valid license' do
before do before do
stub_licensed_features(tracing: false) stub_licensed_features(tracing: true)
sign_in(user) sign_in(user)
end end
shared_examples 'invalid license' do |visibility_level| context 'with maintainer role' do
let(:project) { create(:project, visibility_level) } it_behaves_like 'user with read access', :public
it_behaves_like 'user with read access', :internal
before do it_behaves_like 'user with read access', :private
stub_licensed_features(tracing: false) end
project.add_reporter(user)
sign_in(user)
end
it 'returns 404' do context 'without maintainer role' do
get :show, namespace_id: project.namespace, project_id: project it_behaves_like 'user without read access', :public
it_behaves_like 'user without read access', :internal
it_behaves_like 'user without read access', :private
end
end
expect(response).to have_gitlab_http_status(:not_found) context 'with invalid license' do
end before do
stub_licensed_features(tracing: false)
sign_in(user)
end end
it_behaves_like 'invalid license', :public it_behaves_like 'user without read access', :public
it_behaves_like 'invalid license', :internal it_behaves_like 'user without read access', :internal
it_behaves_like 'invalid license', :private it_behaves_like 'user without read access', :private
end end
end end
end end
FactoryBot.define do
factory :project_tracing_setting do
project
external_url 'https://example.com'
end
end
...@@ -59,4 +59,22 @@ describe('ApproversSelect', () => { ...@@ -59,4 +59,22 @@ describe('ApproversSelect', () => {
expect(output).not.toContain('<script>alert("testing")</script>'); expect(output).not.toContain('<script>alert("testing")</script>');
}); });
}); });
describe('formatSelection', () => {
it('escapes full name', () => {
expect(
ApproversSelect.formatSelection({
full_name: '<script>alert("testing")</script>',
}),
).not.toBe('<script>alert("testing")</script>');
});
it('escapes name', () => {
expect(
ApproversSelect.formatSelection({
name: '<script>alert("testing")</script>',
}),
).not.toBe('<script>alert("testing")</script>');
});
});
}); });
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('ee', 'db', 'post_migrate', '20181116100917_sanitize_tracing_external_url.rb')
describe SanitizeTracingExternalUrl, :migration do
let(:migration) { described_class.new }
describe '#up' do
let(:projects) { table(:projects) }
let(:namespaces) { table(:namespaces) }
let(:project_tracing_settings) { table(:project_tracing_settings) }
let(:valid_url) { "https://replaceme.com/" }
let(:invalid_url) { "https://replaceme.com/'><script>alert(document.cookie)</script>" }
let(:cleaned_url) { "https://replaceme.com/'>" }
before do
namespaces.create(id: 1, name: 'gitlab-org', path: 'gitlab-org')
projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 1)
projects.create!(id: 124, name: 'gitlab2', path: 'gitlab2', namespace_id: 1)
project_tracing_settings.create!(id: 2234, external_url: invalid_url, project_id: 123)
project_tracing_settings.create!(id: 2235, external_url: valid_url, project_id: 124)
end
it 'correctly sanitizes project_tracing_settings external_url' do
migrate!
expect(project_tracing_settings.order(:id).pluck(:external_url)).to match_array([cleaned_url, valid_url])
end
end
end
...@@ -24,5 +24,11 @@ describe ProjectTracingSetting do ...@@ -24,5 +24,11 @@ describe ProjectTracingSetting do
tracing_setting.external_url = " " tracing_setting.external_url = " "
expect(tracing_setting).not_to be_valid expect(tracing_setting).not_to be_valid
end end
it 'sanitizes the url' do
tracing_setting.external_url = "https://replaceme.com/'><script>alert(document.cookie)</script>"
expect(tracing_setting).to be_valid
expect(tracing_setting.external_url).to eq("https://replaceme.com/'>")
end
end end
end end
...@@ -6,14 +6,14 @@ describe DraftNotes::PublishService do ...@@ -6,14 +6,14 @@ describe DraftNotes::PublishService do
let(:project) { merge_request.target_project } let(:project) { merge_request.target_project }
let(:user) { merge_request.author } let(:user) { merge_request.author }
def publish(id: nil) def publish(draft: nil)
DraftNotes::PublishService.new(merge_request, user).execute(id) DraftNotes::PublishService.new(merge_request, user).execute(draft)
end end
it 'publishes a single draft note' do it 'publishes a single draft note' do
drafts = create_list(:draft_note, 2, merge_request: merge_request, author: user) drafts = create_list(:draft_note, 2, merge_request: merge_request, author: user)
expect { publish(id: drafts.first.id) }.to change { DraftNote.count }.by(-1).and change { Note.count }.by(1) expect { publish(draft: drafts.first) }.to change { DraftNote.count }.by(-1).and change { Note.count }.by(1)
expect(DraftNote.count).to eq(1) expect(DraftNote.count).to eq(1)
end end
...@@ -58,7 +58,7 @@ describe DraftNotes::PublishService do ...@@ -58,7 +58,7 @@ describe DraftNotes::PublishService do
let(:draft_note) { create(:draft_note, merge_request: merge_request, author: user, resolve_discussion: true, discussion_id: note.discussion.reply_id) } let(:draft_note) { create(:draft_note, merge_request: merge_request, author: user, resolve_discussion: true, discussion_id: note.discussion.reply_id) }
it 'resolves the discussion' do it 'resolves the discussion' do
publish(id: draft_note.id) publish(draft: draft_note)
expect(note.discussion.resolved?).to be true expect(note.discussion.resolved?).to be true
end end
......
require 'spec_helper'
describe Issues::CreateService do
let(:project) { create(:project) }
let(:opts) do
{
title: 'Awesome issue',
description: 'please fix',
weight: 9
}
end
context 'when current user cannot admin issues in the project' do
let(:guest) { create(:user) }
before do
project.add_guest(guest)
end
it 'filters out params that cannot be set without the :admin_issue permission' do
issue = described_class.new(project, guest, opts).execute
expect(issue).to be_persisted
expect(issue.weight).to be_nil
end
end
context 'when current user can admin issues in the project' do
let(:reporter) { create(:user) }
before do
project.add_reporter(reporter)
end
it 'sets permitted params correctly' do
issue = described_class.new(project, reporter, opts).execute
expect(issue).to be_persisted
expect(issue.weight).to eq(9)
end
end
end
...@@ -31,25 +31,43 @@ describe 'layouts/nav/sidebar/_project' do ...@@ -31,25 +31,43 @@ describe 'layouts/nav/sidebar/_project' do
context 'with project.tracing_external_url' do context 'with project.tracing_external_url' do
let(:tracing_url) { 'https://tracing.url' } let(:tracing_url) { 'https://tracing.url' }
let(:tracing_settings) { create(:project_tracing_setting, project: project, external_url: tracing_url) }
before do before do
allow(view).to receive(:can?).and_return(true) allow(view).to receive(:can?).and_return(true)
allow(project).to receive(:tracing_external_url).and_return(tracing_url)
end end
it 'links to project.tracing_external_url' do it 'links to project.tracing_external_url' do
expect(tracing_settings.external_url).to eq(tracing_url)
expect(project.tracing_external_url).to eq(tracing_url)
render render
expect(rendered).to have_link('Tracing', href: tracing_url) expect(rendered).to have_link('Tracing', href: tracing_url)
end end
context 'with malicious external_url' do
let(:malicious_tracing_url) { "https://replaceme.com/'><script>alert(document.cookie)</script>" }
let(:cleaned_url) { "https://replaceme.com/'>" }
before do
tracing_settings.update_column(:external_url, malicious_tracing_url)
end
it 'sanitizes external_url' do
expect(project.tracing_external_url).to eq(malicious_tracing_url)
render
expect(tracing_settings.external_url).to eq(malicious_tracing_url)
expect(rendered).to have_link('Tracing', href: cleaned_url)
end
end
end end
context 'without project.tracing_external_url' do context 'without project.tracing_external_url' do
before do before do
allow(view).to receive(:can?).and_return(true) allow(view).to receive(:can?).and_return(true)
allow(project).to receive(:tracing_external_url).and_return(nil)
end end
it 'links to Tracing page' do it 'links to Tracing page' do
......
# frozen_string_literal: true
require 'spec_helper'
describe 'projects/settings/operations/show' do
let(:project) { create(:project, :repository) }
before do
assign(:project, project)
assign(:repository, project.repository)
allow(view).to receive(:current_ref).and_return('master')
stub_licensed_features(tracing: true)
end
describe 'Operations > Tracing' do
context 'with project.tracing_external_url' do
let(:tracing_url) { 'https://tracing.url' }
let(:tracing_settings) { create(:project_tracing_setting, project: project, external_url: tracing_url) }
before do
allow(view).to receive(:can?).and_return(true)
assign(:tracing_settings, tracing_settings)
end
it 'links to project.tracing_external_url' do
render
expect(rendered).to have_link('Tracing', href: tracing_url)
end
context 'with malicious external_url' do
let(:malicious_tracing_url) { "https://replaceme.com/'><script>alert(document.cookie)</script>" }
let(:cleaned_url) { "https://replaceme.com/'>" }
before do
tracing_settings.update_column(:external_url, malicious_tracing_url)
end
it 'sanitizes external_url' do
render
expect(tracing_settings.external_url).to eq(malicious_tracing_url)
expect(rendered).to have_link('Tracing', href: cleaned_url)
end
end
end
context 'without project.tracing_external_url' do
let(:tracing_settings) { build(:project_tracing_setting, project: project) }
before do
allow(view).to receive(:can?).and_return(true)
tracing_settings.external_url = nil
assign(:tracing_settings, tracing_settings)
end
it 'links to Tracing page' do
render
expect(rendered).to have_link('Tracing', href: project_tracing_path(project))
end
end
end
end
...@@ -717,6 +717,10 @@ module API ...@@ -717,6 +717,10 @@ module API
expose :diff_refs, using: Entities::DiffRefs expose :diff_refs, using: Entities::DiffRefs
# Allow the status of a rebase to be determined
expose :merge_error
expose :rebase_in_progress?, as: :rebase_in_progress, if: -> (_, options) { options[:include_rebase_in_progress] }
expose :diverged_commits_count, as: :diverged_commits_count, if: -> (_, options) { options[:include_diverged_commits_count] } expose :diverged_commits_count, as: :diverged_commits_count, if: -> (_, options) { options[:include_diverged_commits_count] }
def build_available?(options) def build_available?(options)
......
...@@ -76,6 +76,19 @@ module API ...@@ -76,6 +76,19 @@ module API
options options
end end
def authorize_push_to_merge_request!(merge_request)
forbidden!('Source branch does not exist') unless
merge_request.source_branch_exists?
user_access = Gitlab::UserAccess.new(
current_user,
project: merge_request.source_project
)
forbidden!('Cannot push to source branch') unless
user_access.can_push_to_branch?(merge_request.source_branch)
end
params :merge_requests_params do params :merge_requests_params do
optional :state, type: String, values: %w[opened closed locked merged all], default: 'all', optional :state, type: String, values: %w[opened closed locked merged all], default: 'all',
desc: 'Return opened, closed, locked, merged, or all merge requests' desc: 'Return opened, closed, locked, merged, or all merge requests'
...@@ -241,6 +254,7 @@ module API ...@@ -241,6 +254,7 @@ module API
requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request' requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
optional :render_html, type: Boolean, desc: 'Returns the description and title rendered HTML' optional :render_html, type: Boolean, desc: 'Returns the description and title rendered HTML'
optional :include_diverged_commits_count, type: Boolean, desc: 'Returns the commits count behind the target branch' optional :include_diverged_commits_count, type: Boolean, desc: 'Returns the commits count behind the target branch'
optional :include_rebase_in_progress, type: Boolean, desc: 'Returns whether a rebase operation is ongoing '
end end
desc 'Get a single merge request' do desc 'Get a single merge request' do
success Entities::MergeRequest success Entities::MergeRequest
...@@ -248,7 +262,13 @@ module API ...@@ -248,7 +262,13 @@ module API
get ':id/merge_requests/:merge_request_iid' do get ':id/merge_requests/:merge_request_iid' do
merge_request = find_merge_request_with_access(params[:merge_request_iid]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project, render_html: params[:render_html], include_diverged_commits_count: params[:include_diverged_commits_count] present merge_request,
with: Entities::MergeRequest,
current_user: current_user,
project: user_project,
render_html: params[:render_html],
include_diverged_commits_count: params[:include_diverged_commits_count],
include_rebase_in_progress: params[:include_rebase_in_progress]
end end
desc 'Get the participants of a merge request' do desc 'Get the participants of a merge request' do
...@@ -380,6 +400,19 @@ module API ...@@ -380,6 +400,19 @@ module API
.cancel(merge_request) .cancel(merge_request)
end end
desc 'Rebase the merge request against its target branch' do
detail 'This feature was added in GitLab 11.6'
end
put ':id/merge_requests/:merge_request_iid/rebase' do
merge_request = find_project_merge_request(params[:merge_request_iid])
authorize_push_to_merge_request!(merge_request)
RebaseWorker.perform_async(merge_request.id, current_user.id)
status :accepted
end
desc 'List issues that will be closed on merge' do desc 'List issues that will be closed on merge' do
success Entities::MRNote success Entities::MRNote
end end
......
...@@ -8758,7 +8758,7 @@ msgstr "" ...@@ -8758,7 +8758,7 @@ msgstr ""
msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>." msgid "To only use CI/CD features for an external repository, choose <strong>CI/CD for external repo</strong>."
msgstr "" msgstr ""
msgid "To open Jaeger and easily view tracing from GitLab, link the %{start_tag}Tracing%{end_tag} page to your server" msgid "To open Jaeger and easily view tracing from GitLab, link the %{link} page to your server"
msgstr "" msgstr ""
msgid "To preserve performance only <strong>%{display_size} of ${real_size}</strong> files are displayed." msgid "To preserve performance only <strong>%{display_size} of ${real_size}</strong> files are displayed."
......
...@@ -212,7 +212,7 @@ describe IssuablesHelper do ...@@ -212,7 +212,7 @@ describe IssuablesHelper do
issuableRef: "&#{epic.iid}", issuableRef: "&#{epic.iid}",
markdownPreviewPath: "/groups/#{@group.full_path}/preview_markdown", markdownPreviewPath: "/groups/#{@group.full_path}/preview_markdown",
markdownDocsPath: '/help/user/markdown', markdownDocsPath: '/help/user/markdown',
markdownVersion: 11, markdownVersion: CacheMarkdownField::CACHE_COMMONMARK_VERSION,
issuableTemplates: nil, issuableTemplates: nil,
groupPath: @group.path, groupPath: @group.path,
initialTitleHtml: epic.title, initialTitleHtml: epic.title,
......
...@@ -359,6 +359,8 @@ describe API::MergeRequests do ...@@ -359,6 +359,8 @@ describe API::MergeRequests do
expect(json_response['should_close_merge_request']).to be_falsy expect(json_response['should_close_merge_request']).to be_falsy
expect(json_response['force_close_merge_request']).to be_falsy expect(json_response['force_close_merge_request']).to be_falsy
expect(json_response['changes_count']).to eq(merge_request.merge_request_diff.real_size) expect(json_response['changes_count']).to eq(merge_request.merge_request_diff.real_size)
expect(json_response['merge_error']).to eq(merge_request.merge_error)
expect(json_response).not_to include('rebase_in_progress')
end end
it 'exposes description and title html when render_html is true' do it 'exposes description and title html when render_html is true' do
...@@ -369,6 +371,14 @@ describe API::MergeRequests do ...@@ -369,6 +371,14 @@ describe API::MergeRequests do
expect(json_response).to include('title_html', 'description_html') expect(json_response).to include('title_html', 'description_html')
end end
it 'exposes rebase_in_progress when include_rebase_in_progress is true' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), include_rebase_in_progress: true
expect(response).to have_gitlab_http_status(200)
expect(json_response).to include('rebase_in_progress')
end
context 'merge_request_metrics' do context 'merge_request_metrics' do
before do before do
merge_request.metrics.update!(merged_by: user, merge_request.metrics.update!(merged_by: user,
...@@ -1202,6 +1212,26 @@ describe API::MergeRequests do ...@@ -1202,6 +1212,26 @@ describe API::MergeRequests do
end end
end end
describe 'PUT :id/merge_requests/:merge_request_iid/rebase' do
it 'enqueues a rebase of the merge request against the target branch' do
Sidekiq::Testing.fake! do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/rebase", user)
end
expect(response).to have_gitlab_http_status(202)
expect(RebaseWorker.jobs.size).to eq(1)
end
it 'returns 403 if the user cannot push to the branch' do
guest = create(:user)
project.add_guest(guest)
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/rebase", guest)
expect(response).to have_gitlab_http_status(403)
end
end
describe 'Time tracking' do describe 'Time tracking' do
let(:issuable) { merge_request } let(:issuable) { merge_request }
......
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