Update upstream source from tag 'upstream/11.10.8+dfsg'

Update to upstream version '11.10.8+dfsg'
with Debian dir 890e9ebfea
This commit is contained in:
Pirate Praveen 2019-07-07 11:19:08 +05:30
commit ad11324c19
105 changed files with 1496 additions and 450 deletions

View file

@ -1,4 +1,4 @@
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.18-chrome-71.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.18-chrome-73.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29"
include: include:
- local: /lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml - local: /lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml
@ -655,7 +655,7 @@ gitlab:setup-mysql:
# Frontend-related jobs # Frontend-related jobs
gitlab:assets:compile: gitlab:assets:compile:
<<: *dedicated-no-docs-pull-cache-job <<: *dedicated-no-docs-pull-cache-job
image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.18-chrome-71.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1 image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.18-chrome-73.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1
dependencies: dependencies:
- setup-test-env - setup-test-env
services: services:

View file

@ -2,6 +2,53 @@
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.10.8 (2019-06-27)
- No changes.
### Security (10 changes)
- Fix Denial of Service for comments when rendering issues/MR comments.
- Gate MR head_pipeline behind read_pipeline ability.
- Fix DoS vulnerability in color validation regex.
- Expose merge requests count based on user access.
- Persist tmp snippet uploads at users.
- Add missing authorizations in GraphQL.
- Disable Rails SQL query cache when applying service templates.
- Prevent Billion Laughs attack.
- Correctly check permissions when creating snippet notes.
- Prevent the detection of merge request templates by unauthorized users.
### Performance (1 change)
- Add improvements to global search of issues and merge requests. !27817
## 11.10.7 (2019-06-26)
### Fixed (3 changes)
- Remove a default git depth in Pipelines for merge requests. !28926
- Fix label click scrolling to top. !29202
- Fix scrolling to top on assignee change. !29500
## 11.10.6 (2019-06-04)
### Fixed (7 changes, 1 of them is from the community)
- Allow a member to have an access level equal to parent group. !27913
- Fix uploading of LFS tracked file through UI. !28052
- Use 3-way merge for squashing commits. !28078
- Use a path for the related merge requests endpoint. !28171
- Fix project visibility level validation. !28305 (Peter Marko)
- Fix Rugged get_tree_entries recursive flag not working. !28494
- Use source ref in pipeline webhook. !28772
### Other (1 change)
- Fix input group height.
## 11.10.5 (2019-05-30) ## 11.10.5 (2019-05-30)
### Security (12 changes, 1 of them is from the community) ### Security (12 changes, 1 of them is from the community)

View file

@ -1 +1 @@
1.34.1 1.34.3

View file

@ -419,7 +419,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 1.19.0', require: 'gitaly' gem 'gitaly-proto', '~> 1.22.1', require: 'gitaly'
gem 'grpc', '~> 1.15.0' gem 'grpc', '~> 1.15.0'

View file

@ -281,7 +281,7 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gitaly-proto (1.19.0) gitaly-proto (1.22.1)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-default_value_for (3.1.1) gitlab-default_value_for (3.1.1)
@ -1020,7 +1020,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 1.19.0) gitaly-proto (~> 1.22.1)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1) gitlab-default_value_for (~> 3.1.1)
gitlab-markup (~> 1.7.0) gitlab-markup (~> 1.7.0)

View file

@ -1 +1 @@
11.10.5 11.10.8

View file

@ -4,6 +4,11 @@ import { generateTreeList } from '../store/utils';
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
self.addEventListener('message', e => { self.addEventListener('message', e => {
const { data } = e; const { data } = e;
if (data === undefined) {
return;
}
const { treeEntries, tree } = generateTreeList(data); const { treeEntries, tree } = generateTreeList(data);
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals

View file

@ -561,6 +561,11 @@ GitLabDropdown = (function() {
!$target.data('isLink') !$target.data('isLink')
) { ) {
e.stopPropagation(); e.stopPropagation();
// This prevents automatic scrolling to the top
if ($target.closest('a').length) {
return false;
}
} }
return true; return true;

View file

@ -275,3 +275,7 @@ label {
max-width: $input-lg-width; max-width: $input-lg-width;
width: 100%; width: 100%;
} }
.input-group-text {
max-height: $input-height;
}

View file

@ -7,6 +7,7 @@ $secondary: $gray-light;
$input-disabled-bg: $gray-light; $input-disabled-bg: $gray-light;
$input-border-color: $gray-200; $input-border-color: $gray-200;
$input-color: $gl-text-color; $input-color: $gl-text-color;
$input-font-size: $gl-font-size;
$font-family-sans-serif: $regular-font; $font-family-sans-serif: $regular-font;
$font-family-monospace: $monospace-font; $font-family-monospace: $monospace-font;
$btn-line-height: 20px; $btn-line-height: 20px;

View file

@ -75,6 +75,8 @@ input[type='checkbox']:hover {
} }
.search-input-wrap { .search-input-wrap {
width: 100%;
.search-icon, .search-icon,
.clear-icon { .clear-icon {
position: absolute; position: absolute;

View file

@ -41,7 +41,7 @@ module IssuableCollections
return if pagination_disabled? return if pagination_disabled?
@issuables = @issuables.page(params[:page]) @issuables = @issuables.page(params[:page])
@issuable_meta_data = issuable_meta_data(@issuables, collection_type) @issuable_meta_data = issuable_meta_data(@issuables, collection_type, current_user)
@total_pages = issuable_page_count @total_pages = issuable_page_count
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:enable Gitlab/ModuleWithInstanceVariables

View file

@ -11,7 +11,7 @@ module IssuableCollectionsAction
.non_archived .non_archived
.page(params[:page]) .page(params[:page])
@issuable_meta_data = issuable_meta_data(@issues, collection_type) @issuable_meta_data = issuable_meta_data(@issues, collection_type, current_user)
respond_to do |format| respond_to do |format|
format.html format.html
@ -22,7 +22,7 @@ module IssuableCollectionsAction
def merge_requests def merge_requests
@merge_requests = issuables_collection.page(params[:page]) @merge_requests = issuables_collection.page(params[:page])
@issuable_meta_data = issuable_meta_data(@merge_requests, collection_type) @issuable_meta_data = issuable_meta_data(@merge_requests, collection_type, current_user)
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:enable Gitlab/ModuleWithInstanceVariables

View file

@ -203,17 +203,17 @@ module NotesActions
# These params are also sent by the client but we need to set these based on # These params are also sent by the client but we need to set these based on
# target_type and target_id because we're checking permissions based on that # target_type and target_id because we're checking permissions based on that
create_params[:noteable_type] = params[:target_type].classify create_params[:noteable_type] = noteable.class.name
case params[:target_type] case noteable
when 'commit' when Commit
create_params[:commit_id] = params[:target_id] create_params[:commit_id] = noteable.id
when 'merge_request' when MergeRequest
create_params[:noteable_id] = params[:target_id] create_params[:noteable_id] = noteable.id
# Notes on MergeRequest can have an extra `commit_id` context # Notes on MergeRequest can have an extra `commit_id` context
create_params[:commit_id] = params.dig(:note, :commit_id) create_params[:commit_id] = params.dig(:note, :commit_id)
else else
create_params[:noteable_id] = params[:target_id] create_params[:noteable_id] = noteable.id
end end
end end
end end

View file

@ -13,6 +13,11 @@ class Projects::ApplicationController < ApplicationController
helper_method :repository, :can_collaborate_with_project?, :user_access helper_method :repository, :can_collaborate_with_project?, :user_access
rescue_from Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError do |exception|
log_exception(exception)
render_404
end
private private
def project def project

View file

@ -1,7 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Projects::TemplatesController < Projects::ApplicationController class Projects::TemplatesController < Projects::ApplicationController
before_action :authenticate_user!, :get_template_class before_action :authenticate_user!
before_action :authorize_can_read_issuable!
before_action :get_template_class
def show def show
template = @template_type.find(params[:key], project) template = @template_type.find(params[:key], project)
@ -13,9 +15,20 @@ class Projects::TemplatesController < Projects::ApplicationController
private private
# User must have:
# - `read_merge_request` to see merge request templates, or
# - `read_issue` to see issue templates
#
# Note params[:template_type] has a route constraint to limit it to
# `merge_request` or `issue`
def authorize_can_read_issuable!
action = [:read_, params[:template_type]].join
authorize_action!(action)
end
def get_template_class def get_template_class
template_types = { issue: Gitlab::Template::IssueTemplate, merge_request: Gitlab::Template::MergeRequestTemplate }.with_indifferent_access template_types = { issue: Gitlab::Template::IssueTemplate, merge_request: Gitlab::Template::MergeRequestTemplate }.with_indifferent_access
@template_type = template_types[params[:template_type]] @template_type = template_types[params[:template_type]]
render json: [], status: :not_found unless @template_type
end end
end end

View file

@ -297,7 +297,7 @@ class ProjectsController < Projects::ApplicationController
elsif @project.feature_available?(:issues, current_user) elsif @project.feature_available?(:issues, current_user)
@issues = issuables_collection.page(params[:page]) @issues = issuables_collection.page(params[:page])
@collection_type = 'Issue' @collection_type = 'Issue'
@issuable_meta_data = issuable_meta_data(@issues, @collection_type) @issuable_meta_data = issuable_meta_data(@issues, @collection_type, current_user)
end end
render :show render :show

View file

@ -5,8 +5,8 @@ class Snippets::NotesController < ApplicationController
include ToggleAwardEmoji include ToggleAwardEmoji
skip_before_action :authenticate_user!, only: [:index] skip_before_action :authenticate_user!, only: [:index]
before_action :snippet before_action :authorize_read_snippet!, only: [:show, :index]
before_action :authorize_read_snippet!, only: [:show, :index, :create] before_action :authorize_create_note!, only: [:create]
private private
@ -33,4 +33,8 @@ class Snippets::NotesController < ApplicationController
def authorize_read_snippet! def authorize_read_snippet!
return render_404 unless can?(current_user, :read_personal_snippet, snippet) return render_404 unless can?(current_user, :read_personal_snippet, snippet)
end end
def authorize_create_note!
access_denied! unless can?(current_user, :create_note, noteable)
end
end end

View file

@ -137,7 +137,7 @@ class SnippetsController < ApplicationController
def move_temporary_files def move_temporary_files
params[:files].each do |file| params[:files].each do |file|
FileMover.new(file, @snippet).execute FileMover.new(file, from_model: current_user, to_model: @snippet).execute
end end
end end
end end

View file

@ -41,7 +41,11 @@ class UploadsController < ApplicationController
when Note when Note
can?(current_user, :read_project, model.project) can?(current_user, :read_project, model.project)
when User when User
true # We validate the current user has enough (writing)
# access to itself when a secret is given.
# For instance, user avatars are readable by anyone,
# while temporary, user snippet uploads are not.
!secret? || can?(current_user, :update_user, model)
when Appearance when Appearance
true true
else else
@ -56,8 +60,13 @@ class UploadsController < ApplicationController
def authorize_create_access! def authorize_create_access!
return unless model return unless model
# for now we support only personal snippets comments authorized =
authorized = can?(current_user, :comment_personal_snippet, model) case model
when User
can?(current_user, :update_user, model)
else
can?(current_user, :comment_personal_snippet, model)
end
render_unauthorized unless authorized render_unauthorized unless authorized
end end
@ -74,6 +83,10 @@ class UploadsController < ApplicationController
User === model || Appearance === model User === model || Appearance === model
end end
def secret?
params[:secret].present?
end
def upload_model_class def upload_model_class
MODEL_CLASSES[params[:model]] || raise(UnknownUploadModelError) MODEL_CLASSES[params[:model]] || raise(UnknownUploadModelError)
end end

View file

@ -29,6 +29,7 @@
# updated_after: datetime # updated_after: datetime
# updated_before: datetime # updated_before: datetime
# attempt_group_search_optimizations: boolean # attempt_group_search_optimizations: boolean
# attempt_project_search_optimizations: boolean
# #
class IssuableFinder class IssuableFinder
prepend FinderWithCrossProjectAccess prepend FinderWithCrossProjectAccess
@ -184,7 +185,6 @@ class IssuableFinder
@project = project @project = project
end end
# rubocop: disable CodeReuse/ActiveRecord
def projects def projects
return @projects if defined?(@projects) return @projects if defined?(@projects)
@ -192,17 +192,25 @@ class IssuableFinder
projects = projects =
if current_user && params[:authorized_only].presence && !current_user_related? if current_user && params[:authorized_only].presence && !current_user_related?
current_user.authorized_projects current_user.authorized_projects(min_access_level)
elsif group elsif group
finder_options = { include_subgroups: params[:include_subgroups], only_owned: true } find_group_projects
GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute # rubocop: disable CodeReuse/Finder
else else
ProjectsFinder.new(current_user: current_user).execute # rubocop: disable CodeReuse/Finder Project.public_or_visible_to_user(current_user, min_access_level)
end end
@projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil) @projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil) # rubocop: disable CodeReuse/ActiveRecord
end
def find_group_projects
return Project.none unless group
if params[:include_subgroups]
Project.where(namespace_id: group.self_and_descendants) # rubocop: disable CodeReuse/ActiveRecord
else
group.projects
end.public_or_visible_to_user(current_user, min_access_level)
end end
# rubocop: enable CodeReuse/ActiveRecord
def search def search
params[:search].presence params[:search].presence
@ -572,4 +580,8 @@ class IssuableFinder
scope = params[:scope] scope = params[:scope]
scope == 'created_by_me' || scope == 'authored' || scope == 'assigned_to_me' scope == 'created_by_me' || scope == 'authored' || scope == 'assigned_to_me'
end end
def min_access_level
ProjectFeature.required_minimum_access_level(klass)
end
end end

View file

@ -48,9 +48,9 @@ class IssuesFinder < IssuableFinder
OR (issues.confidential = TRUE OR (issues.confidential = TRUE
AND (issues.author_id = :user_id AND (issues.author_id = :user_id
OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id) OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id)
OR issues.project_id IN(:project_ids)))', OR EXISTS (:authorizations)))',
user_id: current_user.id, user_id: current_user.id,
project_ids: current_user.authorized_projects(CONFIDENTIAL_ACCESS_LEVEL).select(:id)) authorizations: current_user.authorizations_for_projects(min_access_level: CONFIDENTIAL_ACCESS_LEVEL, related_project_column: "issues.project_id"))
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord

View file

@ -62,7 +62,7 @@ class ProjectsFinder < UnionFinder
collection = by_personal(collection) collection = by_personal(collection)
collection = by_starred(collection) collection = by_starred(collection)
collection = by_trending(collection) collection = by_trending(collection)
collection = by_visibilty_level(collection) collection = by_visibility_level(collection)
collection = by_tags(collection) collection = by_tags(collection)
collection = by_search(collection) collection = by_search(collection)
collection = by_archived(collection) collection = by_archived(collection)
@ -71,12 +71,11 @@ class ProjectsFinder < UnionFinder
collection collection
end end
# rubocop: disable CodeReuse/ActiveRecord
def collection_with_user def collection_with_user
if owned_projects? if owned_projects?
current_user.owned_projects current_user.owned_projects
elsif min_access_level? elsif min_access_level?
current_user.authorized_projects.where('project_authorizations.access_level >= ?', params[:min_access_level]) current_user.authorized_projects(params[:min_access_level])
else else
if private_only? if private_only?
current_user.authorized_projects current_user.authorized_projects
@ -85,7 +84,6 @@ class ProjectsFinder < UnionFinder
end end
end end
end end
# rubocop: enable CodeReuse/ActiveRecord
# Builds a collection for an anonymous user. # Builds a collection for an anonymous user.
def collection_without_user def collection_without_user
@ -131,7 +129,7 @@ class ProjectsFinder < UnionFinder
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def by_visibilty_level(items) def by_visibility_level(items)
params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord

View file

@ -4,6 +4,8 @@ module Types
class LabelType < BaseObject class LabelType < BaseObject
graphql_name 'Label' graphql_name 'Label'
authorize :read_label
field :description, GraphQL::STRING_TYPE, null: true field :description, GraphQL::STRING_TYPE, null: true
field :title, GraphQL::STRING_TYPE, null: false field :title, GraphQL::STRING_TYPE, null: false
field :color, GraphQL::STRING_TYPE, null: false field :color, GraphQL::STRING_TYPE, null: false

View file

@ -4,6 +4,8 @@ module Types
class MetadataType < ::Types::BaseObject class MetadataType < ::Types::BaseObject
graphql_name 'Metadata' graphql_name 'Metadata'
authorize :read_instance_metadata
field :version, GraphQL::STRING_TYPE, null: false field :version, GraphQL::STRING_TYPE, null: false
field :revision, GraphQL::STRING_TYPE, null: false field :revision, GraphQL::STRING_TYPE, null: false
end end

View file

@ -12,10 +12,7 @@ module Types
field :metadata, Types::MetadataType, field :metadata, Types::MetadataType,
null: true, null: true,
resolver: Resolvers::MetadataResolver, resolver: Resolvers::MetadataResolver,
description: 'Metadata about GitLab' do |*args| description: 'Metadata about GitLab'
authorize :read_instance_metadata
end
field :echo, GraphQL::STRING_TYPE, null: false, function: Functions::Echo.new field :echo, GraphQL::STRING_TYPE, null: false, function: Functions::Echo.new
end end

View file

@ -277,7 +277,7 @@ module IssuablesHelper
initialTaskStatus: issuable.task_status initialTaskStatus: issuable.task_status
} }
data[:hasClosingMergeRequest] = issuable.merge_requests_count != 0 if issuable.is_a?(Issue) data[:hasClosingMergeRequest] = issuable.merge_requests_count(current_user) != 0 if issuable.is_a?(Issue)
if parent.is_a?(Group) if parent.is_a?(Group)
data[:groupPath] = parent.path data[:groupPath] = parent.path

