Commit aefe6486 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/security/gitlab@13-6-stable-ee

parent 66ebf02c
...@@ -18,7 +18,13 @@ import { __, sprintf } from '~/locale'; ...@@ -18,7 +18,13 @@ import { __, sprintf } from '~/locale';
// //
// This is an arbitrary number; Can be iterated upon when suitable. // This is an arbitrary number; Can be iterated upon when suitable.
const MAX_CHAR_LIMIT = 5000; const MAX_CHAR_LIMIT = 2000;
// Max # of mermaid blocks that can be rendered in a page.
const MAX_MERMAID_BLOCK_LIMIT = 50;
// Keep a map of mermaid blocks we've already rendered.
const elsProcessingMap = new WeakMap();
let renderedMermaidBlocks = 0;
let mermaidModule = {}; let mermaidModule = {};
function importMermaidModule() { function importMermaidModule() {
...@@ -110,13 +116,22 @@ function renderMermaids($els) { ...@@ -110,13 +116,22 @@ function renderMermaids($els) {
let renderedChars = 0; let renderedChars = 0;
$els.each((i, el) => { $els.each((i, el) => {
// Skipping all the elements which we've already queued in requestIdleCallback
if (elsProcessingMap.has(el)) {
return;
}
const { source } = fixElementSource(el); const { source } = fixElementSource(el);
/** /**
* Restrict the rendering to a certain amount of character to * Restrict the rendering to a certain amount of character
* prevent mermaidjs from hanging up the entire thread and * and mermaid blocks to prevent mermaidjs from hanging
* causing a DoS. * up the entire thread and causing a DoS.
*/ */
if ((source && source.length > MAX_CHAR_LIMIT) || renderedChars > MAX_CHAR_LIMIT) { if (
(source && source.length > MAX_CHAR_LIMIT) ||
renderedChars > MAX_CHAR_LIMIT ||
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT
) {
const html = ` const html = `
<div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert"> <div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert">
<div> <div>
...@@ -146,8 +161,13 @@ function renderMermaids($els) { ...@@ -146,8 +161,13 @@ function renderMermaids($els) {
} }
renderedChars += source.length; renderedChars += source.length;
renderedMermaidBlocks += 1;
const requestId = window.requestIdleCallback(() => {
renderMermaidEl(el);
});
renderMermaidEl(el); elsProcessingMap.set(el, requestId);
}); });
}) })
.catch(err => { .catch(err => {
......
...@@ -8,6 +8,8 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -8,6 +8,8 @@ class Explore::ProjectsController < Explore::ApplicationController
include SortingHelper include SortingHelper
include SortingPreference include SortingPreference
MIN_SEARCH_LENGTH = 3
before_action :set_non_archived_param before_action :set_non_archived_param
before_action :set_sorting before_action :set_sorting
...@@ -72,7 +74,7 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -72,7 +74,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def load_projects def load_projects
load_project_counts load_project_counts
projects = ProjectsFinder.new(current_user: current_user, params: params).execute projects = ProjectsFinder.new(current_user: current_user, params: params.merge(minimum_search_length: MIN_SEARCH_LENGTH)).execute
projects = preload_associations(projects) projects = preload_associations(projects)
projects = projects.page(params[:page]).without_count projects = projects.page(params[:page]).without_count
......
...@@ -144,7 +144,6 @@ class SearchController < ApplicationController ...@@ -144,7 +144,6 @@ class SearchController < ApplicationController
payload[:metadata] ||= {} payload[:metadata] ||= {}
payload[:metadata]['meta.search.group_id'] = params[:group_id] payload[:metadata]['meta.search.group_id'] = params[:group_id]
payload[:metadata]['meta.search.project_id'] = params[:project_id] payload[:metadata]['meta.search.project_id'] = params[:project_id]
payload[:metadata]['meta.search.search'] = params[:search]
payload[:metadata]['meta.search.scope'] = params[:scope] payload[:metadata]['meta.search.scope'] = params[:scope]
payload[:metadata]['meta.search.filters.confidential'] = params[:confidential] payload[:metadata]['meta.search.filters.confidential'] = params[:confidential]
payload[:metadata]['meta.search.filters.state'] = params[:state] payload[:metadata]['meta.search.filters.state'] = params[:state]
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
# personal: boolean # personal: boolean
# search: string # search: string
# search_namespaces: boolean # search_namespaces: boolean
# minimum_search_length: int
# non_archived: boolean # non_archived: boolean
# archived: 'only' or boolean # archived: 'only' or boolean
# min_access_level: integer # min_access_level: integer
...@@ -182,6 +183,9 @@ class ProjectsFinder < UnionFinder ...@@ -182,6 +183,9 @@ class ProjectsFinder < UnionFinder
def by_search(items) def by_search(items)
params[:search] ||= params[:name] params[:search] ||= params[:name]
return items.none if params[:search].present? && params[:minimum_search_length].present? && params[:search].length < params[:minimum_search_length].to_i
items.optionally_search(params[:search], include_namespace: params[:search_namespaces].present?) items.optionally_search(params[:search], include_namespace: params[:search_namespaces].present?)
end end
......
...@@ -30,8 +30,7 @@ module Types ...@@ -30,8 +30,7 @@ module Types
resolver: Resolvers::TodoResolver, resolver: Resolvers::TodoResolver,
description: 'Todos of the user' description: 'Todos of the user'
field :group_memberships, Types::GroupMemberType.connection_type, null: true, field :group_memberships, Types::GroupMemberType.connection_type, null: true,
description: 'Group memberships of the user', description: 'Group memberships of the user'
method: :group_members
field :group_count, GraphQL::INT_TYPE, null: true, field :group_count, GraphQL::INT_TYPE, null: true,
resolver: Resolvers::Users::GroupCountResolver, resolver: Resolvers::Users::GroupCountResolver,
description: 'Group count for the user', description: 'Group count for the user',
...@@ -39,8 +38,7 @@ module Types ...@@ -39,8 +38,7 @@ module Types
field :status, Types::UserStatusType, null: true, field :status, Types::UserStatusType, null: true,
description: 'User status' description: 'User status'
field :project_memberships, Types::ProjectMemberType.connection_type, null: true, field :project_memberships, Types::ProjectMemberType.connection_type, null: true,
description: 'Project memberships of the user', description: 'Project memberships of the user'
method: :project_members
field :starred_projects, Types::ProjectType.connection_type, null: true, field :starred_projects, Types::ProjectType.connection_type, null: true,
description: 'Projects starred by the user', description: 'Projects starred by the user',
resolver: Resolvers::UserStarredProjectsResolver resolver: Resolvers::UserStarredProjectsResolver
......
...@@ -2,4 +2,18 @@ ...@@ -2,4 +2,18 @@
class UserPresenter < Gitlab::View::Presenter::Delegated class UserPresenter < Gitlab::View::Presenter::Delegated
presents :user presents :user
def group_memberships
should_be_private? ? GroupMember.none : user.group_members
end
def project_memberships
should_be_private? ? ProjectMember.none : user.project_members
end
private
def should_be_private?
!can?(current_user, :read_user_profile, user)
end
end end
= render 'shared/projects/list', projects: projects, user: current_user, explore_page: true, pipeline_status: Feature.enabled?(:dashboard_pipeline_status, default_enabled: true) - if params[:name].present? && params[:name].size < Explore::ProjectsController::MIN_SEARCH_LENGTH
.nothing-here-block
%h5= _('Enter at least three characters to search')
- else
= render 'shared/projects/list', projects: projects, user: current_user, explore_page: true, pipeline_status: Feature.enabled?(:dashboard_pipeline_status, default_enabled: true)
---
title: Ensure group and project memberships are not leaked via API for users with private profiles
merge_request:
author:
type: security
---
title: Fix mermaid resource consumption in GFM fields
merge_request:
author:
type: security
---
title: Require at least 3 characters when searching for project in the Explore page
merge_request:
author:
type: security
---
title: Filter search parameter to prevent data leaks
merge_request:
author:
type: security
...@@ -137,6 +137,7 @@ module Gitlab ...@@ -137,6 +137,7 @@ module Gitlab
encrypted_key encrypted_key
import_url import_url
elasticsearch_url elasticsearch_url
search
otp_attempt otp_attempt
sentry_dsn sentry_dsn
trace trace
......
...@@ -272,7 +272,7 @@ RSpec.describe SearchController do ...@@ -272,7 +272,7 @@ RSpec.describe SearchController do
expect(last_payload[:metadata]['meta.search.group_id']).to eq('123') expect(last_payload[:metadata]['meta.search.group_id']).to eq('123')
expect(last_payload[:metadata]['meta.search.project_id']).to eq('456') expect(last_payload[:metadata]['meta.search.project_id']).to eq('456')
expect(last_payload[:metadata]['meta.search.search']).to eq('hello world') expect(last_payload[:metadata]).not_to have_key('meta.search.search')
expect(last_payload[:metadata]['meta.search.scope']).to eq('issues') expect(last_payload[:metadata]['meta.search.scope']).to eq('issues')
expect(last_payload[:metadata]['meta.search.force_search_results']).to eq('true') expect(last_payload[:metadata]['meta.search.force_search_results']).to eq('true')
expect(last_payload[:metadata]['meta.search.filters.confidential']).to eq('true') expect(last_payload[:metadata]['meta.search.filters.confidential']).to eq('true')
......
...@@ -47,6 +47,14 @@ RSpec.describe 'User explores projects' do ...@@ -47,6 +47,14 @@ RSpec.describe 'User explores projects' do
end end
end end
shared_examples 'minimum search length' do
it 'shows a prompt to enter a longer search term', :js do
fill_in 'name', with: 'z'
expect(page).to have_content('Enter at least three characters to search')
end
end
context 'when viewing public projects' do context 'when viewing public projects' do
before do before do
visit(explore_projects_path) visit(explore_projects_path)
...@@ -54,6 +62,7 @@ RSpec.describe 'User explores projects' do ...@@ -54,6 +62,7 @@ RSpec.describe 'User explores projects' do
include_examples 'shows public and internal projects' include_examples 'shows public and internal projects'
include_examples 'empty search results' include_examples 'empty search results'
include_examples 'minimum search length'
end end
context 'when viewing most starred projects' do context 'when viewing most starred projects' do
...@@ -63,6 +72,7 @@ RSpec.describe 'User explores projects' do ...@@ -63,6 +72,7 @@ RSpec.describe 'User explores projects' do
include_examples 'shows public and internal projects' include_examples 'shows public and internal projects'
include_examples 'empty search results' include_examples 'empty search results'
include_examples 'minimum search length'
end end
context 'when viewing trending projects' do context 'when viewing trending projects' do
...@@ -76,6 +86,7 @@ RSpec.describe 'User explores projects' do ...@@ -76,6 +86,7 @@ RSpec.describe 'User explores projects' do
include_examples 'shows public projects' include_examples 'shows public projects'
include_examples 'empty search results' include_examples 'empty search results'
include_examples 'minimum search length'
end end
end end
end end
......
...@@ -19,6 +19,9 @@ RSpec.describe 'Mermaid rendering', :js do ...@@ -19,6 +19,9 @@ RSpec.describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
wait_for_requests
wait_for_mermaid
%w[A B C D].each do |label| %w[A B C D].each do |label|
expect(page).to have_selector('svg text', text: label) expect(page).to have_selector('svg text', text: label)
end end
...@@ -39,6 +42,7 @@ RSpec.describe 'Mermaid rendering', :js do ...@@ -39,6 +42,7 @@ RSpec.describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
wait_for_requests wait_for_requests
wait_for_mermaid
expected = '<text style=""><tspan xml:space="preserve" dy="1em" x="1">Line 1</tspan><tspan xml:space="preserve" dy="1em" x="1">Line 2</tspan></text>' expected = '<text style=""><tspan xml:space="preserve" dy="1em" x="1">Line 1</tspan><tspan xml:space="preserve" dy="1em" x="1">Line 2</tspan></text>'
expect(page.html.scan(expected).count).to be(4) expect(page.html.scan(expected).count).to be(4)
...@@ -65,6 +69,9 @@ RSpec.describe 'Mermaid rendering', :js do ...@@ -65,6 +69,9 @@ RSpec.describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
wait_for_requests
wait_for_mermaid
page.within('.description') do page.within('.description') do
expect(page).to have_selector('svg') expect(page).to have_selector('svg')
expect(page).to have_selector('pre.mermaid') expect(page).to have_selector('pre.mermaid')
...@@ -92,6 +99,9 @@ RSpec.describe 'Mermaid rendering', :js do ...@@ -92,6 +99,9 @@ RSpec.describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
wait_for_requests
wait_for_mermaid
page.within('.description') do page.within('.description') do
page.find('summary').click page.find('summary').click
svg = page.find('svg.mermaid') svg = page.find('svg.mermaid')
...@@ -118,6 +128,9 @@ RSpec.describe 'Mermaid rendering', :js do ...@@ -118,6 +128,9 @@ RSpec.describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
wait_for_requests
wait_for_mermaid
expect(page).to have_css('svg.mermaid[style*="max-width"][width="100%"]') expect(page).to have_css('svg.mermaid[style*="max-width"][width="100%"]')
end end
...@@ -147,6 +160,7 @@ RSpec.describe 'Mermaid rendering', :js do ...@@ -147,6 +160,7 @@ RSpec.describe 'Mermaid rendering', :js do
end end
wait_for_requests wait_for_requests
wait_for_mermaid
find('.js-lazy-render-mermaid').click find('.js-lazy-render-mermaid').click
...@@ -156,4 +170,55 @@ RSpec.describe 'Mermaid rendering', :js do ...@@ -156,4 +170,55 @@ RSpec.describe 'Mermaid rendering', :js do
expect(page).not_to have_selector('.js-lazy-render-mermaid-container') expect(page).not_to have_selector('.js-lazy-render-mermaid-container')
end end
end end
it 'does not render more than 50 mermaid blocks', :js, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/234081' } do
graph_edges = "A-->B;B-->A;"
description = <<~MERMAID
```mermaid
graph LR
#{graph_edges}
```
MERMAID
description *= 51
project = create(:project, :public)
issue = create(:issue, project: project, description: description)
visit project_issue_path(project, issue)
wait_for_requests
wait_for_mermaid
page.within('.description') do
expect(page).to have_selector('svg')
expect(page).to have_selector('.lazy-alert-shown')
expect(page).to have_selector('.js-lazy-render-mermaid-container')
end
end
end
def wait_for_mermaid
run_idle_callback = <<~RUN_IDLE_CALLBACK
window.requestIdleCallback(() => {
window.__CAPYBARA_IDLE_CALLBACK_EXEC__ = 1;
})
RUN_IDLE_CALLBACK
page.evaluate_script(run_idle_callback)
Timeout.timeout(Capybara.default_max_wait_time) do
loop until finished_rendering?
end
end
def finished_rendering?
check_idle_callback = <<~CHECK_IDLE_CALLBACK
window.__CAPYBARA_IDLE_CALLBACK_EXEC__
CHECK_IDLE_CALLBACK
page.evaluate_script(check_idle_callback) == 1
end end
...@@ -161,6 +161,29 @@ RSpec.describe ProjectsFinder, :do_not_mock_admin_mode do ...@@ -161,6 +161,29 @@ RSpec.describe ProjectsFinder, :do_not_mock_admin_mode do
it { is_expected.to eq([public_project]) } it { is_expected.to eq([public_project]) }
end end
describe 'filter by search with minimum search length' do
context 'when search term is shorter than minimum length' do
let(:params) { { search: 'C', minimum_search_length: 3 } }
it { is_expected.to be_empty }
end
context 'when search term is longer than minimum length' do
let(:project) { create(:project, :public, group: group, name: 'test_project') }
let(:params) { { search: 'test', minimum_search_length: 3 } }
it { is_expected.to eq([project]) }
end
context 'when minimum length is invalid' do
let(:params) { { search: 'C', minimum_search_length: 'x' } }
it 'ignores the minimum length param' do
is_expected.to eq([public_project])
end
end
end
describe 'filter by group name' do describe 'filter by group name' do
let(:params) { { name: group.name, search_namespaces: true } } let(:params) { { name: group.name, search_namespaces: true } }
......
...@@ -250,7 +250,7 @@ RSpec.describe 'getting user information' do ...@@ -250,7 +250,7 @@ RSpec.describe 'getting user information' do
context 'the user is private' do context 'the user is private' do
before do before do
user.update(private_profile: true) user.update!(private_profile: true)
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
end end
...@@ -260,6 +260,50 @@ RSpec.describe 'getting user information' do ...@@ -260,6 +260,50 @@ RSpec.describe 'getting user information' do
it_behaves_like 'a working graphql query' it_behaves_like 'a working graphql query'
end end
context 'we request the groupMemberships' do
let_it_be(:membership_a) { create(:group_member, user: user) }
let(:group_memberships) { graphql_data_at(:user, :group_memberships, :nodes) }
let(:user_fields) { 'groupMemberships { nodes { id } }' }
it_behaves_like 'a working graphql query'
it 'cannot be found' do
expect(group_memberships).to be_empty
end
context 'the current user is the user' do
let(:current_user) { user }
it 'can be found' do
expect(group_memberships).to include(
a_hash_including('id' => global_id_of(membership_a))
)
end
end
end
context 'we request the projectMemberships' do
let_it_be(:membership_a) { create(:project_member, user: user) }
let(:project_memberships) { graphql_data_at(:user, :project_memberships, :nodes) }
let(:user_fields) { 'projectMemberships { nodes { id } }' }
it_behaves_like 'a working graphql query'
it 'cannot be found' do
expect(project_memberships).to be_empty
end
context 'the current user is the user' do
let(:current_user) { user }
it 'can be found' do
expect(project_memberships).to include(
a_hash_including('id' => global_id_of(membership_a))
)
end
end
end
context 'we request the authoredMergeRequests' do context 'we request the authoredMergeRequests' do
let(:user_fields) { 'authoredMergeRequests { nodes { id } }' } let(:user_fields) { 'authoredMergeRequests { nodes { id } }' }
......
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