...@@ -486,6 +486,33 @@ entry. ...@@ -486,6 +486,33 @@ entry.
- Update url placeholder for the sentry configuration page. !24338 - Update url placeholder for the sentry configuration page. !24338
## 11.6.10 (2019-02-28)
### Security (21 changes)
- Stop linking to unrecognized package sources. !55518
- Check snippet attached file to be moved is within designated directory.
- Fix potential Addressable::URI::InvalidURIError.
- Do not display impersonated sessions under active sessions and remove ability to revoke session.
- Display only information visible to current user on the Milestone page.
- Show only merge requests visible to user on milestone detail page.
- Disable issue boards API when issues are disabled.
- Don't show new issue link after move when a user does not have permissions.
- Fix git clone revealing private repo's presence.
- Fix blind SSRF in Prometheus integration by checking URL before querying.
- Check if desired milestone for an issue is available.
- Don't allow non-members to see private related MRs.
- Fix arbitrary file read via diffs during import.
- Display the correct number of MRs a user has access to.
- Forbid creating discussions for users with restricted access.
- Do not disclose milestone titles for unauthorized users.
- Validate session key when authorizing with GCP to create a cluster.
- Block local URLs for Kubernetes integration.
- Limit mermaid rendering to 5K characters.
- Remove the possibility to share a project with a group that a user is not a member of.
- Fix leaking private repository information in API.
## 11.6.8 (2019-01-30) ## 11.6.8 (2019-01-30)
- No changes. - No changes.
... ...
......
...@@ -68,7 +68,7 @@ gem 'gpgme', '~> 2.0.18' ...@@ -68,7 +68,7 @@ gem 'gpgme', '~> 2.0.18'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master # see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
gem 'gitlab_omniauth-ldap', '~> 2.0.4', require: 'omniauth-ldap' gem 'gitlab_omniauth-ldap', '~> 2.1.1', require: 'omniauth-ldap'
gem 'net-ldap' gem 'net-ldap'
# API # API
... ...
......
...@@ -291,7 +291,7 @@ GEM ...@@ -291,7 +291,7 @@ GEM
rubocop (~> 0.54.0) rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0) rubocop-gitlab-security (~> 0.1.0)
rubocop-rspec (~> 1.19) rubocop-rspec (~> 1.19)
gitlab_omniauth-ldap (2.0.4) gitlab_omniauth-ldap (2.1.1)
net-ldap (~> 0.16) net-ldap (~> 0.16)
omniauth (~> 1.3) omniauth (~> 1.3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1) pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
...@@ -1024,7 +1024,7 @@ DEPENDENCIES ...@@ -1024,7 +1024,7 @@ DEPENDENCIES
gitlab-markup (~> 1.6.5) gitlab-markup (~> 1.6.5)
gitlab-sidekiq-fetcher (~> 0.4.0) gitlab-sidekiq-fetcher (~> 0.4.0)
gitlab-styles (~> 2.4) gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2) gon (~> 6.2)
google-api-client (~> 0.23) google-api-client (~> 0.23)
google-protobuf (~> 3.6) google-protobuf (~> 3.6)
... ...
......
...@@ -12,6 +12,7 @@ const Api = { ...@@ -12,6 +12,7 @@ const Api = {
projectsPath: '/api/:version/projects.json', projectsPath: '/api/:version/projects.json',
projectPath: '/api/:version/projects/:id', projectPath: '/api/:version/projects/:id',
projectLabelsPath: '/:namespace_path/:project_path/labels', projectLabelsPath: '/:namespace_path/:project_path/labels',
projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests',
projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid', projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes', projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
projectMergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions', projectMergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
...@@ -111,6 +112,22 @@ const Api = { ...@@ -111,6 +112,22 @@ const Api = {
return axios.get(url); return axios.get(url);
}, },
/**
* Get all Merge Requests for a project, eventually filtering based on
* supplied parameters
* @param projectPath
* @param params
* @returns {Promise}
*/
projectMergeRequests(projectPath, params = {}) {
const url = Api.buildUrl(Api.projectMergeRequestsPath).replace(
':id',
encodeURIComponent(projectPath),
);
return axios.get(url, { params });
},
// Return Merge Request for project // Return Merge Request for project
projectMergeRequest(projectPath, mergeRequestId, params = {}) { projectMergeRequest(projectPath, mergeRequestId, params = {}) {
const url = Api.buildUrl(Api.projectMergeRequestPath) const url = Api.buildUrl(Api.projectMergeRequestPath)
... ...
......
import flash from '~/flash'; import flash from '~/flash';
import { sprintf, __ } from '../../locale';
// Renders diagrams and flowcharts from text using Mermaid in any element with the // Renders diagrams and flowcharts from text using Mermaid in any element with the
// `js-render-mermaid` class. // `js-render-mermaid` class.
...@@ -14,6 +15,9 @@ import flash from '~/flash'; ...@@ -14,6 +15,9 @@ import flash from '~/flash';
// </pre> // </pre>
// //
// This is an arbitary number; Can be iterated upon when suitable.
const MAX_CHAR_LIMIT = 5000;
export default function renderMermaid($els) { export default function renderMermaid($els) {
if (!$els.length) return; if (!$els.length) return;
...@@ -34,6 +38,21 @@ export default function renderMermaid($els) { ...@@ -34,6 +38,21 @@ export default function renderMermaid($els) {
$els.each((i, el) => { $els.each((i, el) => {
const source = el.textContent; const source = el.textContent;
/**
* Restrict the rendering to a certain amount of character to
* prevent mermaidjs from hanging up the entire thread and
* causing a DoS.
*/
if (source && source.length > MAX_CHAR_LIMIT) {
el.textContent = sprintf(
__(
'Cannot render the image. Maximum character count (%{charLimit}) has been exceeded.',
),
{ charLimit: MAX_CHAR_LIMIT },
);
return;
}
// Remove any extra spans added by the backend syntax highlighting. // Remove any extra spans added by the backend syntax highlighting.
Object.assign(el, { textContent: source }); Object.assign(el, { textContent: source });
... ...
......
...@@ -40,6 +40,9 @@ export default { ...@@ -40,6 +40,9 @@ export default {
getProjectData(namespace, project) { getProjectData(namespace, project) {
return Api.project(`${namespace}/${project}`); return Api.project(`${namespace}/${project}`);
}, },
getProjectMergeRequests(projectId, params = {}) {
return Api.projectMergeRequests(projectId, params);
},
getProjectMergeRequestData(projectId, mergeRequestId, params = {}) { getProjectMergeRequestData(projectId, mergeRequestId, params = {}) {
return Api.projectMergeRequest(projectId, mergeRequestId, params); return Api.projectMergeRequest(projectId, mergeRequestId, params);
}, },
... ...
......
...@@ -4,6 +4,38 @@ import service from '../../services'; ...@@ -4,6 +4,38 @@ import service from '../../services';
import * as types from '../mutation_types'; import * as types from '../mutation_types';
import { activityBarViews } from '../../constants'; import { activityBarViews } from '../../constants';
export const getMergeRequestsForBranch = ({ commit }, { projectId, branchId } = {}) =>
service
.getProjectMergeRequests(`${projectId}`, {
source_branch: branchId,
order_by: 'created_at',
per_page: 1,
})
.then(({ data }) => {
if (data.length > 0) {
const currentMR = data[0];
commit(types.SET_MERGE_REQUEST, {
projectPath: projectId,
mergeRequestId: currentMR.iid,
mergeRequest: currentMR,
});
commit(types.SET_CURRENT_MERGE_REQUEST, `${currentMR.iid}`);
}
})
.catch(e => {
flash(
__(`Error fetching merge requests for ${branchId}`),
'alert',
document,
null,
false,
true,
);
throw e;
});
export const getMergeRequestData = ( export const getMergeRequestData = (
{ commit, dispatch, state }, { commit, dispatch, state },
{ projectId, mergeRequestId, targetProjectId = null, force = false } = {}, { projectId, mergeRequestId, targetProjectId = null, force = false } = {},
... ...
......
...@@ -136,7 +136,8 @@ export const openBranch = ({ dispatch, state }, { projectId, branchId, basePath ...@@ -136,7 +136,8 @@ export const openBranch = ({ dispatch, state }, { projectId, branchId, basePath
return dispatch('getFiles', { return dispatch('getFiles', {
projectId, projectId,
branchId, branchId,
}).then(() => { })
.then(() => {
if (basePath) { if (basePath) {
const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath; const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
const treeEntryKey = Object.keys(state.entries).find( const treeEntryKey = Object.keys(state.entries).find(
...@@ -148,5 +149,11 @@ export const openBranch = ({ dispatch, state }, { projectId, branchId, basePath ...@@ -148,5 +149,11 @@ export const openBranch = ({ dispatch, state }, { projectId, branchId, basePath
dispatch('handleTreeEntryAction', treeEntry); dispatch('handleTreeEntryAction', treeEntry);
} }
} }
})
.then(() => {
dispatch('getMergeRequestsForBranch', {
projectId,
branchId,
});
}); });
}; };
...@@ -26,16 +26,11 @@ ...@@ -26,16 +26,11 @@
*/ */
@mixin markdown-table { @mixin markdown-table {
width: auto; width: auto;
display: inline-block; display: block;
overflow-x: auto; overflow-x: auto;
border: 0; border: 0;
border-color: $gl-gray-100; border-color: $gl-gray-100;
@supports (width: fit-content) {
display: block;
width: fit-content;
}
tr { tr {
th { th {
border-bottom: solid 2px $gl-gray-100; border-bottom: solid 2px $gl-gray-100;
... ...
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
.detail-page-header, .detail-page-header,
.page-content-header, .page-content-header,
.commit-box, .commit-box,
.info-well,
.commit-ci-menu, .commit-ci-menu,
.files-changed-inner, .files-changed-inner,
.limited-header-width, .limited-header-width,
... ...
......
...@@ -8,7 +8,7 @@ module MilestoneActions ...@@ -8,7 +8,7 @@ module MilestoneActions
format.html { redirect_to milestone_redirect_path } format.html { redirect_to milestone_redirect_path }
format.json do format.json do
render json: tabs_json("shared/milestones/_merge_requests_tab", { render json: tabs_json("shared/milestones/_merge_requests_tab", {
merge_requests: @milestone.sorted_merge_requests, # rubocop:disable Gitlab/ModuleWithInstanceVariables merge_requests: @milestone.sorted_merge_requests(current_user), # rubocop:disable Gitlab/ModuleWithInstanceVariables
show_project_name: true show_project_name: true
}) })
end end
... ...
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
module GoogleApi module GoogleApi
class AuthorizationsController < ApplicationController class AuthorizationsController < ApplicationController
include Gitlab::Utils::StrongMemoize
before_action :validate_session_key!
def callback def callback
token, expires_at = GoogleApi::CloudPlatform::Client token, expires_at = GoogleApi::CloudPlatform::Client
.new(nil, callback_google_api_auth_url) .new(nil, callback_google_api_auth_url)
...@@ -11,21 +15,27 @@ module GoogleApi ...@@ -11,21 +15,27 @@ module GoogleApi
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] =
expires_at.to_s expires_at.to_s
state_redirect_uri = redirect_uri_from_session_key(params[:state]) redirect_to redirect_uri_from_session
end
private
def validate_session_key!
access_denied! unless redirect_uri_from_session.present?
end
if state_redirect_uri def redirect_uri_from_session
redirect_to state_redirect_uri strong_memoize(:redirect_uri_from_session) do
if params[:state].present?
session[session_key_for_redirect_uri(params[:state])]
else else
redirect_to root_path nil
end
end end
end end
private def session_key_for_redirect_uri(state)
GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(state)
def redirect_uri_from_session_key(state)
key = GoogleApi::CloudPlatform::Client
.session_key_for_redirect_uri(params[:state])
session[key] if key
end end
end end
end end
...@@ -2,15 +2,6 @@ ...@@ -2,15 +2,6 @@
class Profiles::ActiveSessionsController < Profiles::ApplicationController class Profiles::ActiveSessionsController < Profiles::ApplicationController
def index def index
@sessions = ActiveSession.list(current_user) @sessions = ActiveSession.list(current_user).reject(&:is_impersonated)
end
def destroy
ActiveSession.destroy(current_user, params[:id])
respond_to do |format|
format.html { redirect_to profile_active_sessions_url, status: :found }
format.js { head :ok }
end
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
class Projects::AutocompleteSourcesController < Projects::ApplicationController class Projects::AutocompleteSourcesController < Projects::ApplicationController
before_action :authorize_read_milestone!, only: :milestones
def members def members
render json: ::Projects::ParticipantsService.new(@project, current_user).execute(target) render json: ::Projects::ParticipantsService.new(@project, current_user).execute(target)
end end
... ...
......
...@@ -65,7 +65,11 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -65,7 +65,11 @@ class Projects::CommitController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def merge_requests def merge_requests
@merge_requests = @commit.merge_requests.map do |mr| @merge_requests = MergeRequestsFinder.new(
current_user,
project_id: @project.id,
commit_sha: @commit.sha
).execute.map do |mr|
{ iid: mr.iid, path: merge_request_path(mr), title: mr.title } { iid: mr.iid, path: merge_request_path(mr), title: mr.title }
end end
... ...
......
...@@ -13,9 +13,10 @@ class Projects::GroupLinksController < Projects::ApplicationController ...@@ -13,9 +13,10 @@ class Projects::GroupLinksController < Projects::ApplicationController
group = Group.find(params[:link_group_id]) if params[:link_group_id].present? group = Group.find(params[:link_group_id]) if params[:link_group_id].present?
if group if group
return render_404 unless can?(current_user, :read_group, group) result = Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group)
return render_404 if result[:http_status] == 404
Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group) flash[:alert] = result[:message] if result[:http_status] == 409
else else
flash[:alert] = 'Please select a group.' flash[:alert] = 'Please select a group.'
end end
... ...
......
...@@ -37,13 +37,20 @@ class MergeRequestsFinder < IssuableFinder ...@@ -37,13 +37,20 @@ class MergeRequestsFinder < IssuableFinder
end end
def filter_items(_items) def filter_items(_items)
items = by_source_branch(super) items = by_commit(super)
items = by_source_branch(items)
items = by_wip(items) items = by_wip(items)
by_target_branch(items) by_target_branch(items)
end end
private private
def by_commit(items)
return items unless params[:commit_sha].presence
items.by_commit_sha(params[:commit_sha])
end
def source_branch def source_branch
@source_branch ||= params[:source_branch].presence @source_branch ||= params[:source_branch].presence
end end
... ...
......
...@@ -16,7 +16,6 @@ module Types ...@@ -16,7 +16,6 @@ module Types
field :description, GraphQL::STRING_TYPE, null: true field :description, GraphQL::STRING_TYPE, null: true
field :default_branch, GraphQL::STRING_TYPE, null: true
field :tag_list, GraphQL::STRING_TYPE, null: true field :tag_list, GraphQL::STRING_TYPE, null: true
field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true
...@@ -59,7 +58,6 @@ module Types ...@@ -59,7 +58,6 @@ module Types
end end
field :import_status, GraphQL::STRING_TYPE, null: true field :import_status, GraphQL::STRING_TYPE, null: true
field :ci_config_path, GraphQL::STRING_TYPE, null: true
field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true
... ...
......
...@@ -100,17 +100,6 @@ module CiStatusHelper ...@@ -100,17 +100,6 @@ module CiStatusHelper
"pipeline-status/#{pipeline_status.sha}-#{pipeline_status.status}" "pipeline-status/#{pipeline_status.sha}-#{pipeline_status.status}"
end end
def render_project_pipeline_status(pipeline_status, tooltip_placement: 'left')
project = pipeline_status.project
path = pipelines_project_commit_path(project, pipeline_status.sha, ref: pipeline_status.ref)
render_status_with_link(
'commit',
pipeline_status.status,
path,
tooltip_placement: tooltip_placement)
end
def render_commit_status(commit, ref: nil, tooltip_placement: 'left') def render_commit_status(commit, ref: nil, tooltip_placement: 'left')
project = commit.project project = commit.project
path = pipelines_project_commit_path(project, commit, ref: ref) path = pipelines_project_commit_path(project, commit, ref: ref)
...@@ -123,12 +112,6 @@ module CiStatusHelper ...@@ -123,12 +112,6 @@ module CiStatusHelper
icon_size: 24) icon_size: 24)
end end
def render_pipeline_status(pipeline, tooltip_placement: 'left')
project = pipeline.project
path = project_pipeline_path(project, pipeline)
render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement)
end
def render_status_with_link(type, status, path = nil, tooltip_placement: 'left', cssclass: '', container: 'body', icon_size: 16) def render_status_with_link(type, status, path = nil, tooltip_placement: 'left', cssclass: '', container: 'body', icon_size: 16)
klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}" klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}"
title = "#{type.titleize}: #{ci_label_for_status(status)}" title = "#{type.titleize}: #{ci_label_for_status(status)}"
... ...
......
...@@ -74,6 +74,7 @@ module Emails ...@@ -74,6 +74,7 @@ module Emails
@new_issue = new_issue @new_issue = new_issue
@new_project = new_issue.project @new_project = new_issue.project
@can_access_project = recipient.can?(:read_project, @new_project)
mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id, reason)) mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id, reason))
end end
... ...
......