View file

@ -1,6 +1,16 @@
# frozen_string_literal: true # frozen_string_literal: true
module SnippetsHelper module SnippetsHelper
def snippets_upload_path(snippet, user)
return unless user
if snippet&.persisted?
upload_path('personal_snippet', id: snippet.id)
else
upload_path('user', id: user.id)
end
end
def reliable_snippet_path(snippet, opts = nil) def reliable_snippet_path(snippet, opts = nil)
if snippet.project_id? if snippet.project_id?
project_snippet_path(snippet.project, snippet, opts) project_snippet_path(snippet.project, snippet, opts)

View file

@ -29,7 +29,11 @@ module Issuable
# This object is used to gather issuable meta data for displaying # This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests # upvotes, downvotes, notes and closing merge requests count for issues and merge requests
# lists avoiding n+1 queries and improving performance. # lists avoiding n+1 queries and improving performance.
IssuableMeta = Struct.new(:upvotes, :downvotes, :user_notes_count, :merge_requests_count) IssuableMeta = Struct.new(:upvotes, :downvotes, :user_notes_count, :mrs_count) do
def merge_requests_count(user = nil)
mrs_count
end
end
included do included do
cache_markdown_field :title, pipeline: :single_line cache_markdown_field :title, pipeline: :single_line

View file

@ -270,8 +270,8 @@ class Issue < ApplicationRecord
end end
# rubocop: enable CodeReuse/ServiceClass # rubocop: enable CodeReuse/ServiceClass
def merge_requests_count def merge_requests_count(user = nil)
merge_requests_closing_issues.count ::MergeRequestsClosingIssues.count_for_issue(self.id, user)
end end
private private

View file

@ -446,10 +446,10 @@ class Member < ApplicationRecord
end end
def higher_access_level_than_group def higher_access_level_than_group
if highest_group_member && highest_group_member.access_level >= access_level if highest_group_member && highest_group_member.access_level > access_level
error_parameters = { access: highest_group_member.human_access, group_name: highest_group_member.group.name } error_parameters = { access: highest_group_member.human_access, group_name: highest_group_member.group.name }
errors.add(:access_level, s_("should be higher than %{access} inherited membership from group %{group_name}") % error_parameters) errors.add(:access_level, s_("should be greater than or equal to %{access} inherited membership from group %{group_name}") % error_parameters)
end end
end end
end end

View file

@ -7,11 +7,38 @@ class MergeRequestsClosingIssues < ApplicationRecord
validates :merge_request_id, uniqueness: { scope: :issue_id }, presence: true validates :merge_request_id, uniqueness: { scope: :issue_id }, presence: true
validates :issue_id, presence: true validates :issue_id, presence: true
scope :with_issues, ->(ids) { where(issue_id: ids) }
scope :with_merge_requests_enabled, -> do
joins(:merge_request)
.joins('INNER JOIN project_features ON merge_requests.target_project_id = project_features.project_id')
.where('project_features.merge_requests_access_level >= :access', access: ProjectFeature::ENABLED)
end
scope :accessible_by, ->(user) do
joins(:merge_request)
.joins('INNER JOIN project_features ON merge_requests.target_project_id = project_features.project_id')
.where('project_features.merge_requests_access_level >= :access OR EXISTS(:authorizations)',
access: ProjectFeature::ENABLED,
authorizations: user.authorizations_for_projects(min_access_level: Gitlab::Access::REPORTER, related_project_column: "merge_requests.target_project_id")
)
end
class << self class << self
def count_for_collection(ids) def count_for_collection(ids, current_user)
group(:issue_id) closing_merge_requests(ids, current_user).group(:issue_id).pluck('issue_id', 'COUNT(*) as count')
.where(issue_id: ids) end
.pluck('issue_id', 'COUNT(*) as count')
def count_for_issue(id, current_user)
closing_merge_requests(id, current_user).count
end
private
def closing_merge_requests(ids, current_user)
return with_issues(ids) if current_user&.admin?
return with_issues(ids).with_merge_requests_enabled if current_user.blank?
with_issues(ids).accessible_by(current_user)
end end
end end
end end

View file

@ -461,10 +461,12 @@ class Project < ApplicationRecord
# Returns a collection of projects that is either public or visible to the # Returns a collection of projects that is either public or visible to the
# logged in user. # logged in user.
def self.public_or_visible_to_user(user = nil) def self.public_or_visible_to_user(user = nil, min_access_level = nil)
min_access_level = nil if user&.admin?
if user if user
where('EXISTS (?) OR projects.visibility_level IN (?)', where('EXISTS (?) OR projects.visibility_level IN (?)',
user.authorizations_for_projects, user.authorizations_for_projects(min_access_level: min_access_level),
Gitlab::VisibilityLevel.levels_for_user(user)) Gitlab::VisibilityLevel.levels_for_user(user))
else else
public_to_user public_to_user
@ -474,30 +476,32 @@ class Project < ApplicationRecord
# project features may be "disabled", "internal", "enabled" or "public". If "internal", # project features may be "disabled", "internal", "enabled" or "public". If "internal",
# they are only available to team members. This scope returns projects where # they are only available to team members. This scope returns projects where
# the feature is either public, enabled, or internal with permission for the user. # the feature is either public, enabled, or internal with permission for the user.
# Note: this scope doesn't enforce that the user has access to the projects, it just checks
# that the user has access to the feature. It's important to use this scope with others
# that checks project authorizations first.
# #
# This method uses an optimised version of `with_feature_access_level` for # This method uses an optimised version of `with_feature_access_level` for
# logged in users to more efficiently get private projects with the given # logged in users to more efficiently get private projects with the given
# feature. # feature.
def self.with_feature_available_for_user(feature, user) def self.with_feature_available_for_user(feature, user)
visible = [ProjectFeature::ENABLED, ProjectFeature::PUBLIC] visible = [ProjectFeature::ENABLED, ProjectFeature::PUBLIC]
min_access_level = ProjectFeature.required_minimum_access_level(feature)
if user&.admin? if user&.admin?
with_feature_enabled(feature) with_feature_enabled(feature)
elsif user elsif user
min_access_level = ProjectFeature.required_minimum_access_level(feature)
column = ProjectFeature.quoted_access_level_column(feature) column = ProjectFeature.quoted_access_level_column(feature)
with_project_feature with_project_feature
.where( .where("#{column} IS NULL OR #{column} IN (:public_visible) OR (#{column} = :private_visible AND EXISTS (:authorizations))",
"(projects.visibility_level > :private AND (#{column} IS NULL OR #{column} >= (:public_visible) OR (#{column} = :private_visible AND EXISTS(:authorizations))))"\
" OR (projects.visibility_level = :private AND (#{column} IS NULL OR #{column} >= :private_visible) AND EXISTS(:authorizations))",
{ {
private: Gitlab::VisibilityLevel::PRIVATE, public_visible: visible,
public_visible: ProjectFeature::ENABLED,
private_visible: ProjectFeature::PRIVATE, private_visible: ProjectFeature::PRIVATE,
authorizations: user.authorizations_for_projects(min_access_level: min_access_level) authorizations: user.authorizations_for_projects(min_access_level: min_access_level)
}) })
else else
# This has to be added to include features whose value is nil in the db
visible << nil
with_feature_access_level(feature, visible) with_feature_access_level(feature, visible)
end end
end end

