New upstream version 13.4.7

This commit is contained in:
Pirate Praveen 2020-12-08 15:28:05 +05:30
parent a557ad5f5f
commit 03a7b5db89
35 changed files with 620 additions and 57 deletions

View file

@ -2,6 +2,22 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 13.4.7 (2020-12-07)
### Security (10 changes)
- Validate zoom links to start with https only. !1055
- Require at least 3 characters when searching for project in the Explore page.
- Do not show emails of users in confirmation page.
- Forbid setting a gitlabUserList strategy to a list from another project.
- Fix mermaid resource consumption in GFM fields.
- Ensure group and project memberships are not leaked via API for users with private profiles.
- GraphQL User: do not expose email if set to private.
- Filter search parameter to prevent data leaks.
- Do not expose starred projects of users with private profile via API.
- Do not show starred & contributed projects of users with private profile.
## 13.4.6 (2020-11-03)
### Fixed (1 change)

View file

@ -1 +1 @@
13.4.6
13.4.7

View file

@ -1 +1 @@
13.4.6
13.4.7

View file

@ -18,7 +18,13 @@ import { __, sprintf } from '~/locale';
//
// 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 = {};
function importMermaidModule() {
@ -110,13 +116,22 @@ function renderMermaids($els) {
let renderedChars = 0;
$els.each((i, el) => {
// Skipping all the elements which we've already queued in requestIdleCallback
if (elsProcessingMap.has(el)) {
return;
}
const { source } = fixElementSource(el);
/**
* Restrict the rendering to a certain amount of character to
* prevent mermaidjs from hanging up the entire thread and
* causing a DoS.
* Restrict the rendering to a certain amount of character
* and mermaid blocks to prevent mermaidjs from hanging
* 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 = `
<div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert">
<div>
@ -146,9 +161,14 @@ function renderMermaids($els) {
}
renderedChars += source.length;
renderedMermaidBlocks += 1;
const requestId = window.requestIdleCallback(() => {
renderMermaidEl(el);
});
elsProcessingMap.set(el, requestId);
});
})
.catch(err => {
flash(sprintf(__('Encountered an error while rendering: %{err}'), { err }));

View file

@ -8,6 +8,8 @@ class Explore::ProjectsController < Explore::ApplicationController
include SortingHelper
include SortingPreference
MIN_SEARCH_LENGTH = 3
before_action :set_non_archived_param
before_action :set_sorting
@ -70,7 +72,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def load_projects
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 = projects.page(params[:page]).without_count

View file

@ -126,7 +126,6 @@ class SearchController < ApplicationController
payload[:metadata] ||= {}
payload[:metadata]['meta.search.group_id'] = params[:group_id]
payload[:metadata]['meta.search.project_id'] = params[:project_id]
payload[:metadata]['meta.search.search'] = params[:search]
payload[:metadata]['meta.search.scope'] = params[:scope]
end

View file

@ -19,7 +19,7 @@ class UsersController < ApplicationController
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
before_action :user, except: [:exists, :suggests]
before_action :authorize_read_user_profile!,
only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :starred_projects, :snippets]
only: [:calendar, :calendar_activities, :groups, :projects, :contributed, :starred, :snippets]
def show
respond_to do |format|

View file

@ -18,6 +18,7 @@
# personal: boolean
# search: string
# search_namespaces: boolean
# minimum_search_length: int
# non_archived: boolean
# archived: 'only' or boolean
# min_access_level: integer
@ -177,6 +178,9 @@ class ProjectsFinder < UnionFinder
def by_search(items)
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?)
end

View file

@ -1,11 +1,22 @@
# frozen_string_literal: true
class StarredProjectsFinder < ProjectsFinder
include Gitlab::Allowable
def initialize(user, params: {}, current_user: nil)
@user = user
super(
params: params,
current_user: current_user,
project_ids_relation: user.starred_projects.select(:id)
)
end
def execute
# Do not show starred projects if the user has a private profile.
return Project.none unless can?(current_user, :read_user_profile, @user)
super
end
end

View file

@ -19,7 +19,7 @@ module Types
field :state, Types::UserStateEnum, null: false,
description: 'State of the user'
field :email, GraphQL::STRING_TYPE, null: true,
description: 'User email'
description: 'User email', method: :public_email
field :avatar_url, GraphQL::STRING_TYPE, null: true,
description: "URL of the user's avatar"
field :web_url, GraphQL::STRING_TYPE, null: false,
@ -30,13 +30,11 @@ module Types
resolver: Resolvers::TodoResolver,
description: 'Todos of the user'
field :group_memberships, Types::GroupMemberType.connection_type, null: true,
description: 'Group memberships of the user',
method: :group_members
description: 'Group memberships of the user'
field :status, Types::UserStatusType, null: true,
description: 'User status'
field :project_memberships, Types::ProjectMemberType.connection_type, null: true,
description: 'Project memberships of the user',
method: :project_members
description: 'Project memberships of the user'
field :starred_projects, Types::ProjectType.connection_type, null: true,
description: 'Projects starred by the user',
resolver: Resolvers::UserStarredProjectsResolver

View file

@ -23,6 +23,11 @@ module Operations
before_destroy :ensure_no_associated_strategies
def self.belongs_to?(project_id, user_list_ids)
uniq_ids = user_list_ids.uniq
where(id: uniq_ids, project_id: project_id).count == uniq_ids.count
end
private
def ensure_no_associated_strategies

View file

@ -2,4 +2,18 @@
class UserPresenter < Gitlab::View::Presenter::Delegated
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

View file

@ -24,7 +24,7 @@ module Todos
# if at least reporter, all entities including confidential issues can be accessed
return if user_has_reporter_access?
remove_confidential_issue_todos
remove_confidential_resource_todos
if entity.private?
remove_project_todos
@ -43,7 +43,7 @@ module Todos
end
# rubocop: disable CodeReuse/ActiveRecord
def remove_confidential_issue_todos
def remove_confidential_resource_todos
Todo.where(
target_id: confidential_issues.select(:id), target_type: Issue.name, user_id: user.id
).delete_all
@ -147,3 +147,5 @@ module Todos
end
end
end
Todos::Destroy::EntityLeaveService.prepend_if_ee('EE::Todos::Destroy::EntityLeaveService')

View file

@ -5,8 +5,13 @@
# Custom validator for zoom urls
#
class ZoomUrlValidator < ActiveModel::EachValidator
ALLOWED_SCHEMES = %w(https).freeze
def validate_each(record, attribute, value)
return if Gitlab::ZoomLinkExtractor.new(value).links.size == 1
links_count = Gitlab::ZoomLinkExtractor.new(value).links.size
valid = Gitlab::UrlSanitizer.valid?(value, allowed_schemes: ALLOWED_SCHEMES)
return if links_count == 1 && valid
record.errors.add(:url, 'must contain one valid Zoom URL')
end

View file

@ -6,7 +6,7 @@
= render "devise/shared/error_messages", resource: resource
.form-group
= f.label :email
= f.email_field :email, class: "form-control", required: true, title: 'Please provide a valid email address.'
= f.email_field :email, class: "form-control", required: true, title: 'Please provide a valid email address.', value: nil
.clearfix
= f.submit "Resend", class: 'btn btn-success'

View file

@ -1,2 +1,6 @@
- is_explore_page = defined?(explore_page) && explore_page
= render 'shared/projects/list', projects: projects, user: current_user, explore_page: is_explore_page, 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
- is_explore_page = defined?(explore_page) && explore_page
= render 'shared/projects/list', projects: projects, user: current_user, explore_page: is_explore_page, pipeline_status: Feature.enabled?(:dashboard_pipeline_status, default_enabled: true)

View file

@ -135,6 +135,7 @@ module Gitlab
hook
import_url
elasticsearch_url
search
otp_attempt
sentry_dsn
trace

View file

@ -46,6 +46,7 @@
- dynamic_application_security_testing
- editor_extension
- epics
- epic_tracking
- error_tracking
- feature_flags
- foundations

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
class ScheduleRemoveInaccessibleEpicTodos < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INTERVAL = 2.minutes
BATCH_SIZE = 10
MIGRATION = 'RemoveInaccessibleEpicTodos'
disable_ddl_transaction!
class Epic < ActiveRecord::Base
include EachBatch
end
def up
return unless Gitlab.ee?
relation = Epic.where(confidential: true)
queue_background_migration_jobs_by_range_at_intervals(
relation, MIGRATION, INTERVAL, batch_size: BATCH_SIZE)
end
def down
# no-op
end
end

View file

@ -0,0 +1 @@
ae8034ec52df47ce2ce3397715dd18347e4d297a963c17c7b26321f414dfa632

View file

@ -129,6 +129,10 @@ Note the following when promoting a secondary:
```
1. Promote the **secondary** node to the **primary** node.
CAUTION: **Caution:**
If the secondary node [has been paused](../../geo/index.md#pausing-and-resuming-replication), this performs
a point-in-time recovery to the last known state.
Data that was created on the primary while the secondary was paused will be lost.
To promote the secondary node to primary along with preflight checks:
@ -159,11 +163,16 @@ conjunction with multiple servers, as it can only
perform changes on a **secondary** with only a single machine. Instead, you must
do this manually.
CAUTION: **Caution:**
If the secondary node [has been paused](../../geo/index.md#pausing-and-resuming-replication), this performs
a point-in-time recovery to the last known state.
Data that was created on the primary while the secondary was paused will be lost.
1. SSH in to the database node in the **secondary** and trigger PostgreSQL to
promote to read-write:
```shell
sudo gitlab-pg-ctl promote
sudo gitlab-ctl promote-db
```
In GitLab 12.8 and earlier, see [Message: `sudo: gitlab-pg-ctl: command not found`](../replication/troubleshooting.md#message-sudo-gitlab-pg-ctl-command-not-found).

View file

@ -195,6 +195,10 @@ For information on how to update your Geo nodes to the latest GitLab version, se
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35913) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
CAUTION: **Caution:**
Pausing and resuming of replication is currently only supported for Geo installations using an
Omnibus GitLab-managed database. External databases are currently not supported.
In some circumstances, like during [upgrades](replication/updating_the_geo_nodes.md) or a [planned failover](disaster_recovery/planned_failover.md), it is desirable to pause replication between the primary and secondary.
Pausing and resuming replication is done via a command line tool from the secondary node.

View file

@ -64,7 +64,7 @@ To-do triggers aren't affected by [GitLab notification email settings](profile/n
NOTE: **Note:**
When a user no longer has access to a resource related to a to-do (such as an
issue, merge request, project, or group), for security reasons GitLab deletes
issue, merge request, epic, project, or group), for security reasons GitLab deletes
any related to-do items within the next hour. Deletion is delayed to prevent
data loss, in the case where a user's access is accidentally revoked.

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# rubocop:disable Style/Documentation
class RemoveInaccessibleEpicTodos
def perform(start_id, stop_id)
end
end
end
end
Gitlab::BackgroundMigration::RemoveInaccessibleEpicTodos.prepend_if_ee('EE::Gitlab::BackgroundMigration::RemoveInaccessibleEpicTodos')

View file

@ -0,0 +1,80 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ConfirmationsController do
include DeviseHelpers
before do
set_devise_mapping(context: @request)
end
describe '#show' do
render_views
subject { get :show, params: { confirmation_token: confirmation_token } }
context 'user is already confirmed' do
let_it_be_with_reload(:user) { create(:user, :unconfirmed) }
let(:confirmation_token) { user.confirmation_token }
before do
user.confirm
subject
end
it 'renders `new`' do
expect(response).to render_template(:new)
end
it 'displays an error message' do
expect(response.body).to include('Email was already confirmed, please try signing in')
end
it 'does not display the email of the user' do
expect(response.body).not_to include(user.email)
end
end
context 'user accesses the link after the expiry of confirmation token has passed' do
let_it_be_with_reload(:user) { create(:user, :unconfirmed) }
let(:confirmation_token) { user.confirmation_token }
before do
allow(Devise).to receive(:confirm_within).and_return(1.day)
travel_to(3.days.from_now) do
subject
end
end
it 'renders `new`' do
expect(response).to render_template(:new)
end
it 'displays an error message' do
expect(response.body).to include('Email needs to be confirmed within 1 day, please request a new one below')
end
it 'does not display the email of the user' do
expect(response.body).not_to include(user.email)
end
end
context 'with an invalid confirmation token' do
let(:confirmation_token) { 'invalid_confirmation_token' }
before do
subject
end
it 'renders `new`' do
expect(response).to render_template(:new)
end
it 'displays an error message' do
expect(response.body).to include('Confirmation token is invalid')
end
end
end
end

View file

@ -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.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')
end
end

View file

@ -247,32 +247,99 @@ RSpec.describe UsersController do
describe 'GET #contributed' do
let(:project) { create(:project, :public) }
let(:current_user) { create(:user) }
subject do
get :contributed, params: { username: author.username }, format: format
end
before do
sign_in(current_user)
sign_in(user)
project.add_developer(public_user)
project.add_developer(private_user)
create(:push_event, project: project, author: author)
subject
end
shared_examples_for 'renders contributed projects' do
it 'renders contributed projects' do
expect(assigns[:contributed_projects]).not_to be_empty
expect(response).to have_gitlab_http_status(:ok)
end
end
%i(html json).each do |format|
context "format: #{format}" do
let(:format) { format }
context 'with public profile' do
it 'renders contributed projects' do
create(:push_event, project: project, author: public_user)
let(:author) { public_user }
get :contributed, params: { username: public_user.username }
expect(assigns[:contributed_projects]).not_to be_empty
end
it_behaves_like 'renders contributed projects'
end
context 'with private profile' do
it 'does not render contributed projects' do
create(:push_event, project: project, author: private_user)
let(:author) { private_user }
get :contributed, params: { username: private_user.username }
it 'returns 404' do
expect(response).to have_gitlab_http_status(:not_found)
end
expect(assigns[:contributed_projects]).to be_empty
context 'with a user that has the ability to read private profiles', :enable_admin_mode do
let(:user) { create(:admin) }
it_behaves_like 'renders contributed projects'
end
end
end
end
end
describe 'GET #starred' do
let(:project) { create(:project, :public) }
subject do
get :starred, params: { username: author.username }, format: format
end
before do
author.toggle_star(project)
sign_in(user)
subject
end
shared_examples_for 'renders starred projects' do
it 'renders starred projects' do
expect(response).to have_gitlab_http_status(:ok)
expect(assigns[:starred_projects]).not_to be_empty
end
end
%i(html json).each do |format|
context "format: #{format}" do
let(:format) { format }
context 'with public profile' do
let(:author) { public_user }
it_behaves_like 'renders starred projects'
end
context 'with private profile' do
let(:author) { private_user }
it 'returns 404' do
expect(response).to have_gitlab_http_status(:not_found)
end
context 'with a user that has the ability to read private profiles', :enable_admin_mode do
let(:user) { create(:admin) }
it_behaves_like 'renders starred projects'
end
end
end
end
end

View file

@ -34,6 +34,16 @@ RSpec.describe 'User explores projects' do
before do
sign_in(user)
stub_feature_flags(project_list_filter_bar: false)
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
@ -42,6 +52,7 @@ RSpec.describe 'User explores projects' do
end
include_examples 'shows public and internal projects'
include_examples 'minimum search length'
end
context 'when viewing most starred projects' do
@ -50,6 +61,7 @@ RSpec.describe 'User explores projects' do
end
include_examples 'shows public and internal projects'
include_examples 'minimum search length'
end
context 'when viewing trending projects' do
@ -62,6 +74,7 @@ RSpec.describe 'User explores projects' do
end
include_examples 'shows public projects'
include_examples 'minimum search length'
end
end
end

View file

@ -19,6 +19,9 @@ RSpec.describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue)
wait_for_requests
wait_for_mermaid
%w[A B C D].each do |label|
expect(page).to have_selector('svg text', text: label)
end
@ -39,6 +42,7 @@ RSpec.describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue)
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>'
expect(page.html.scan(expected).count).to be(4)
@ -65,6 +69,9 @@ RSpec.describe 'Mermaid rendering', :js do
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('pre.mermaid')
@ -92,6 +99,9 @@ RSpec.describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue)
wait_for_requests
wait_for_mermaid
page.within('.description') do
page.find('summary').click
svg = page.find('svg.mermaid')
@ -118,6 +128,9 @@ RSpec.describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue)
wait_for_requests
wait_for_mermaid
expect(page).to have_css('svg.mermaid[style*="max-width"][width="100%"]')
end
@ -147,6 +160,7 @@ RSpec.describe 'Mermaid rendering', :js do
end
wait_for_requests
wait_for_mermaid
find('.js-lazy-render-mermaid').click
@ -156,4 +170,55 @@ RSpec.describe 'Mermaid rendering', :js do
expect(page).not_to have_selector('.js-lazy-render-mermaid-container')
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

View file

@ -157,6 +157,29 @@ RSpec.describe ProjectsFinder, :do_not_mock_admin_mode do
it { is_expected.to eq([public_project]) }
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
let(:params) { { name: group.name, search_namespaces: true } }

View file

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe StarredProjectsFinder do
let(:project1) { create(:project, :public, :empty_repo) }
let(:project2) { create(:project, :public, :empty_repo) }
let(:other_project) { create(:project, :public, :empty_repo) }
let(:private_project) { create(:project, :private, :empty_repo) }
let(:user) { create(:user) }
let(:other_user) { create(:user) }
@ -13,6 +13,9 @@ RSpec.describe StarredProjectsFinder do
before do
user.toggle_star(project1)
user.toggle_star(project2)
private_project.add_maintainer(user)
user.toggle_star(private_project)
end
describe '#execute' do
@ -20,10 +23,11 @@ RSpec.describe StarredProjectsFinder do
subject { finder.execute }
context 'user has a public profile' do
describe 'as same user' do
let(:current_user) { user }
it { is_expected.to contain_exactly(project1, project2) }
it { is_expected.to contain_exactly(project1, project2, private_project) }
end
describe 'as other user' do
@ -38,4 +42,37 @@ RSpec.describe StarredProjectsFinder do
it { is_expected.to contain_exactly(project1, project2) }
end
end
context 'user has a private profile' do
before do
user.update!(private_profile: true)
end
describe 'as same user' do
let(:current_user) { user }
it { is_expected.to contain_exactly(project1, project2, private_project) }
end
describe 'as other user' do
context 'user does not have access to view the private profile' do
let(:current_user) { other_user }
it { is_expected.to be_empty }
end
context 'user has access to view the private profile', :enable_admin_mode do
let(:current_user) { create(:admin) }
it { is_expected.to contain_exactly(project1, project2, private_project) }
end
end
describe 'as no user' do
let(:current_user) { nil }
it { is_expected.to be_empty }
end
end
end
end

View file

@ -70,4 +70,31 @@ RSpec.describe 'Getting starredProjects of the user' do
)
end
end
context 'the user has a private profile' do
before do
user.update!(private_profile: true)
post_graphql(query, current_user: current_user)
end
context 'the current user does not have access to view the private profile of the user' do
let(:current_user) { create(:user) }
it 'finds no projects' do
expect(starred_projects).to be_empty
end
end
context 'the current user has access to view the private profile of the user' do
let(:current_user) { create(:admin) }
it 'finds all projects starred by the user, which the current user has access to' do
expect(starred_projects).to contain_exactly(
a_hash_including('id' => global_id_of(project_a)),
a_hash_including('id' => global_id_of(project_b)),
a_hash_including('id' => global_id_of(project_c))
)
end
end
end
end

View file

@ -77,7 +77,7 @@ RSpec.describe 'getting user information' do
'webUrl' => presenter.web_url,
'avatarUrl' => presenter.avatar_url,
'status' => presenter.status,
'email' => presenter.email
'email' => presenter.public_email
))
end
@ -210,7 +210,7 @@ RSpec.describe 'getting user information' do
context 'the user is private' do
before do
user.update(private_profile: true)
user.update!(private_profile: true)
post_graphql(query, current_user: current_user)
end
@ -220,6 +220,50 @@ RSpec.describe 'getting user information' do
it_behaves_like 'a working graphql query'
end
context 'we request the groupMemberships' do
let_it_be(:membership_a) { create(:group_member, :developer, 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
let(:user_fields) { 'authoredMergeRequests { nodes { id } }' }

View file

@ -1255,13 +1255,46 @@ RSpec.describe API::Projects do
expect(json_response['message']).to eq('404 User Not Found')
end
context 'with a public profile' do
it 'returns projects filtered by user' do
get api("/users/#{user3.id}/starred_projects/", user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, project2.id, project3.id)
expect(json_response.map { |project| project['id'] })
.to contain_exactly(project.id, project2.id, project3.id)
end
end
context 'with a private profile' do
before do
user3.update!(private_profile: true)
user3.reload
end
context 'user does not have access to view the private profile' do
it 'returns no projects' do
get api("/users/#{user3.id}/starred_projects/", user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response).to be_empty
end
end
context 'user has access to view the private profile' do
it 'returns projects filtered by user' do
get api("/users/#{user3.id}/starred_projects/", admin)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |project| project['id'] })
.to contain_exactly(project.id, project2.id, project3.id)
end
end
end
end

View file

@ -0,0 +1,36 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ZoomUrlValidator do
let(:zoom_meeting) { build(:zoom_meeting) }
describe 'validations' do
context 'when zoom link starts with https' do
it 'passes validation' do
zoom_meeting.url = 'https://zoom.us/j/123456789'
expect(zoom_meeting.valid?).to eq(true)
expect(zoom_meeting.errors).to be_empty
end
end
shared_examples 'zoom link does not start with https' do |url|
it 'fails validation' do
zoom_meeting.url = url
expect(zoom_meeting.valid?).to eq(false)
expect(zoom_meeting.errors).to be_present
expect(zoom_meeting.errors.first[1]).to eq 'must contain one valid Zoom URL'
end
end
context 'when zoom link does not start with https' do
include_examples 'zoom link does not start with https', 'http://zoom.us/j/123456789'
context 'when zoom link does not start with a scheme' do
include_examples 'zoom link does not start with https', 'testinghttp://zoom.us/j/123456789'
end
end
end
end