View file

@ -761,11 +761,15 @@ class User < ApplicationRecord
# Typically used in conjunction with projects table to get projects # Typically used in conjunction with projects table to get projects
# a user has been given access to. # a user has been given access to.
# The param `related_project_column` is the column to compare to the
# project_authorizations. By default is projects.id
# #
# Example use: # Example use:
# `Project.where('EXISTS(?)', user.authorizations_for_projects)` # `Project.where('EXISTS(?)', user.authorizations_for_projects)`
def authorizations_for_projects(min_access_level: nil) def authorizations_for_projects(min_access_level: nil, related_project_column: 'projects.id')
authorizations = project_authorizations.select(1).where('project_authorizations.project_id = projects.id') authorizations = project_authorizations
.select(1)
.where("project_authorizations.project_id = #{related_project_column}")
return authorizations unless min_access_level.present? return authorizations unless min_access_level.present?

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
class RepositoryPolicy < BasePolicy
delegate { @subject.project }
end

View file

@ -4,7 +4,6 @@ module Ci
class BuildRunnerPresenter < SimpleDelegator class BuildRunnerPresenter < SimpleDelegator
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
DEFAULT_GIT_DEPTH_MERGE_REQUEST = 10
RUNNER_REMOTE_TAG_PREFIX = 'refs/tags/'.freeze RUNNER_REMOTE_TAG_PREFIX = 'refs/tags/'.freeze
RUNNER_REMOTE_BRANCH_PREFIX = 'refs/remotes/origin/'.freeze RUNNER_REMOTE_BRANCH_PREFIX = 'refs/remotes/origin/'.freeze
@ -28,7 +27,6 @@ module Ci
def git_depth def git_depth
strong_memoize(:git_depth) do strong_memoize(:git_depth) do
git_depth = variables&.find { |variable| variable[:key] == 'GIT_DEPTH' }&.dig(:value) git_depth = variables&.find { |variable| variable[:key] == 'GIT_DEPTH' }&.dig(:value)
git_depth ||= DEFAULT_GIT_DEPTH_MERGE_REQUEST if merge_request_ref?
git_depth.to_i git_depth.to_i
end end
end end
@ -39,12 +37,13 @@ module Ci
if git_depth > 0 if git_depth > 0
specs << refspec_for_branch(ref) if branch? || legacy_detached_merge_request_pipeline? specs << refspec_for_branch(ref) if branch? || legacy_detached_merge_request_pipeline?
specs << refspec_for_tag(ref) if tag? specs << refspec_for_tag(ref) if tag?
specs << refspec_for_merge_request_ref if merge_request_ref?
else else
specs << refspec_for_branch specs << refspec_for_branch
specs << refspec_for_tag specs << refspec_for_tag
end end
specs << refspec_for_merge_request_ref if merge_request_ref?
specs specs
end end

View file

@ -24,7 +24,7 @@ module Lfs
def new_file(file_path, file_content, encoding: nil) def new_file(file_path, file_content, encoding: nil)
if project.lfs_enabled? && lfs_file?(file_path) if project.lfs_enabled? && lfs_file?(file_path)
file_content = Base64.decode64(file_content) if encoding == 'base64' file_content = parse_file_content(file_content, encoding: encoding)
lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content) lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content)
lfs_object = create_lfs_object!(lfs_pointer_file, file_content) lfs_object = create_lfs_object!(lfs_pointer_file, file_content)
@ -66,5 +66,12 @@ module Lfs
def link_lfs_object!(lfs_object) def link_lfs_object!(lfs_object)
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
def parse_file_content(file_content, encoding: nil)
return file_content.read if file_content.respond_to?(:read)
return Base64.decode64(file_content) if encoding == 'base64'
file_content
end
end end
end end

View file

@ -9,7 +9,7 @@ module Projects
end end
def execute def execute
Projects::HousekeepingService.new(@project, :gc).execute do Projects::HousekeepingService.new(@project).execute do
repository.delete_all_refs_except(RESERVED_REF_PREFIXES) repository.delete_all_refs_except(RESERVED_REF_PREFIXES)
end end
rescue Projects::HousekeepingService::LeaseTaken => e rescue Projects::HousekeepingService::LeaseTaken => e

View file

@ -24,7 +24,7 @@ module Projects
def propagate_projects_with_template def propagate_projects_with_template
loop do loop do
batch = project_ids_batch batch = Project.uncached { project_ids_batch }
bulk_create_from_template(batch) unless batch.empty? bulk_create_from_template(batch) unless batch.empty?

View file

@ -1,22 +1,29 @@
# frozen_string_literal: true # frozen_string_literal: true
class FileMover class FileMover
attr_reader :secret, :file_name, :model, :update_field include Gitlab::Utils::StrongMemoize
def initialize(file_path, model, update_field = :description) attr_reader :secret, :file_name, :from_model, :to_model, :update_field
def initialize(file_path, update_field = :description, from_model:, to_model:)
@secret = File.split(File.dirname(file_path)).last @secret = File.split(File.dirname(file_path)).last
@file_name = File.basename(file_path) @file_name = File.basename(file_path)
@model = model @from_model = from_model
@to_model = to_model
@update_field = update_field @update_field = update_field
end end
def execute def execute
temp_file_uploader.retrieve_from_store!(file_name)
return unless valid? return unless valid?
uploader.retrieve_from_store!(file_name)
move move
if update_markdown if update_markdown
uploader.record_upload update_upload_model
uploader.schedule_background_upload uploader.schedule_background_upload
end end
end end
@ -24,52 +31,77 @@ class FileMover
private private
def valid? def valid?
if temp_file_uploader.file_storage?
Pathname.new(temp_file_path).realpath.to_path.start_with?( Pathname.new(temp_file_path).realpath.to_path.start_with?(
(Pathname(temp_file_uploader.root) + temp_file_uploader.base_dir).to_path (Pathname(temp_file_uploader.root) + temp_file_uploader.base_dir).to_path
) )
else
temp_file_uploader.exists?
end
end end
def move def move
if temp_file_uploader.file_storage?
FileUtils.mkdir_p(File.dirname(file_path)) FileUtils.mkdir_p(File.dirname(file_path))
FileUtils.move(temp_file_path, file_path) FileUtils.move(temp_file_path, file_path)
else
uploader.copy_file(temp_file_uploader.file)
temp_file_uploader.upload.destroy!
end
end end
def update_markdown def update_markdown
updated_text = model.read_attribute(update_field) updated_text = to_model.read_attribute(update_field)
.gsub(temp_file_uploader.markdown_link, uploader.markdown_link) .gsub(temp_file_uploader.markdown_link, uploader.markdown_link)
model.update_attribute(update_field, updated_text) to_model.update_attribute(update_field, updated_text)
rescue rescue
revert revert
false false
end end
def update_upload_model
return unless upload = temp_file_uploader.upload
return if upload.destroyed?
upload.update!(model: to_model)
end
def temp_file_path def temp_file_path
return @temp_file_path if @temp_file_path strong_memoize(:temp_file_path) do
temp_file_uploader.file.path
temp_file_uploader.retrieve_from_store!(file_name) end
@temp_file_path = temp_file_uploader.file.path
end end
def file_path def file_path
return @file_path if @file_path strong_memoize(:file_path) do
uploader.file.path
uploader.retrieve_from_store!(file_name) end
@file_path = uploader.file.path
end end
def uploader def uploader
@uploader ||= PersonalFileUploader.new(model, secret: secret) @uploader ||=
begin
uploader = PersonalFileUploader.new(to_model, secret: secret)
# Enforcing a REMOTE object storage given FileUploader#retrieve_from_store! won't do it
# (there's no upload at the target yet).
if uploader.class.object_store_enabled?
uploader.object_store = ::ObjectStorage::Store::REMOTE
end
uploader
end
end end
def temp_file_uploader def temp_file_uploader
@temp_file_uploader ||= PersonalFileUploader.new(nil, secret: secret) @temp_file_uploader ||= PersonalFileUploader.new(from_model, secret: secret)
end end
def revert def revert
Rails.logger.warn("Markdown not updated, file move reverted for #{model}") Rails.logger.warn("Markdown not updated, file move reverted for #{to_model}")
if temp_file_uploader.file_storage?
FileUtils.move(file_path, temp_file_path) FileUtils.move(file_path, temp_file_path)
end end
end
end end

View file

@ -12,7 +12,7 @@
# end # end
# #
class ColorValidator < ActiveModel::EachValidator class ColorValidator < ActiveModel::EachValidator
PATTERN = /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/.freeze PATTERN = /\A\#(?:[0-9A-Fa-f]{3}){1,2}\Z/.freeze
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
unless value =~ PATTERN unless value =~ PATTERN

View file

@ -1,9 +1,10 @@
- header_title _("Snippets"), snippets_path - header_title _("Snippets"), snippets_path
- snippets_upload_path = snippets_upload_path(@snippet, current_user)
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
- if @snippet && current_user - if snippets_upload_path
-# haml-lint:disable InlineJavaScript -# haml-lint:disable InlineJavaScript
:javascript :javascript
window.uploads_path = "#{upload_path('personal_snippet', id: @snippet.id)}"; window.uploads_path = "#{snippets_upload_path}";
= render template: "layouts/application" = render template: "layouts/application"

View file

@ -77,7 +77,7 @@
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago') = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
#js-related-merge-requests{ data: { endpoint: expose_url(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: @issue.iid)), project_namespace: @project.namespace.path, project_path: @project.path } } #js-related-merge-requests{ data: { endpoint: expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: @issue.iid)), project_namespace: @project.namespace.path, project_path: @project.path } }
- if can?(current_user, :download_code, @project) - if can?(current_user, :download_code, @project)
#related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } } #related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } }

View file

@ -2,7 +2,7 @@
- issue_votes = @issuable_meta_data[issuable.id] - issue_votes = @issuable_meta_data[issuable.id]
- upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes - upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes
- issuable_url = @collection_type == "Issue" ? issue_path(issuable, anchor: 'notes') : merge_request_path(issuable, anchor: 'notes') - issuable_url = @collection_type == "Issue" ? issue_path(issuable, anchor: 'notes') : merge_request_path(issuable, anchor: 'notes')
- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count - issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count(current_user)
- if issuable_mr > 0 - if issuable_mr > 0
%li.issuable-mr.d-none.d-sm-block.has-tooltip{ title: _('Related merge requests') } %li.issuable-mr.d-none.d-sm-block.has-tooltip{ title: _('Related merge requests') }

View file

@ -41,7 +41,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# #
# Templates # Templates
# #
get '/templates/:template_type/:key' => 'templates#show', as: :template, constraints: { key: %r{[^/]+} } get '/templates/:template_type/:key' => 'templates#show',
as: :template,
defaults: { format: 'json' },
constraints: { key: %r{[^/]+}, template_type: /issue|merge_request/, format: 'json' }
resource :avatar, only: [:show, :destroy] resource :avatar, only: [:show, :destroy]
resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do

View file

@ -7,7 +7,7 @@ scope path: :uploads do
# show uploads for models, snippets (notes) available for now # show uploads for models, snippets (notes) available for now
get '-/system/:model/:id/:secret/:filename', get '-/system/:model/:id/:secret/:filename',
to: 'uploads#show', to: 'uploads#show',
constraints: { model: /personal_snippet/, id: /\d+/, filename: %r{[^/]+} } constraints: { model: /personal_snippet|user/, id: /\d+/, filename: %r{[^/]+} }
# show temporary uploads # show temporary uploads
get '-/system/temp/:secret/:filename', get '-/system/temp/:secret/:filename',
@ -28,7 +28,7 @@ scope path: :uploads do
# create uploads for models, snippets (notes) available for now # create uploads for models, snippets (notes) available for now
post ':model', post ':model',
to: 'uploads#create', to: 'uploads#create',
constraints: { model: /personal_snippet/, id: /\d+/ }, constraints: { model: /personal_snippet|user/, id: /\d+/ },
as: 'upload' as: 'upload'
end end

View file

@ -493,9 +493,9 @@ module API
expose :state, :created_at, :updated_at expose :state, :created_at, :updated_at
# Avoids an N+1 query when metadata is included # Avoids an N+1 query when metadata is included
def issuable_metadata(subject, options, method) def issuable_metadata(subject, options, method, args = nil)
cached_subject = options.dig(:issuable_metadata, subject.id) cached_subject = options.dig(:issuable_metadata, subject.id)
(cached_subject || subject).public_send(method) # rubocop: disable GitlabSecurity/PublicSend (cached_subject || subject).public_send(method, *args) # rubocop: disable GitlabSecurity/PublicSend
end end
end end
@ -554,7 +554,7 @@ module API
end end
expose(:user_notes_count) { |issue, options| issuable_metadata(issue, options, :user_notes_count) } expose(:user_notes_count) { |issue, options| issuable_metadata(issue, options, :user_notes_count) }
expose(:merge_requests_count) { |issue, options| issuable_metadata(issue, options, :merge_requests_count) } expose(:merge_requests_count) { |issue, options| issuable_metadata(issue, options, :merge_requests_count, options[:current_user]) }
expose(:upvotes) { |issue, options| issuable_metadata(issue, options, :upvotes) } expose(:upvotes) { |issue, options| issuable_metadata(issue, options, :upvotes) }
expose(:downvotes) { |issue, options| issuable_metadata(issue, options, :downvotes) } expose(:downvotes) { |issue, options| issuable_metadata(issue, options, :downvotes) }
expose :due_date expose :due_date
@ -731,7 +731,9 @@ module API
merge_request.metrics&.pipeline merge_request.metrics&.pipeline
end end
expose :head_pipeline, using: 'API::Entities::Pipeline' expose :head_pipeline, using: 'API::Entities::Pipeline', if: -> (_, options) do
Ability.allowed?(options[:current_user], :read_pipeline, options[:project])
end
expose :diff_refs, using: Entities::DiffRefs expose :diff_refs, using: Entities::DiffRefs

View file

@ -13,6 +13,10 @@ module API
available?(:merge_requests, project, options[:current_user]) available?(:merge_requests, project, options[:current_user])
end end
def expose_path(path)
Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, path)
end
def expose_url(path) def expose_url(path)
url_options = Gitlab::Application.routes.default_url_options url_options = Gitlab::Application.routes.default_url_options
protocol, host, port, script_name = url_options.values_at(:protocol, :host, :port, :script_name) protocol, host, port, script_name = url_options.values_at(:protocol, :host, :port, :script_name)

View file

@ -93,7 +93,7 @@ module API
options = { options = {
with: Entities::IssueBasic, with: Entities::IssueBasic,
current_user: current_user, current_user: current_user,
issuable_metadata: issuable_meta_data(issues, 'Issue') issuable_metadata: issuable_meta_data(issues, 'Issue', current_user)
} }
present issues, options present issues, options
@ -120,7 +120,7 @@ module API
options = { options = {
with: Entities::IssueBasic, with: Entities::IssueBasic,
current_user: current_user, current_user: current_user,
issuable_metadata: issuable_meta_data(issues, 'Issue') issuable_metadata: issuable_meta_data(issues, 'Issue', current_user)
} }
present issues, options present issues, options
@ -150,7 +150,7 @@ module API
with: Entities::IssueBasic, with: Entities::IssueBasic,
current_user: current_user, current_user: current_user,
project: user_project, project: user_project,
issuable_metadata: issuable_meta_data(issues, 'Issue') issuable_metadata: issuable_meta_data(issues, 'Issue', current_user)
} }
present issues, options present issues, options

View file

@ -71,7 +71,7 @@ module API
if params[:view] == 'simple' if params[:view] == 'simple'
options[:with] = Entities::MergeRequestSimple options[:with] = Entities::MergeRequestSimple
else else
options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest') options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest', current_user)
end end
options options

View file

@ -65,7 +65,7 @@ module API
next unless collection next unless collection
targets = collection.map(&:target) targets = collection.map(&:target)
options[type] = { issuable_metadata: issuable_meta_data(targets, type) } options[type] = { issuable_metadata: issuable_meta_data(targets, type, current_user) }
end end
end end
end end

View file

@ -100,7 +100,7 @@ module Banzai
end end
def relative_file_path(uri) def relative_file_path(uri)
path = Addressable::URI.unescape(uri.path) path = Addressable::URI.unescape(uri.path).delete("\0")
request_path = Addressable::URI.unescape(context[:requested_path]) request_path = Addressable::URI.unescape(context[:requested_path])
nested_path = build_relative_path(path, request_path) nested_path = build_relative_path(path, request_path)
file_exists?(nested_path) ? nested_path : path file_exists?(nested_path) ? nested_path : path

View file

@ -15,6 +15,9 @@ module Gitlab
@global = Entry::Global.new(@config) @global = Entry::Global.new(@config)
@global.compose! @global.compose!
rescue Gitlab::Config::Loader::Yaml::DataTooLargeError => e
Gitlab::Sentry.track_exception(e, extra: { user: user.inspect, project: project.inspect })
raise Config::ConfigError, e.message
rescue Gitlab::Config::Loader::FormatError, rescue Gitlab::Config::Loader::FormatError,
Extendable::ExtensionError, Extendable::ExtensionError,
External::Processor::IncludeError => e External::Processor::IncludeError => e

View file

@ -4,6 +4,13 @@ module Gitlab
module Config module Config
module Loader module Loader
class Yaml class Yaml
DataTooLargeError = Class.new(Loader::FormatError)
include Gitlab::Utils::StrongMemoize
MAX_YAML_SIZE = 1.megabyte
MAX_YAML_DEPTH = 100
def initialize(config) def initialize(config)
@config = YAML.safe_load(config, [Symbol], [], true) @config = YAML.safe_load(config, [Symbol], [], true)
rescue Psych::Exception => e rescue Psych::Exception => e
@ -11,16 +18,35 @@ module Gitlab
end end
def valid? def valid?
@config.is_a?(Hash) hash? && !too_big?
end end
def load! def load!
unless valid? raise DataTooLargeError, 'The parsed YAML is too big' if too_big?
raise Loader::FormatError, 'Invalid configuration format' raise Loader::FormatError, 'Invalid configuration format' unless hash?
end
@config.deep_symbolize_keys @config.deep_symbolize_keys
end end
private
def hash?
@config.is_a?(Hash)
end
def too_big?
return false unless Feature.enabled?(:ci_yaml_limit_size, default_enabled: true)
!deep_size.valid?
end
def deep_size
strong_memoize(:deep_size) do
Gitlab::Utils::DeepSize.new(@config,
max_size: MAX_YAML_SIZE,
max_depth: MAX_YAML_DEPTH)
end
end
end end
end end
end end

View file

@ -19,7 +19,7 @@ module Gitlab
def hook_attrs(pipeline) def hook_attrs(pipeline)
{ {
id: pipeline.id, id: pipeline.id,
ref: pipeline.ref, ref: pipeline.source_ref,
tag: pipeline.tag, tag: pipeline.tag,
sha: pipeline.sha, sha: pipeline.sha,
before_sha: pipeline.before_sha, before_sha: pipeline.before_sha,

View file

@ -905,6 +905,12 @@ module Gitlab
end end
end end
def remove_foreign_key_if_exists(*args)
if foreign_key_exists?(*args)
remove_foreign_key(*args)
end
end
def remove_foreign_key_without_error(*args) def remove_foreign_key_without_error(*args)
remove_foreign_key(*args) remove_foreign_key(*args)
rescue ArgumentError rescue ArgumentError

View file

@ -303,6 +303,11 @@ module Gitlab
(size.to_f / 1024).round(2) (size.to_f / 1024).round(2)
end end
# Return git object directory size in bytes
def object_directory_size
gitaly_repository_client.get_object_directory_size.to_f * 1024
end
# Build an array of commits. # Build an array of commits.
# #
# Usage. # Usage.

View file

@ -43,6 +43,8 @@ module Gitlab
ordered_entries.concat(tree_entries_from_rugged(repository, sha, entry.path, true)) ordered_entries.concat(tree_entries_from_rugged(repository, sha, entry.path, true))
end end
end end
ordered_entries
end end
def rugged_populate_flat_path(repository, sha, path, entries) def rugged_populate_flat_path(repository, sha, path, entries)

View file

@ -47,6 +47,13 @@ module Gitlab
response.size response.size
end end
def get_object_directory_size
request = Gitaly::GetObjectDirectorySizeRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :repository_service, :get_object_directory_size, request, timeout: GitalyClient.medium_timeout)
response.size
end
def apply_gitattributes(revision) def apply_gitattributes(revision)
request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: encode_binary(revision)) request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: encode_binary(revision))
GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request, timeout: GitalyClient.fast_timeout) GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request, timeout: GitalyClient.fast_timeout)

View file

@ -39,6 +39,8 @@ module Gitlab
type = node_type_for_basic_connection(type) type = node_type_for_basic_connection(type)
end end
type = type.unwrap if type.kind.non_null?
Array.wrap(type.metadata[:authorize]) Array.wrap(type.metadata[:authorize])
end end

View file

@ -2,6 +2,8 @@
module Gitlab module Gitlab
class GroupSearchResults < SearchResults class GroupSearchResults < SearchResults
attr_reader :group
def initialize(current_user, limit_projects, group, query, default_project_filter: false, per_page: 20) def initialize(current_user, limit_projects, group, query, default_project_filter: false, per_page: 20)
super(current_user, limit_projects, query, default_project_filter: default_project_filter, per_page: per_page) super(current_user, limit_projects, query, default_project_filter: default_project_filter, per_page: per_page)
@ -26,5 +28,9 @@ module Gitlab
.where(id: groups.select('members.user_id')) .where(id: groups.select('members.user_id'))
end end
# rubocop:enable CodeReuse/ActiveRecord # rubocop:enable CodeReuse/ActiveRecord
def issuable_params
super.merge(group_id: group.id)
end
end end
end end

View file

@ -2,7 +2,7 @@
module Gitlab module Gitlab
module IssuableMetadata module IssuableMetadata
def issuable_meta_data(issuable_collection, collection_type) def issuable_meta_data(issuable_collection, collection_type, user = nil)
# ActiveRecord uses Object#extend for null relations. # ActiveRecord uses Object#extend for null relations.
if !(issuable_collection.singleton_class < ActiveRecord::NullRelation) && if !(issuable_collection.singleton_class < ActiveRecord::NullRelation) &&
issuable_collection.respond_to?(:limit_value) && issuable_collection.respond_to?(:limit_value) &&
@ -23,7 +23,7 @@ module Gitlab
issuable_votes_count = ::AwardEmoji.votes_for_collection(issuable_ids, collection_type) issuable_votes_count = ::AwardEmoji.votes_for_collection(issuable_ids, collection_type)
issuable_merge_requests_count = issuable_merge_requests_count =
if collection_type == 'Issue' if collection_type == 'Issue'
::MergeRequestsClosingIssues.count_for_collection(issuable_ids) ::MergeRequestsClosingIssues.count_for_collection(issuable_ids, user)
else else
[] []
end end

View file

@ -151,5 +151,9 @@ module Gitlab
def repository_wiki_ref def repository_wiki_ref
@repository_wiki_ref ||= repository_ref || project.wiki.default_branch @repository_wiki_ref ||= repository_ref || project.wiki.default_branch
end end
def issuable_params
super.merge(project_id: project.id)
end
end end
end end

View file

@ -2,6 +2,8 @@
module Gitlab module Gitlab
class SearchResults class SearchResults
COUNT_LIMIT = 1001
attr_reader :current_user, :query, :per_page attr_reader :current_user, :query, :per_page
# Limit search results by passed projects # Limit search results by passed projects
@ -25,29 +27,26 @@ module Gitlab
def objects(scope, page = nil, without_count = true) def objects(scope, page = nil, without_count = true)
collection = case scope collection = case scope
when 'projects' when 'projects'
projects.page(page).per(per_page) projects
when 'issues' when 'issues'
issues.page(page).per(per_page) issues
when 'merge_requests' when 'merge_requests'
merge_requests.page(page).per(per_page) merge_requests
when 'milestones' when 'milestones'
milestones.page(page).per(per_page) milestones
when 'users' when 'users'
users.page(page).per(per_page) users
else else
Kaminari.paginate_array([]).page(page).per(per_page) Kaminari.paginate_array([])
end end.page(page).per(per_page)
without_count ? collection.without_count : collection without_count ? collection.without_count : collection
end end
# rubocop: disable CodeReuse/ActiveRecord
def limited_projects_count def limited_projects_count
@limited_projects_count ||= projects.limit(count_limit).count @limited_projects_count ||= limited_count(projects)
end end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def limited_issues_count def limited_issues_count
return @limited_issues_count if @limited_issues_count return @limited_issues_count if @limited_issues_count
@ -56,35 +55,28 @@ module Gitlab
# and confidential issues user has access to, is too complex. # and confidential issues user has access to, is too complex.
# It's faster to try to fetch all public issues first, then only # It's faster to try to fetch all public issues first, then only
# if necessary try to fetch all issues. # if necessary try to fetch all issues.
sum = issues(public_only: true).limit(count_limit).count sum = limited_count(issues(public_only: true))
@limited_issues_count = sum < count_limit ? issues.limit(count_limit).count : sum @limited_issues_count = sum < count_limit ? limited_count(issues) : sum
end end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def limited_merge_requests_count def limited_merge_requests_count
@limited_merge_requests_count ||= merge_requests.limit(count_limit).count @limited_merge_requests_count ||= limited_count(merge_requests)
end end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def limited_milestones_count def limited_milestones_count
@limited_milestones_count ||= milestones.limit(count_limit).count @limited_milestones_count ||= limited_count(milestones)
end end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop:disable CodeReuse/ActiveRecord
def limited_users_count def limited_users_count
@limited_users_count ||= users.limit(count_limit).count @limited_users_count ||= limited_count(users)
end end
# rubocop:enable CodeReuse/ActiveRecord
def single_commit_result? def single_commit_result?
false false
end end
def count_limit def count_limit
1001 COUNT_LIMIT
end end
def users def users
@ -99,24 +91,16 @@ module Gitlab
limit_projects.search(query) limit_projects.search(query)
end end
# rubocop: disable CodeReuse/ActiveRecord
def issues(finder_params = {}) def issues(finder_params = {})
issues = IssuesFinder.new(current_user, finder_params).execute issues = IssuesFinder.new(current_user, issuable_params.merge(finder_params)).execute
unless default_project_filter unless default_project_filter
issues = issues.where(project_id: project_ids_relation) issues = issues.where(project_id: project_ids_relation) # rubocop: disable CodeReuse/ActiveRecord
end end
issues = issues
if query =~ /#(\d+)\z/
issues.where(iid: $1)
else
issues.full_search(query)
end end
issues.reorder('updated_at DESC')
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def milestones def milestones
milestones = Milestone.search(query) milestones = Milestone.search(query)
@ -127,24 +111,16 @@ module Gitlab
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def merge_requests def merge_requests
merge_requests = MergeRequestsFinder.new(current_user).execute merge_requests = MergeRequestsFinder.new(current_user, issuable_params).execute
unless default_project_filter unless default_project_filter
merge_requests = merge_requests.in_projects(project_ids_relation) merge_requests = merge_requests.in_projects(project_ids_relation)
end end
merge_requests = merge_requests
if query =~ /[#!](\d+)\z/
merge_requests.where(iid: $1)
else
merge_requests.full_search(query)
end end
merge_requests.reorder('updated_at DESC')
end
# rubocop: enable CodeReuse/ActiveRecord
def default_scope def default_scope
'projects' 'projects'
end end
@ -174,5 +150,23 @@ module Gitlab
limit_projects.select(:id).reorder(nil) limit_projects.select(:id).reorder(nil)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def issuable_params
{}.tap do |params|
params[:sort] = 'updated_desc'
if query =~ /#(\d+)\z/
params[:iids] = $1
else
params[:search] = query
end
end
end
# rubocop: disable CodeReuse/ActiveRecord
def limited_count(relation)
relation.reorder(nil).limit(count_limit).size
end
# rubocop: enable CodeReuse/ActiveRecord
end end
end end

View file

@ -0,0 +1,79 @@
# frozen_string_literal: true
require 'objspace'
module Gitlab
module Utils
class DeepSize
Error = Class.new(StandardError)
TooMuchDataError = Class.new(Error)
DEFAULT_MAX_SIZE = 1.megabyte
DEFAULT_MAX_DEPTH = 100
def initialize(root, max_size: DEFAULT_MAX_SIZE, max_depth: DEFAULT_MAX_DEPTH)
@root = root
@max_size = max_size
@max_depth = max_depth
@size = 0
@depth = 0
evaluate
end
def valid?
!too_big? && !too_deep?
end
private
def evaluate
add_object(@root)
rescue Error
# NOOP
end
def too_big?
@size > @max_size
end
def too_deep?
@depth > @max_depth
end
def add_object(object)
@size += ObjectSpace.memsize_of(object)
raise TooMuchDataError if @size > @max_size
add_array(object) if object.is_a?(Array)
add_hash(object) if object.is_a?(Hash)
end
def add_array(object)
with_nesting do
object.each do |n|
add_object(n)
end
end
end
def add_hash(object)
with_nesting do
object.each do |key, value|
add_object(key)
add_object(value)
end
end
end
def with_nesting
@depth += 1
raise TooMuchDataError if too_deep?
yield
@depth -= 1
end
end
end
end

View file

@ -10281,7 +10281,7 @@ msgstr[1] ""
msgid "score" msgid "score"
msgstr "" msgstr ""
msgid "should be higher than %{access} inherited membership from group %{group_name}" msgid "should be greater than or equal to %{access} inherited membership from group %{group_name}"
msgstr "" msgstr ""
msgid "show less" msgid "show less"

View file

@ -250,7 +250,7 @@ describe Projects::NotesController do
before do before do
service_params = ActionController::Parameters.new({ service_params = ActionController::Parameters.new({
note: 'some note', note: 'some note',
noteable_id: merge_request.id.to_s, noteable_id: merge_request.id,
noteable_type: 'MergeRequest', noteable_type: 'MergeRequest',
commit_id: nil, commit_id: nil,
merge_request_diff_head_sha: 'sha' merge_request_diff_head_sha: 'sha'

View file

@ -1,49 +1,101 @@
require 'spec_helper' require 'spec_helper'
describe Projects::TemplatesController do describe Projects::TemplatesController do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository, :private) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:file_path_1) { '.gitlab/issue_templates/issue_template.md' }
let(:file_path_1) { '.gitlab/issue_templates/bug.md' } let(:file_path_2) { '.gitlab/merge_request_templates/merge_request_template.md' }
let(:body) { JSON.parse(response.body) } let(:body) { JSON.parse(response.body) }
let!(:file_1) { project.repository.create_file(user, file_path_1, 'issue content', message: 'message', branch_name: 'master') }
let!(:file_2) { project.repository.create_file(user, file_path_2, 'merge request content', message: 'message', branch_name: 'master') }
describe '#show' do
shared_examples 'renders issue templates as json' do
it do
get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :json)
expect(response.status).to eq(200)
expect(body['name']).to eq('issue_template')
expect(body['content']).to eq('issue content')
end
end
shared_examples 'renders merge request templates as json' do
it do
get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template', project_id: project }, format: :json)
expect(response.status).to eq(200)
expect(body['name']).to eq('merge_request_template')
expect(body['content']).to eq('merge request content')
end
end
shared_examples 'renders 404 when requesting an issue template' do
it do
get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :json)
expect(response.status).to eq(404)
end
end
shared_examples 'renders 404 when requesting a merge request template' do
it do
get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template', project_id: project }, format: :json)
expect(response.status).to eq(404)
end
end
shared_examples 'renders 404 when params are invalid' do
it 'does not route when the template type is invalid' do
expect do
get(:show, params: { namespace_id: project.namespace, template_type: 'invalid_type', key: 'issue_template', project_id: project }, format: :json)
end.to raise_error(ActionController::UrlGenerationError)
end
it 'renders 404 when the format type is invalid' do
get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :html)
expect(response.status).to eq(404)
end
it 'renders 404 when the key is unknown' do
get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'unknown_template', project_id: project }, format: :json)
expect(response.status).to eq(404)
end
end
context 'when the user is not a member of the project' do
before do
sign_in(user)
end
include_examples 'renders 404 when requesting an issue template'
include_examples 'renders 404 when requesting a merge request template'
include_examples 'renders 404 when params are invalid'
end
context 'when user is a member of the project' do
before do before do
project.add_developer(user) project.add_developer(user)
sign_in(user) sign_in(user)
end end
include_examples 'renders issue templates as json'
include_examples 'renders merge request templates as json'
include_examples 'renders 404 when params are invalid'
end
context 'when user is a guest of the project' do
before do before do
project.add_user(user, Gitlab::Access::MAINTAINER) project.add_guest(user)
project.repository.create_file(user, file_path_1, 'something valid',
message: 'test 3', branch_name: 'master')
end
describe '#show' do
it 'renders template name and content as json' do
get(:show, params: { namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project }, format: :json)
expect(response.status).to eq(200)
expect(body["name"]).to eq("bug")
expect(body["content"]).to eq("something valid")
end
it 'renders 404 when unauthorized' do
sign_in(user2)
get(:show, params: { namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project }, format: :json)
expect(response.status).to eq(404)
end
it 'renders 404 when template type is not found' do
sign_in(user) sign_in(user)
get(:show, params: { namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project }, format: :json)
expect(response.status).to eq(404)
end end
it 'renders 404 without errors' do include_examples 'renders issue templates as json'
sign_in(user) include_examples 'renders 404 when requesting a merge request template'
expect { get(:show, params: { namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project }, format: :json) }.not_to raise_error include_examples 'renders 404 when params are invalid'
end end
end end
end end

View file

@ -117,6 +117,119 @@ describe Snippets::NotesController do
end end
end end
describe 'POST create' do
context 'when a snippet is public' do
let(:request_params) do
{
note: attributes_for(:note_on_personal_snippet, noteable: public_snippet),
snippet_id: public_snippet.id
}
end
before do
sign_in user
end
it 'returns status 302' do
post :create, params: request_params
expect(response).to have_gitlab_http_status(302)
end
it 'creates the note' do
expect { post :create, params: request_params }.to change { Note.count }.by(1)
end
end
context 'when a snippet is internal' do
let(:request_params) do
{
note: attributes_for(:note_on_personal_snippet, noteable: internal_snippet),
snippet_id: internal_snippet.id
}
end
before do
sign_in user
end
it 'returns status 302' do
post :create, params: request_params
expect(response).to have_gitlab_http_status(302)
end
it 'creates the note' do
expect { post :create, params: request_params }.to change { Note.count }.by(1)
end
end
context 'when a snippet is private' do
let(:request_params) do
{
note: attributes_for(:note_on_personal_snippet, noteable: private_snippet),
snippet_id: private_snippet.id
}
end
before do
sign_in user
end
context 'when user is not the author' do
before do
sign_in(user)
end
it 'returns status 404' do
post :create, params: request_params
expect(response).to have_gitlab_http_status(404)
end
it 'does not create the note' do
expect { post :create, params: request_params }.not_to change { Note.count }
end
context 'when user sends a snippet_id for a public snippet' do
let(:request_params) do
{
note: attributes_for(:note_on_personal_snippet, noteable: private_snippet),
snippet_id: public_snippet.id
}
end
it 'returns status 302' do
post :create, params: request_params
expect(response).to have_gitlab_http_status(302)
end
it 'creates the note on the public snippet' do
expect { post :create, params: request_params }.to change { Note.count }.by(1)
expect(Note.last.noteable).to eq public_snippet
end
end
end
context 'when user is the author' do
before do
sign_in(private_snippet.author)
end
it 'returns status 302' do
post :create, params: request_params
expect(response).to have_gitlab_http_status(302)
end
it 'creates the note' do
expect { post :create, params: request_params }.to change { Note.count }.by(1)
end
end
end
end
describe 'DELETE destroy' do describe 'DELETE destroy' do
let(:request_params) do let(:request_params) do
{ {

View file

@ -207,8 +207,8 @@ describe SnippetsController do
context 'when the snippet description contains a file' do context 'when the snippet description contains a file' do
include FileMoverHelpers include FileMoverHelpers
let(:picture_file) { '/-/system/temp/secret56/picture.jpg' } let(:picture_file) { "/-/system/user/#{user.id}/secret56/picture.jpg" }
let(:text_file) { '/-/system/temp/secret78/text.txt' } let(:text_file) { "/-/system/user/#{user.id}/secret78/text.txt" }
let(:description) do let(:description) do
"Description with picture: ![picture](/uploads#{picture_file}) and "\ "Description with picture: ![picture](/uploads#{picture_file}) and "\
"text: [text.txt](/uploads#{text_file})" "text: [text.txt](/uploads#{text_file})"

View file

@ -22,11 +22,13 @@ describe UploadsController do
let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) } let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
describe 'POST create' do describe 'POST create' do
let(:model) { 'personal_snippet' }
let(:snippet) { create(:personal_snippet, :public) }
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') } let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
context 'snippet uploads' do
let(:model) { 'personal_snippet' }
let(:snippet) { create(:personal_snippet, :public) }
context 'when a user does not have permissions to upload a file' do context 'when a user does not have permissions to upload a file' do
it "returns 401 when the user is not logged in" do it "returns 401 when the user is not logged in" do
post :create, params: { model: model, id: snippet.id }, format: :json post :create, params: { model: model, id: snippet.id }, format: :json
@ -105,38 +107,75 @@ describe UploadsController do
end end
end end
end end
end
end
context 'user uploads' do
let(:model) { 'user' }
it 'returns 401 when the user has no access' do
post :create, params: { model: 'user', id: user.id }, format: :json
expect(response).to have_gitlab_http_status(401)
end
context 'when user is logged in' do
before do
sign_in(user)
end
context 'temporal with valid image' do
subject do subject do
post :create, params: { model: 'personal_snippet', file: jpg }, format: :json post :create, params: { model: model, id: user.id, file: jpg }, format: :json
end end
it 'returns a content with original filename, new link, and correct type.' do it 'returns a content with original filename, new link, and correct type.' do
subject subject
expect(response.body).to match '\"alt\":\"rails_sample\"' expect(response.body).to match '\"alt\":\"rails_sample\"'
expect(response.body).to match "\"url\":\"/uploads/-/system/temp" expect(response.body).to match "\"url\":\"/uploads/-/system/user/#{user.id}/"
end end
it 'does not create an Upload record' do it 'creates a corresponding Upload record' do
expect { subject }.not_to change { Upload.count } expect { subject }.to change { Upload.count }
upload = Upload.last
aggregate_failures do
expect(upload).to exist
expect(upload.model).to eq user
end end
end end
context 'temporal with valid non-image file' do context 'with valid non-image file' do
subject do subject do
post :create, params: { model: 'personal_snippet', file: txt }, format: :json post :create, params: { model: model, id: user.id, file: txt }, format: :json
end end
it 'returns a content with original filename, new link, and correct type.' do it 'returns a content with original filename, new link, and correct type.' do
subject subject
expect(response.body).to match '\"alt\":\"doc_sample.txt\"' expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
expect(response.body).to match "\"url\":\"/uploads/-/system/temp" expect(response.body).to match "\"url\":\"/uploads/-/system/user/#{user.id}/"
end end
it 'does not create an Upload record' do it 'creates a corresponding Upload record' do
expect { subject }.not_to change { Upload.count } expect { subject }.to change { Upload.count }
upload = Upload.last
aggregate_failures do
expect(upload).to exist
expect(upload.model).to eq user
end
end
end
it 'returns 404 when given user is not the logged in one' do
another_user = create(:user)
post :create, params: { model: model, id: another_user.id, file: txt }, format: :json
expect(response).to have_gitlab_http_status(404)
end end
end end
end end

View file

@ -260,6 +260,7 @@ FactoryBot.define do
trait(:merge_requests_enabled) { merge_requests_access_level ProjectFeature::ENABLED } trait(:merge_requests_enabled) { merge_requests_access_level ProjectFeature::ENABLED }
trait(:merge_requests_disabled) { merge_requests_access_level ProjectFeature::DISABLED } trait(:merge_requests_disabled) { merge_requests_access_level ProjectFeature::DISABLED }
trait(:merge_requests_private) { merge_requests_access_level ProjectFeature::PRIVATE } trait(:merge_requests_private) { merge_requests_access_level ProjectFeature::PRIVATE }
trait(:merge_requests_public) { merge_requests_access_level ProjectFeature::PUBLIC }
trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED } trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED }
trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED } trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED }
trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE } trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE }

View file

@ -41,7 +41,7 @@ describe 'User creates snippet', :js do
expect(page).to have_content('My Snippet') expect(page).to have_content('My Snippet')
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
expect(link).to match(%r{/uploads/-/system/temp/\h{32}/banana_sample\.gif\z}) expect(link).to match(%r{/uploads/-/system/user/#{user.id}/\h{32}/banana_sample\.gif\z})
reqs = inspect_requests { visit(link) } reqs = inspect_requests { visit(link) }
expect(reqs.first.status_code).to eq(200) expect(reqs.first.status_code).to eq(200)

View file

@ -669,9 +669,7 @@ describe IssuesFinder do
end end
it 'filters by confidentiality' do it 'filters by confidentiality' do
expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything) expect(subject.to_sql).to match("issues.confidential")
subject
end end
end end
@ -688,9 +686,7 @@ describe IssuesFinder do
end end
it 'filters by confidentiality' do it 'filters by confidentiality' do
expect(Issue).to receive(:where).with(a_string_matching('confidential'), anything) expect(subject.to_sql).to match("issues.confidential")
subject
end end
end end

View file

@ -31,7 +31,7 @@ describe MergeRequestsFinder do
end end
context 'filtering by group' do context 'filtering by group' do
it 'includes all merge requests when user has access exceluding merge requests from projects the user does not have access to' do it 'includes all merge requests when user has access excluding merge requests from projects the user does not have access to' do
private_project = allow_gitaly_n_plus_1 { create(:project, :private, group: group) } private_project = allow_gitaly_n_plus_1 { create(:project, :private, group: group) }
private_project.add_guest(user) private_project.add_guest(user)
create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project) create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project)

View file

@ -0,0 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['Label'] do
it { is_expected.to require_graphql_authorizations(:read_label) }
end

View file

@ -2,4 +2,5 @@ require 'spec_helper'
describe GitlabSchema.types['Metadata'] do describe GitlabSchema.types['Metadata'] do
it { expect(described_class.graphql_name).to eq('Metadata') } it { expect(described_class.graphql_name).to eq('Metadata') }
it { is_expected.to require_graphql_authorizations(:read_instance_metadata) }
end end

View file

@ -24,9 +24,5 @@ describe GitlabSchema.types['Query'] do
is_expected.to have_graphql_type(Types::MetadataType) is_expected.to have_graphql_type(Types::MetadataType)
is_expected.to have_graphql_resolver(Resolvers::MetadataResolver) is_expected.to have_graphql_resolver(Resolvers::MetadataResolver)
end end
it 'authorizes with read_instance_metadata' do
is_expected.to require_graphql_authorizations(:read_instance_metadata)
end
end end
end end

View file

@ -5,6 +5,40 @@ describe API::Helpers::RelatedResourcesHelpers do
Class.new.include(described_class).new Class.new.include(described_class).new
end end
describe '#expose_path' do
let(:path) { '/api/v4/awesome_endpoint' }
context 'empty relative URL root' do
before do
stub_config_setting(relative_url_root: '')
end
it 'returns the existing path' do
expect(helpers.expose_path(path)).to eq(path)
end
end
context 'slash relative URL root' do
before do
stub_config_setting(relative_url_root: '/')
end
it 'returns the existing path' do
expect(helpers.expose_path(path)).to eq(path)
end
end
context 'with relative URL root' do
before do
stub_config_setting(relative_url_root: '/gitlab/root')
end
it 'returns the existing path' do
expect(helpers.expose_path(path)).to eq("/gitlab/root" + path)
end
end
end
describe '#expose_url' do describe '#expose_url' do
let(:path) { '/api/v4/awesome_endpoint' } let(:path) { '/api/v4/awesome_endpoint' }
subject(:url) { helpers.expose_url(path) } subject(:url) { helpers.expose_url(path) }

View file

@ -83,6 +83,11 @@ describe Banzai::Filter::RelativeLinkFilter do
expect { filter(act) }.not_to raise_error expect { filter(act) }.not_to raise_error
end end
it 'does not explode with an escaped null byte' do
act = link("/%00")
expect { filter(act) }.not_to raise_error
end
it 'does not raise an exception with a space in the path' do it 'does not raise an exception with a space in the path' do
act = link("/uploads/d18213acd3732630991986120e167e3d/Landscape_8.jpg \nBut here's some more unexpected text :smile:)") act = link("/uploads/d18213acd3732630991986120e167e3d/Landscape_8.jpg \nBut here's some more unexpected text :smile:)")
expect { filter(act) }.not_to raise_error expect { filter(act) }.not_to raise_error

View file

@ -90,6 +90,27 @@ describe Gitlab::Ci::Config do
end end
end end
context 'when yml is too big' do
let(:yml) do
<<~YAML
--- &1
- hi
- *1
YAML
end
describe '.new' do
it 'raises error' do
expect(Gitlab::Sentry).to receive(:track_exception)
expect { config }.to raise_error(
described_class::ConfigError,
/The parsed YAML is too big/
)
end
end
end
context 'when config logic is incorrect' do context 'when config logic is incorrect' do
let(:yml) { 'before_script: "ls"' } let(:yml) { 'before_script: "ls"' }

View file

@ -8,7 +8,7 @@ describe Gitlab::Config::Loader::Yaml do
describe '#valid?' do describe '#valid?' do
it 'returns true' do it 'returns true' do
expect(loader.valid?).to be true expect(loader).to be_valid
end end
end end
@ -24,7 +24,7 @@ describe Gitlab::Config::Loader::Yaml do
describe '#valid?' do describe '#valid?' do
it 'returns false' do it 'returns false' do
expect(loader.valid?).to be false expect(loader).not_to be_valid
end end
end end
@ -43,7 +43,10 @@ describe Gitlab::Config::Loader::Yaml do
describe '#initialize' do describe '#initialize' do
it 'raises FormatError' do it 'raises FormatError' do
expect { loader }.to raise_error(Gitlab::Config::Loader::FormatError, 'Unknown alias: bad_alias') expect { loader }.to raise_error(
Gitlab::Config::Loader::FormatError,
'Unknown alias: bad_alias'
)
end end
end end
end end
@ -53,7 +56,68 @@ describe Gitlab::Config::Loader::Yaml do
describe '#valid?' do describe '#valid?' do
it 'returns false' do it 'returns false' do
expect(loader.valid?).to be false expect(loader).not_to be_valid
end
end
end
# Prevent Billion Laughs attack: https://gitlab.com/gitlab-org/gitlab-ce/issues/56018
context 'when yaml size is too large' do
let(:yml) do
<<~YAML
a: &a ["lol","lol","lol","lol","lol","lol","lol","lol","lol"]
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e]
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f]
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g]
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]
YAML
end
describe '#valid?' do
it 'returns false' do
expect(loader).not_to be_valid
end
it 'returns true if "ci_yaml_limit_size" feature flag is disabled' do
stub_feature_flags(ci_yaml_limit_size: false)
expect(loader).to be_valid
end
end
describe '#load!' do
it 'raises FormatError' do
expect { loader.load! }.to raise_error(
Gitlab::Config::Loader::FormatError,
'The parsed YAML is too big'
)
end
end
end
# Prevent Billion Laughs attack: https://gitlab.com/gitlab-org/gitlab-ce/issues/56018
context 'when yaml has cyclic data structure' do
let(:yml) do
<<~YAML
--- &1
- hi
- *1
YAML
end
describe '#valid?' do
it 'returns false' do
expect(loader.valid?).to be(false)
end
end
describe '#load!' do
it 'raises FormatError' do
expect { loader.load! }.to raise_error(Gitlab::Config::Loader::FormatError, 'The parsed YAML is too big')
end end
end end
end end

View file

@ -50,5 +50,14 @@ describe Gitlab::DataBuilder::Pipeline do
it { expect(attributes[:variables]).to be_a(Array) } it { expect(attributes[:variables]).to be_a(Array) }
it { expect(attributes[:variables]).to contain_exactly({ key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1' }) } it { expect(attributes[:variables]).to contain_exactly({ key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1' }) }
end end
context 'when pipeline is a detached merge request pipeline' do
let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
let(:pipeline) { merge_request.all_pipelines.first }
it 'returns a source ref' do
expect(attributes[:ref]).to eq(merge_request.source_branch)
end
end
end end
end end

View file

@ -215,6 +215,18 @@ describe Gitlab::Git::Repository, :seed_helper do
it { is_expected.to be < 2 } it { is_expected.to be < 2 }
end end
describe '#object_directory_size' do
before do
allow(repository.gitaly_repository_client)
.to receive(:get_object_directory_size)
.and_return(2)
end
subject { repository.object_directory_size }
it { is_expected.to eq 2048 }
end
describe '#empty?' do describe '#empty?' do
it { expect(repository).not_to be_empty } it { expect(repository).not_to be_empty }
end end

View file

@ -19,7 +19,9 @@ describe Gitlab::Git::Tree, :seed_helper do
it 'returns a list of tree objects' do it 'returns a list of tree objects' do
entries = described_class.where(repository, SeedRepo::Commit::ID, 'files', true) entries = described_class.where(repository, SeedRepo::Commit::ID, 'files', true)
expect(entries.count).to be >= 5 expect(entries.map(&:path)).to include('files/html',
'files/markdown/ruby-style-guide.md')
expect(entries.count).to be >= 10
expect(entries).to all(be_a(Gitlab::Git::Tree)) expect(entries).to all(be_a(Gitlab::Git::Tree))
end end

View file

@ -73,6 +73,17 @@ describe Gitlab::GitalyClient::RepositoryService do
end end
end end
describe '#get_object_directory_size' do
it 'sends a get_object_directory_size message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:get_object_directory_size)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(size: 0)
client.get_object_directory_size
end
end
describe '#apply_gitattributes' do describe '#apply_gitattributes' do
let(:revision) { 'master' } let(:revision) { 'master' }

View file

@ -7,35 +7,39 @@ require 'spec_helper'
describe Gitlab::Graphql::Authorize::AuthorizeFieldService do describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
def type(type_authorizations = []) def type(type_authorizations = [])
Class.new(Types::BaseObject) do Class.new(Types::BaseObject) do
graphql_name "TestType" graphql_name 'TestType'
authorize type_authorizations authorize type_authorizations
end end
end end
def type_with_field(field_type, field_authorizations = [], resolved_value = "Resolved value") def type_with_field(field_type, field_authorizations = [], resolved_value = 'Resolved value', **options)
Class.new(Types::BaseObject) do Class.new(Types::BaseObject) do
graphql_name "TestTypeWithField" graphql_name 'TestTypeWithField'
field :test_field, field_type, null: true, authorize: field_authorizations, resolve: -> (_, _, _) { resolved_value} options.reverse_merge!(null: true)
field :test_field, field_type,
authorize: field_authorizations,
resolve: -> (_, _, _) { resolved_value },
**options
end end
end end
let(:current_user) { double(:current_user) } let(:current_user) { double(:current_user) }
subject(:service) { described_class.new(field) } subject(:service) { described_class.new(field) }
describe "#authorized_resolve" do describe '#authorized_resolve' do
let(:presented_object) { double("presented object") } let(:presented_object) { double('presented object') }
let(:presented_type) { double("parent type", object: presented_object) } let(:presented_type) { double('parent type', object: presented_object) }
subject(:resolved) { service.authorized_resolve.call(presented_type, {}, { current_user: current_user }) } subject(:resolved) { service.authorized_resolve.call(presented_type, {}, { current_user: current_user }) }
context "scalar types" do context 'scalar types' do
shared_examples "checking permissions on the presented object" do shared_examples 'checking permissions on the presented object' do
it "checks the abilities on the object being presented and returns the value" do it 'checks the abilities on the object being presented and returns the value' do
expected_permissions.each do |permission| expected_permissions.each do |permission|
spy_ability_check_for(permission, presented_object, passed: true) spy_ability_check_for(permission, presented_object, passed: true)
end end
expect(resolved).to eq("Resolved value") expect(resolved).to eq('Resolved value')
end end
it "returns nil if the value wasn't authorized" do it "returns nil if the value wasn't authorized" do
@ -45,47 +49,57 @@ describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
end end
end end
context "when the field is a scalar type" do context 'when the field is a built-in scalar type' do
let(:field) { type_with_field(GraphQL::STRING_TYPE, :read_field).fields["testField"].to_graphql } let(:field) { type_with_field(GraphQL::STRING_TYPE, :read_field).fields['testField'].to_graphql }
let(:expected_permissions) { [:read_field] } let(:expected_permissions) { [:read_field] }
it_behaves_like "checking permissions on the presented object" it_behaves_like 'checking permissions on the presented object'
end end
context "when the field is a list of scalar types" do context 'when the field is a list of scalar types' do
let(:field) { type_with_field([GraphQL::STRING_TYPE], :read_field).fields["testField"].to_graphql } let(:field) { type_with_field([GraphQL::STRING_TYPE], :read_field).fields['testField'].to_graphql }
let(:expected_permissions) { [:read_field] } let(:expected_permissions) { [:read_field] }
it_behaves_like "checking permissions on the presented object" it_behaves_like 'checking permissions on the presented object'
end end
end end
context "when the field is a specific type" do context 'when the field is a specific type' do
let(:custom_type) { type(:read_type) } let(:custom_type) { type(:read_type) }
let(:object_in_field) { double("presented in field") } let(:object_in_field) { double('presented in field') }
let(:field) { type_with_field(custom_type, :read_field, object_in_field).fields["testField"].to_graphql } let(:field) { type_with_field(custom_type, :read_field, object_in_field).fields['testField'].to_graphql }
it "checks both field & type permissions" do it 'checks both field & type permissions' do
spy_ability_check_for(:read_field, object_in_field, passed: true) spy_ability_check_for(:read_field, object_in_field, passed: true)
spy_ability_check_for(:read_type, object_in_field, passed: true) spy_ability_check_for(:read_type, object_in_field, passed: true)
expect(resolved).to eq(object_in_field) expect(resolved).to eq(object_in_field)
end end
it "returns nil if viewing was not allowed" do it 'returns nil if viewing was not allowed' do
spy_ability_check_for(:read_field, object_in_field, passed: false) spy_ability_check_for(:read_field, object_in_field, passed: false)
spy_ability_check_for(:read_type, object_in_field, passed: true) spy_ability_check_for(:read_type, object_in_field, passed: true)
expect(resolved).to be_nil expect(resolved).to be_nil
end end
context "when the field is a list" do context 'when the field is not nullable' do
let(:object_1) { double("presented in field 1") } let(:field) { type_with_field(custom_type, [], object_in_field, null: false).fields['testField'].to_graphql }
let(:object_2) { double("presented in field 2") }
let(:presented_types) { [double(object: object_1), double(object: object_2)] }
let(:field) { type_with_field([custom_type], :read_field, presented_types).fields["testField"].to_graphql }
it "checks all permissions" do it 'returns nil when viewing is not allowed' do
spy_ability_check_for(:read_type, object_in_field, passed: false)
expect(resolved).to be_nil
end
end
context 'when the field is a list' do
let(:object_1) { double('presented in field 1') }
let(:object_2) { double('presented in field 2') }
let(:presented_types) { [double(object: object_1), double(object: object_2)] }
let(:field) { type_with_field([custom_type], :read_field, presented_types).fields['testField'].to_graphql }
it 'checks all permissions' do
allow(Ability).to receive(:allowed?) { true } allow(Ability).to receive(:allowed?) { true }
spy_ability_check_for(:read_field, object_1, passed: true) spy_ability_check_for(:read_field, object_1, passed: true)
@ -96,7 +110,7 @@ describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
expect(resolved).to eq(presented_types) expect(resolved).to eq(presented_types)
end end
it "filters out objects that the user cannot see" do it 'filters out objects that the user cannot see' do
allow(Ability).to receive(:allowed?) { true } allow(Ability).to receive(:allowed?) { true }
spy_ability_check_for(:read_type, object_1, passed: false) spy_ability_check_for(:read_type, object_1, passed: false)

View file

@ -7,11 +7,11 @@ describe Gitlab::IssuableMetadata do
subject { Class.new { include Gitlab::IssuableMetadata }.new } subject { Class.new { include Gitlab::IssuableMetadata }.new }
it 'returns an empty Hash if an empty collection is provided' do it 'returns an empty Hash if an empty collection is provided' do
expect(subject.issuable_meta_data(Issue.none, 'Issue')).to eq({}) expect(subject.issuable_meta_data(Issue.none, 'Issue', user)).to eq({})
end end
it 'raises an error when given a collection with no limit' do it 'raises an error when given a collection with no limit' do
expect { subject.issuable_meta_data(Issue.all, 'Issue') }.to raise_error(/must have a limit/) expect { subject.issuable_meta_data(Issue.all, 'Issue', user) }.to raise_error(/must have a limit/)
end end
context 'issues' do context 'issues' do
@ -23,7 +23,7 @@ describe Gitlab::IssuableMetadata do
let!(:closing_issues) { create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request) } let!(:closing_issues) { create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request) }
it 'aggregates stats on issues' do it 'aggregates stats on issues' do
data = subject.issuable_meta_data(Issue.all.limit(10), 'Issue') data = subject.issuable_meta_data(Issue.all.limit(10), 'Issue', user)
expect(data.count).to eq(2) expect(data.count).to eq(2)
expect(data[issue.id].upvotes).to eq(1) expect(data[issue.id].upvotes).to eq(1)
@ -46,7 +46,7 @@ describe Gitlab::IssuableMetadata do
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
it 'aggregates stats on merge requests' do it 'aggregates stats on merge requests' do
data = subject.issuable_meta_data(MergeRequest.all.limit(10), 'MergeRequest') data = subject.issuable_meta_data(MergeRequest.all.limit(10), 'MergeRequest', user)
expect(data.count).to eq(2) expect(data.count).to eq(2)
expect(data[merge_request.id].upvotes).to eq(1) expect(data[merge_request.id].upvotes).to eq(1)

View file

@ -0,0 +1,43 @@
require 'spec_helper'
describe Gitlab::Utils::DeepSize do
let(:data) do
{
a: [1, 2, 3],
b: {
c: [4, 5],
d: [
{ e: [[6], [7]] }
]
}
}
end
let(:max_size) { 1.kilobyte }
let(:max_depth) { 10 }
let(:deep_size) { described_class.new(data, max_size: max_size, max_depth: max_depth) }
describe '#evaluate' do
context 'when data within size and depth limits' do
it 'returns true' do
expect(deep_size).to be_valid
end
end
context 'when data not within size limit' do
let(:max_size) { 200.bytes }
it 'returns false' do
expect(deep_size).not_to be_valid
end
end
context 'when data not within depth limit' do
let(:max_depth) { 2 }
it 'returns false' do
expect(deep_size).not_to be_valid
end
end
end
end

View file

@ -70,6 +70,16 @@ describe Member do
expect(child_member).not_to be_valid expect(child_member).not_to be_valid
end end
# Membership in a subgroup confers certain access rights, such as being
# able to merge or push code to protected branches.
it "is valid with an equal level" do
child_member.access_level = GroupMember::DEVELOPER
child_member.validate
expect(child_member).to be_valid
end
it "is valid with a higher level" do it "is valid with a higher level" do
child_member.access_level = GroupMember::MAINTAINER child_member.access_level = GroupMember::MAINTAINER

View file

@ -3188,63 +3188,107 @@ describe Project do
end end
describe '.with_feature_available_for_user' do describe '.with_feature_available_for_user' do
let!(:user) { create(:user) } let(:user) { create(:user) }
let!(:feature) { MergeRequest } let(:feature) { MergeRequest }
let!(:project) { create(:project, :public, :merge_requests_enabled) }
subject { described_class.with_feature_available_for_user(feature, user) } subject { described_class.with_feature_available_for_user(feature, user) }
context 'when user has access to project' do shared_examples 'feature disabled' do
subject { described_class.with_feature_available_for_user(feature, user) } let(:project) { create(:project, :public, :merge_requests_disabled) }
it 'does not return projects with the project feature disabled' do
is_expected.not_to include(project)
end
end
shared_examples 'feature public' do
let(:project) { create(:project, :public, :merge_requests_public) }
it 'returns projects with the project feature public' do
is_expected.to include(project)
end
end
shared_examples 'feature enabled' do
let(:project) { create(:project, :public, :merge_requests_enabled) }
it 'returns projects with the project feature enabled' do
is_expected.to include(project)
end
end
shared_examples 'feature access level is nil' do
let(:project) { create(:project, :public) }
it 'returns projects with the project feature access level nil' do
project.project_feature.update(merge_requests_access_level: nil)
is_expected.to include(project)
end
end
context 'with user' do
before do before do
project.add_guest(user) project.add_guest(user)
end end
context 'when public project' do it_behaves_like 'feature disabled'
context 'when feature is public' do it_behaves_like 'feature public'
it 'returns project' do it_behaves_like 'feature enabled'
is_expected.to include(project) it_behaves_like 'feature access level is nil'
end
end
context 'when feature is private' do context 'when feature is private' do
let!(:project) { create(:project, :public, :merge_requests_private) } let(:project) { create(:project, :public, :merge_requests_private) }
it 'returns project when user has access to the feature' do context 'when user does not has access to the feature' do
project.add_maintainer(user) it 'does not return projects with the project feature private' do
is_expected.not_to include(project)
end
end
context 'when user has access to the feature' do
it 'returns projects with the project feature private' do
project.add_reporter(user)
is_expected.to include(project) is_expected.to include(project)
end end
it 'does not return project when user does not have the minimum access level required' do
is_expected.not_to include(project)
end end
end end
end end
context 'when private project' do context 'user is an admin' do
let!(:project) { create(:project) } let(:user) { create(:user, :admin) }
it 'returns project when user has access to the feature' do it_behaves_like 'feature disabled'
project.add_maintainer(user) it_behaves_like 'feature public'
it_behaves_like 'feature enabled'
it_behaves_like 'feature access level is nil'
context 'when feature is private' do
let(:project) { create(:project, :public, :merge_requests_private) }
it 'returns projects with the project feature private' do
is_expected.to include(project) is_expected.to include(project)
end end
end
end
it 'does not return project when user does not have the minimum access level required' do context 'without user' do
let(:user) { nil }
it_behaves_like 'feature disabled'
it_behaves_like 'feature public'
it_behaves_like 'feature enabled'
it_behaves_like 'feature access level is nil'
context 'when feature is private' do
let(:project) { create(:project, :public, :merge_requests_private) }
it 'does not return projects with the project feature private' do
is_expected.not_to include(project) is_expected.not_to include(project)
end end
end end
end end
context 'when user does not have access to project' do
let!(:project) { create(:project) }
it 'does not return project when user cant access project' do
is_expected.not_to include(project)
end
end
end end
describe '#pages_available?' do describe '#pages_available?' do

View file

@ -136,24 +136,6 @@ describe Ci::BuildRunnerPresenter do
is_expected.to eq(1) is_expected.to eq(1)
end end
end end
context 'when pipeline is detached merge request pipeline' do
let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
let(:pipeline) { merge_request.all_pipelines.first }
let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) }
it 'returns the default git depth for pipelines for merge requests' do
is_expected.to eq(described_class::DEFAULT_GIT_DEPTH_MERGE_REQUEST)
end
context 'when pipeline is legacy detached merge request pipeline' do
let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) }
it 'behaves as branch pipeline' do
is_expected.to eq(0)
end
end
end
end end
describe '#refspecs' do describe '#refspecs' do
@ -191,7 +173,9 @@ describe Ci::BuildRunnerPresenter do
it 'returns the correct refspecs' do it 'returns the correct refspecs' do
is_expected is_expected
.to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head') .to contain_exactly('+refs/heads/*:refs/remotes/origin/*',
'+refs/tags/*:refs/tags/*',
'+refs/merge-requests/1/head:refs/merge-requests/1/head')
end end
context 'when pipeline is legacy detached merge request pipeline' do context 'when pipeline is legacy detached merge request pipeline' do

View file

@ -236,7 +236,7 @@ describe API::Members do
params: { user_id: stranger.id, access_level: Member::REPORTER } params: { user_id: stranger.id, access_level: Member::REPORTER }
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['access_level']).to eq(["should be higher than Developer inherited membership from group #{parent.name}"]) expect(json_response['message']['access_level']).to eq(["should be greater than or equal to Developer inherited membership from group #{parent.name}"])
end end
it 'creates the member if group level is lower', :nested_groups do it 'creates the member if group level is lower', :nested_groups do

View file

@ -830,6 +830,31 @@ describe API::MergeRequests do
end end
end end
context 'head_pipeline' do
before do
merge_request.update(head_pipeline: create(:ci_pipeline))
merge_request.project.project_feature.update(builds_access_level: 10)
end
context 'when user can read the pipeline' do
it 'exposes pipeline information' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
expect(json_response).to include('head_pipeline')
end
end
context 'when user can not read the pipeline' do
let(:guest) { create(:user) }
it 'does not expose pipeline information' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", guest)
expect(json_response).not_to include('head_pipeline')
end
end
end
it 'returns the commits behind the target branch when include_diverged_commits_count is present' do it 'returns the commits behind the target branch when include_diverged_commits_count is present' do
allow_any_instance_of(merge_request.class).to receive(:diverged_commits_count).and_return(1) allow_any_instance_of(merge_request.class).to receive(:diverged_commits_count).and_return(1)

View file

@ -504,8 +504,9 @@ describe API::Projects do
project4.add_reporter(user2) project4.add_reporter(user2)
end end
it 'returns an array of groups the user has at least developer access' do it 'returns an array of projects the user has at least developer access' do
get api('/projects', user2), params: { min_access_level: 30 } get api('/projects', user2), params: { min_access_level: 30 }
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
expect(json_response).to be_an Array expect(json_response).to be_an Array

View file

@ -661,4 +661,24 @@ describe 'project routing' do
end end
end end
end end
describe Projects::TemplatesController, 'routing' do
describe '#show' do
def show_with_template_type(template_type)
"/gitlab/gitlabhq/templates/#{template_type}/template_name"
end
it 'routes when :template_type is `merge_request`' do
expect(get(show_with_template_type('merge_request'))).to route_to('projects/templates#show', namespace_id: 'gitlab', project_id: 'gitlabhq', template_type: 'merge_request', key: 'template_name', format: 'json')
end
it 'routes when :template_type is `issue`' do
expect(get(show_with_template_type('issue'))).to route_to('projects/templates#show', namespace_id: 'gitlab', project_id: 'gitlabhq', template_type: 'issue', key: 'template_name', format: 'json')
end
it 'routes to application#route_not_found when :template_type is unknown' do
expect(get(show_with_template_type('invalid'))).to route_to('application#route_not_found', unmatched_route: 'gitlab/gitlabhq/templates/invalid/template_name')
end
end
end
end end

View file

@ -0,0 +1,31 @@
# frozen_string_literal: true
require 'spec_helper'
describe 'Uploads', 'routing' do
it 'allows creating uploads for personal snippets' do
expect(post('/uploads/personal_snippet?id=1')).to route_to(
controller: 'uploads',
action: 'create',
model: 'personal_snippet',
id: '1'
)
end
it 'allows creating uploads for users' do
expect(post('/uploads/user?id=1')).to route_to(
controller: 'uploads',
action: 'create',
model: 'user',
id: '1'
)
end
it 'does not allow creating uploads for other models' do
unroutable_models = UploadsController::MODEL_CLASSES.keys.compact - %w(personal_snippet user)
unroutable_models.each do |model|
expect(post("/uploads/#{model}?id=1")).not_to be_routable
end
end
end

View file

@ -62,6 +62,25 @@ describe Lfs::FileTransformer do
expect(result.encoding).to eq('text') expect(result.encoding).to eq('text')
end end
context 'when an actual file is passed' do
let(:file) { Tempfile.new(file_path) }
before do
file.write(file_content)
file.rewind
end
after do
file.unlink
end
it "creates an LfsObject with the file's content" do
subject.new_file(file_path, file)
expect(LfsObject.last.file.read).to eq file_content
end
end
context "when doesn't use LFS" do context "when doesn't use LFS" do
let(:file_path) { 'other.filetype' } let(:file_path) { 'other.filetype' }

View file

@ -13,7 +13,7 @@ describe Projects::AfterImportService do
describe '#execute' do describe '#execute' do
before do before do
allow(Projects::HousekeepingService) allow(Projects::HousekeepingService)
.to receive(:new).with(project, :gc).and_return(housekeeping_service) .to receive(:new).with(project).and_return(housekeeping_service)
allow(housekeeping_service) allow(housekeeping_service)
.to receive(:execute).and_yield .to receive(:execute).and_yield

View file

@ -70,7 +70,7 @@ describe Projects::PropagateServiceTemplate do
expect(project.pushover_service.properties).to eq(service_template.properties) expect(project.pushover_service.properties).to eq(service_template.properties)
end end
describe 'bulk update' do describe 'bulk update', :use_sql_query_cache do
let(:project_total) { 5 } let(:project_total) { 5 }
before do before do

Some files were not shown because too many files have changed in this diff Show more