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:
- local: /lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml
@ -655,7 +655,7 @@ gitlab:setup-mysql:
# Frontend-related jobs
gitlab:assets:compile:
<<: *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:
- setup-test-env
services:

View file

@ -2,6 +2,53 @@
documentation](doc/development/changelog.md) for instructions on adding your own
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)
### 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
# Gitaly GRPC client
gem 'gitaly-proto', '~> 1.19.0', require: 'gitaly'
gem 'gitaly-proto', '~> 1.22.1', require: 'gitaly'
gem 'grpc', '~> 1.15.0'

View file

@ -281,7 +281,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gitaly-proto (1.19.0)
gitaly-proto (1.22.1)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-default_value_for (3.1.1)
@ -1020,7 +1020,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 1.19.0)
gitaly-proto (~> 1.22.1)
github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1)
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
self.addEventListener('message', e => {
const { data } = e;
if (data === undefined) {
return;
}
const { treeEntries, tree } = generateTreeList(data);
// eslint-disable-next-line no-restricted-globals

View file

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

View file

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

View file

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

View file

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

View file

@ -41,7 +41,7 @@ module IssuableCollections
return if pagination_disabled?
@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
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables

View file

@ -11,7 +11,7 @@ module IssuableCollectionsAction
.non_archived
.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|
format.html
@ -22,7 +22,7 @@ module IssuableCollectionsAction
def merge_requests
@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
# 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
# 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]
when 'commit'
create_params[:commit_id] = params[:target_id]
when 'merge_request'
create_params[:noteable_id] = params[:target_id]
case noteable
when Commit
create_params[:commit_id] = noteable.id
when MergeRequest
create_params[:noteable_id] = noteable.id
# Notes on MergeRequest can have an extra `commit_id` context
create_params[:commit_id] = params.dig(:note, :commit_id)
else
create_params[:noteable_id] = params[:target_id]
create_params[:noteable_id] = noteable.id
end
end
end

View file

@ -13,6 +13,11 @@ class Projects::ApplicationController < ApplicationController
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
def project

View file

@ -1,7 +1,9 @@
# frozen_string_literal: true
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
template = @template_type.find(params[:key], project)
@ -13,9 +15,20 @@ class Projects::TemplatesController < Projects::ApplicationController
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
template_types = { issue: Gitlab::Template::IssueTemplate, merge_request: Gitlab::Template::MergeRequestTemplate }.with_indifferent_access
@template_type = template_types[params[:template_type]]
render json: [], status: :not_found unless @template_type
end
end

View file

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

View file

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

View file

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

View file

@ -41,7 +41,11 @@ class UploadsController < ApplicationController
when Note
can?(current_user, :read_project, model.project)
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
true
else
@ -56,8 +60,13 @@ class UploadsController < ApplicationController
def authorize_create_access!
return unless model
# for now we support only personal snippets comments
authorized = can?(current_user, :comment_personal_snippet, model)
authorized =
case model
when User
can?(current_user, :update_user, model)
else
can?(current_user, :comment_personal_snippet, model)
end
render_unauthorized unless authorized
end
@ -74,6 +83,10 @@ class UploadsController < ApplicationController
User === model || Appearance === model
end
def secret?
params[:secret].present?
end
def upload_model_class
MODEL_CLASSES[params[:model]] || raise(UnknownUploadModelError)
end

View file

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

View file

@ -48,9 +48,9 @@ class IssuesFinder < IssuableFinder
OR (issues.confidential = TRUE
AND (issues.author_id = :user_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,
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
# rubocop: enable CodeReuse/ActiveRecord

View file

@ -62,7 +62,7 @@ class ProjectsFinder < UnionFinder
collection = by_personal(collection)
collection = by_starred(collection)
collection = by_trending(collection)
collection = by_visibilty_level(collection)
collection = by_visibility_level(collection)
collection = by_tags(collection)
collection = by_search(collection)
collection = by_archived(collection)
@ -71,12 +71,11 @@ class ProjectsFinder < UnionFinder
collection
end
# rubocop: disable CodeReuse/ActiveRecord
def collection_with_user
if owned_projects?
current_user.owned_projects
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
if private_only?
current_user.authorized_projects
@ -85,7 +84,6 @@ class ProjectsFinder < UnionFinder
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
# Builds a collection for an anonymous user.
def collection_without_user
@ -131,7 +129,7 @@ class ProjectsFinder < UnionFinder
end
# 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
end
# rubocop: enable CodeReuse/ActiveRecord

View file

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

View file

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

View file

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

View file

@ -277,7 +277,7 @@ module IssuablesHelper
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)
data[:groupPath] = parent.path

View file

@ -1,6 +1,16 @@
# frozen_string_literal: true
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)
if snippet.project_id?
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
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
# 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
cache_markdown_field :title, pipeline: :single_line

View file

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

View file

@ -446,10 +446,10 @@ class Member < ApplicationRecord
end
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 }
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

View file

@ -7,11 +7,38 @@ class MergeRequestsClosingIssues < ApplicationRecord
validates :merge_request_id, uniqueness: { scope: :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
def count_for_collection(ids)
group(:issue_id)
.where(issue_id: ids)
.pluck('issue_id', 'COUNT(*) as count')
def count_for_collection(ids, current_user)
closing_merge_requests(ids, current_user).group(:issue_id).pluck('issue_id', 'COUNT(*) as count')
end
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

View file

@ -461,10 +461,12 @@ class Project < ApplicationRecord
# Returns a collection of projects that is either public or visible to the
# 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
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))
else
public_to_user
@ -474,30 +476,32 @@ class Project < ApplicationRecord
# project features may be "disabled", "internal", "enabled" or "public". If "internal",
# 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.
# 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
# logged in users to more efficiently get private projects with the given
# feature.
def self.with_feature_available_for_user(feature, user)
visible = [ProjectFeature::ENABLED, ProjectFeature::PUBLIC]
min_access_level = ProjectFeature.required_minimum_access_level(feature)
if user&.admin?
with_feature_enabled(feature)
elsif user
min_access_level = ProjectFeature.required_minimum_access_level(feature)
column = ProjectFeature.quoted_access_level_column(feature)
with_project_feature
.where(
"(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: ProjectFeature::ENABLED,
private_visible: ProjectFeature::PRIVATE,
authorizations: user.authorizations_for_projects(min_access_level: min_access_level)
})
.where("#{column} IS NULL OR #{column} IN (:public_visible) OR (#{column} = :private_visible AND EXISTS (:authorizations))",
{
public_visible: visible,
private_visible: ProjectFeature::PRIVATE,
authorizations: user.authorizations_for_projects(min_access_level: min_access_level)
})
else
# This has to be added to include features whose value is nil in the db
visible << nil
with_feature_access_level(feature, visible)
end
end

View file

@ -761,11 +761,15 @@ class User < ApplicationRecord
# Typically used in conjunction with projects table to get projects
# 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:
# `Project.where('EXISTS(?)', user.authorizations_for_projects)`
def authorizations_for_projects(min_access_level: nil)
authorizations = project_authorizations.select(1).where('project_authorizations.project_id = projects.id')
def authorizations_for_projects(min_access_level: nil, related_project_column: 'projects.id')
authorizations = project_authorizations
.select(1)
.where("project_authorizations.project_id = #{related_project_column}")
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
include Gitlab::Utils::StrongMemoize
DEFAULT_GIT_DEPTH_MERGE_REQUEST = 10
RUNNER_REMOTE_TAG_PREFIX = 'refs/tags/'.freeze
RUNNER_REMOTE_BRANCH_PREFIX = 'refs/remotes/origin/'.freeze
@ -28,7 +27,6 @@ module Ci
def git_depth
strong_memoize(:git_depth) do
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
end
end
@ -39,12 +37,13 @@ module Ci
if git_depth > 0
specs << refspec_for_branch(ref) if branch? || legacy_detached_merge_request_pipeline?
specs << refspec_for_tag(ref) if tag?
specs << refspec_for_merge_request_ref if merge_request_ref?
else
specs << refspec_for_branch
specs << refspec_for_tag
end
specs << refspec_for_merge_request_ref if merge_request_ref?
specs
end

View file

@ -24,7 +24,7 @@ module Lfs
def new_file(file_path, file_content, encoding: nil)
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_object = create_lfs_object!(lfs_pointer_file, file_content)
@ -66,5 +66,12 @@ module Lfs
def link_lfs_object!(lfs_object)
project.lfs_objects << lfs_object
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

View file

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

View file

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

View file

@ -1,22 +1,29 @@
# frozen_string_literal: true
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
@file_name = File.basename(file_path)
@model = model
@from_model = from_model
@to_model = to_model
@update_field = update_field
end
def execute
temp_file_uploader.retrieve_from_store!(file_name)
return unless valid?
uploader.retrieve_from_store!(file_name)
move
if update_markdown
uploader.record_upload
update_upload_model
uploader.schedule_background_upload
end
end
@ -24,52 +31,77 @@ class FileMover
private
def valid?
Pathname.new(temp_file_path).realpath.to_path.start_with?(
(Pathname(temp_file_uploader.root) + temp_file_uploader.base_dir).to_path
)
if temp_file_uploader.file_storage?
Pathname.new(temp_file_path).realpath.to_path.start_with?(
(Pathname(temp_file_uploader.root) + temp_file_uploader.base_dir).to_path
)
else
temp_file_uploader.exists?
end
end
def move
FileUtils.mkdir_p(File.dirname(file_path))
FileUtils.move(temp_file_path, file_path)
if temp_file_uploader.file_storage?
FileUtils.mkdir_p(File.dirname(file_path))
FileUtils.move(temp_file_path, file_path)
else
uploader.copy_file(temp_file_uploader.file)
temp_file_uploader.upload.destroy!
end
end
def update_markdown
updated_text = model.read_attribute(update_field)
.gsub(temp_file_uploader.markdown_link, uploader.markdown_link)
model.update_attribute(update_field, updated_text)
updated_text = to_model.read_attribute(update_field)
.gsub(temp_file_uploader.markdown_link, uploader.markdown_link)
to_model.update_attribute(update_field, updated_text)
rescue
revert
false
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
return @temp_file_path if @temp_file_path
temp_file_uploader.retrieve_from_store!(file_name)
@temp_file_path = temp_file_uploader.file.path
strong_memoize(:temp_file_path) do
temp_file_uploader.file.path
end
end
def file_path
return @file_path if @file_path
uploader.retrieve_from_store!(file_name)
@file_path = uploader.file.path
strong_memoize(:file_path) do
uploader.file.path
end
end
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
def temp_file_uploader
@temp_file_uploader ||= PersonalFileUploader.new(nil, secret: secret)
@temp_file_uploader ||= PersonalFileUploader.new(from_model, secret: secret)
end
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}")
FileUtils.move(file_path, temp_file_path)
if temp_file_uploader.file_storage?
FileUtils.move(file_path, temp_file_path)
end
end
end

View file

@ -12,7 +12,7 @@
# end
#
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)
unless value =~ PATTERN

View file

@ -1,9 +1,10 @@
- header_title _("Snippets"), snippets_path
- snippets_upload_path = snippets_upload_path(@snippet, current_user)
- content_for :page_specific_javascripts do
- if @snippet && current_user
- if snippets_upload_path
-# haml-lint:disable InlineJavaScript
:javascript
window.uploads_path = "#{upload_path('personal_snippet', id: @snippet.id)}";
window.uploads_path = "#{snippets_upload_path}";
= 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')
#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)
#related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } }

View file

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

View file

@ -493,9 +493,9 @@ module API
expose :state, :created_at, :updated_at
# 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 || subject).public_send(method) # rubocop: disable GitlabSecurity/PublicSend
(cached_subject || subject).public_send(method, *args) # rubocop: disable GitlabSecurity/PublicSend
end
end
@ -554,7 +554,7 @@ module API
end
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(:downvotes) { |issue, options| issuable_metadata(issue, options, :downvotes) }
expose :due_date
@ -731,7 +731,9 @@ module API
merge_request.metrics&.pipeline
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

View file

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

View file

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

View file

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

View file

@ -65,7 +65,7 @@ module API
next unless collection
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

View file

@ -100,7 +100,7 @@ module Banzai
end
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])
nested_path = build_relative_path(path, request_path)
file_exists?(nested_path) ? nested_path : path

View file

@ -15,6 +15,9 @@ module Gitlab
@global = Entry::Global.new(@config)
@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,
Extendable::ExtensionError,
External::Processor::IncludeError => e

View file

@ -4,6 +4,13 @@ module Gitlab
module Config
module Loader
class Yaml
DataTooLargeError = Class.new(Loader::FormatError)
include Gitlab::Utils::StrongMemoize
MAX_YAML_SIZE = 1.megabyte
MAX_YAML_DEPTH = 100
def initialize(config)
@config = YAML.safe_load(config, [Symbol], [], true)
rescue Psych::Exception => e
@ -11,16 +18,35 @@ module Gitlab
end
def valid?
@config.is_a?(Hash)
hash? && !too_big?
end
def load!
unless valid?
raise Loader::FormatError, 'Invalid configuration format'
end
raise DataTooLargeError, 'The parsed YAML is too big' if too_big?
raise Loader::FormatError, 'Invalid configuration format' unless hash?
@config.deep_symbolize_keys
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

View file

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

View file

@ -905,6 +905,12 @@ module Gitlab
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)
remove_foreign_key(*args)
rescue ArgumentError

View file

@ -303,6 +303,11 @@ module Gitlab
(size.to_f / 1024).round(2)
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.
#
# Usage.

View file

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

View file

@ -47,6 +47,13 @@ module Gitlab
response.size
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)
request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: encode_binary(revision))
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)
end
type = type.unwrap if type.kind.non_null?
Array.wrap(type.metadata[:authorize])
end

View file

@ -2,6 +2,8 @@
module Gitlab
class GroupSearchResults < SearchResults
attr_reader :group
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)
@ -26,5 +28,9 @@ module Gitlab
.where(id: groups.select('members.user_id'))
end
# rubocop:enable CodeReuse/ActiveRecord
def issuable_params
super.merge(group_id: group.id)
end
end
end

View file

@ -2,7 +2,7 @@
module Gitlab
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.
if !(issuable_collection.singleton_class < ActiveRecord::NullRelation) &&
issuable_collection.respond_to?(:limit_value) &&
@ -23,7 +23,7 @@ module Gitlab
issuable_votes_count = ::AwardEmoji.votes_for_collection(issuable_ids, collection_type)
issuable_merge_requests_count =
if collection_type == 'Issue'
::MergeRequestsClosingIssues.count_for_collection(issuable_ids)
::MergeRequestsClosingIssues.count_for_collection(issuable_ids, user)
else
[]
end

View file

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

View file

@ -2,6 +2,8 @@
module Gitlab
class SearchResults
COUNT_LIMIT = 1001
attr_reader :current_user, :query, :per_page
# Limit search results by passed projects
@ -25,29 +27,26 @@ module Gitlab
def objects(scope, page = nil, without_count = true)
collection = case scope
when 'projects'
projects.page(page).per(per_page)
projects
when 'issues'
issues.page(page).per(per_page)
issues
when 'merge_requests'
merge_requests.page(page).per(per_page)
merge_requests
when 'milestones'
milestones.page(page).per(per_page)
milestones
when 'users'
users.page(page).per(per_page)
users
else
Kaminari.paginate_array([]).page(page).per(per_page)
end
Kaminari.paginate_array([])
end.page(page).per(per_page)
without_count ? collection.without_count : collection
end
# rubocop: disable CodeReuse/ActiveRecord
def limited_projects_count
@limited_projects_count ||= projects.limit(count_limit).count
@limited_projects_count ||= limited_count(projects)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def 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.
# It's faster to try to fetch all public issues first, then only
# if necessary try to fetch all issues.
sum = issues(public_only: true).limit(count_limit).count
@limited_issues_count = sum < count_limit ? issues.limit(count_limit).count : sum
sum = limited_count(issues(public_only: true))
@limited_issues_count = sum < count_limit ? limited_count(issues) : sum
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def limited_merge_requests_count
@limited_merge_requests_count ||= merge_requests.limit(count_limit).count
@limited_merge_requests_count ||= limited_count(merge_requests)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def limited_milestones_count
@limited_milestones_count ||= milestones.limit(count_limit).count
@limited_milestones_count ||= limited_count(milestones)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop:disable CodeReuse/ActiveRecord
def limited_users_count
@limited_users_count ||= users.limit(count_limit).count
@limited_users_count ||= limited_count(users)
end
# rubocop:enable CodeReuse/ActiveRecord
def single_commit_result?
false
end
def count_limit
1001
COUNT_LIMIT
end
def users
@ -99,23 +91,15 @@ module Gitlab
limit_projects.search(query)
end
# rubocop: disable CodeReuse/ActiveRecord
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
issues = issues.where(project_id: project_ids_relation)
issues = issues.where(project_id: project_ids_relation) # rubocop: disable CodeReuse/ActiveRecord
end
issues =
if query =~ /#(\d+)\z/
issues.where(iid: $1)
else
issues.full_search(query)
end
issues.reorder('updated_at DESC')
issues
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def milestones
@ -127,23 +111,15 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def merge_requests
merge_requests = MergeRequestsFinder.new(current_user).execute
merge_requests = MergeRequestsFinder.new(current_user, issuable_params).execute
unless default_project_filter
merge_requests = merge_requests.in_projects(project_ids_relation)
end
merge_requests =
if query =~ /[#!](\d+)\z/
merge_requests.where(iid: $1)
else
merge_requests.full_search(query)
end
merge_requests.reorder('updated_at DESC')
merge_requests
end
# rubocop: enable CodeReuse/ActiveRecord
def default_scope
'projects'
@ -174,5 +150,23 @@ module Gitlab
limit_projects.select(:id).reorder(nil)
end
# 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

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"
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 ""
msgid "show less"

View file

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

View file

@ -1,49 +1,101 @@
require 'spec_helper'
describe Projects::TemplatesController do
let(:project) { create(:project, :repository) }
let(:project) { create(:project, :repository, :private) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:file_path_1) { '.gitlab/issue_templates/bug.md' }
let(:file_path_1) { '.gitlab/issue_templates/issue_template.md' }
let(:file_path_2) { '.gitlab/merge_request_templates/merge_request_template.md' }
let(:body) { JSON.parse(response.body) }
before do
project.add_developer(user)
sign_in(user)
end
before do
project.add_user(user, Gitlab::Access::MAINTAINER)
project.repository.create_file(user, file_path_1, 'something valid',
message: 'test 3', branch_name: 'master')
end
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
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)
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("bug")
expect(body["content"]).to eq("something valid")
expect(response.status).to eq(200)
expect(body['name']).to eq('issue_template')
expect(body['content']).to eq('issue content')
end
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)
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(404)
expect(response.status).to eq(200)
expect(body['name']).to eq('merge_request_template')
expect(body['content']).to eq('merge request content')
end
end
it 'renders 404 when template type is not found' do
sign_in(user)
get(:show, params: { namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project }, format: :json)
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)
expect(response.status).to eq(404)
end
end
it 'renders 404 without errors' do
sign_in(user)
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
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
project.add_developer(user)
sign_in(user)
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
project.add_guest(user)
sign_in(user)
end
include_examples 'renders issue templates as json'
include_examples 'renders 404 when requesting a merge request template'
include_examples 'renders 404 when params are invalid'
end
end
end

View file

@ -117,6 +117,119 @@ describe Snippets::NotesController do
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
let(:request_params) do
{

View file

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

View file

@ -22,121 +22,160 @@ describe UploadsController do
let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
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(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
context 'when a user does not have permissions to upload a file' do
it "returns 401 when the user is not logged in" do
post :create, params: { model: model, id: snippet.id }, format: :json
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
it "returns 401 when the user is not logged in" do
post :create, params: { model: model, id: snippet.id }, format: :json
expect(response).to have_gitlab_http_status(401)
end
it "returns 404 when user can't comment on a snippet" do
private_snippet = create(:personal_snippet, :private)
sign_in(user)
post :create, params: { model: model, id: private_snippet.id }, format: :json
expect(response).to have_gitlab_http_status(404)
end
end
context 'when a user is logged in' do
before do
sign_in(user)
end
it "returns an error without file" do
post :create, params: { model: model, id: snippet.id }, format: :json
expect(response).to have_gitlab_http_status(422)
end
it "returns an error with invalid model" do
expect { post :create, params: { model: 'invalid', id: snippet.id }, format: :json }
.to raise_error(ActionController::UrlGenerationError)
end
it "returns 404 status when object not found" do
post :create, params: { model: model, id: 9999 }, format: :json
expect(response).to have_gitlab_http_status(404)
end
context 'with valid image' do
before do
post :create, params: { model: 'personal_snippet', id: snippet.id, file: jpg }, format: :json
end
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"rails_sample\"'
expect(response.body).to match "\"url\":\"/uploads"
end
it 'creates a corresponding Upload record' do
upload = Upload.last
aggregate_failures do
expect(upload).to exist
expect(upload.model).to eq snippet
end
end
end
context 'with valid non-image file' do
before do
post :create, params: { model: 'personal_snippet', id: snippet.id, file: txt }, format: :json
end
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
expect(response.body).to match "\"url\":\"/uploads"
end
it 'creates a corresponding Upload record' do
upload = Upload.last
aggregate_failures do
expect(upload).to exist
expect(upload.model).to eq snippet
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
it "returns 404 when user can't comment on a snippet" do
private_snippet = create(:personal_snippet, :private)
sign_in(user)
post :create, params: { model: model, id: private_snippet.id }, format: :json
expect(response).to have_gitlab_http_status(404)
end
end
context 'when a user is logged in' do
before do
sign_in(user)
end
it "returns an error without file" do
post :create, params: { model: model, id: snippet.id }, format: :json
expect(response).to have_gitlab_http_status(422)
end
it "returns an error with invalid model" do
expect { post :create, params: { model: 'invalid', id: snippet.id }, format: :json }
.to raise_error(ActionController::UrlGenerationError)
end
it "returns 404 status when object not found" do
post :create, params: { model: model, id: 9999 }, format: :json
expect(response).to have_gitlab_http_status(404)
end
context 'with valid image' do
context 'when user is logged in' do
before do
post :create, params: { model: 'personal_snippet', id: snippet.id, file: jpg }, format: :json
sign_in(user)
end
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"rails_sample\"'
expect(response.body).to match "\"url\":\"/uploads"
end
it 'creates a corresponding Upload record' do
upload = Upload.last
aggregate_failures do
expect(upload).to exist
expect(upload.model).to eq snippet
end
end
end
context 'with valid non-image file' do
before do
post :create, params: { model: 'personal_snippet', id: snippet.id, file: txt }, format: :json
end
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
expect(response.body).to match "\"url\":\"/uploads"
end
it 'creates a corresponding Upload record' do
upload = Upload.last
aggregate_failures do
expect(upload).to exist
expect(upload.model).to eq snippet
end
end
end
context 'temporal with valid image' 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
it 'returns a content with original filename, new link, and correct type.' do
subject
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
it 'does not create an Upload record' do
expect { subject }.not_to change { Upload.count }
end
end
it 'creates a corresponding Upload record' do
expect { subject }.to change { Upload.count }
context 'temporal with valid non-image file' do
subject do
post :create, params: { model: 'personal_snippet', file: txt }, format: :json
upload = Upload.last
aggregate_failures do
expect(upload).to exist
expect(upload.model).to eq user
end
end
it 'returns a content with original filename, new link, and correct type.' do
subject
context 'with valid non-image file' do
subject do
post :create, params: { model: model, id: user.id, file: txt }, format: :json
end
expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
expect(response.body).to match "\"url\":\"/uploads/-/system/temp"
it 'returns a content with original filename, new link, and correct type.' do
subject
expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
expect(response.body).to match "\"url\":\"/uploads/-/system/user/#{user.id}/"
end
it 'creates a corresponding Upload record' do
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 'does not create an Upload record' do
expect { subject }.not_to change { Upload.count }
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

View file

@ -260,6 +260,7 @@ FactoryBot.define do
trait(:merge_requests_enabled) { merge_requests_access_level ProjectFeature::ENABLED }
trait(:merge_requests_disabled) { merge_requests_access_level ProjectFeature::DISABLED }
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_disabled) { repository_access_level ProjectFeature::DISABLED }
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')
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) }
expect(reqs.first.status_code).to eq(200)

View file

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

View file

@ -31,7 +31,7 @@ describe MergeRequestsFinder do
end
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.add_guest(user)
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
it { expect(described_class.graphql_name).to eq('Metadata') }
it { is_expected.to require_graphql_authorizations(:read_instance_metadata) }
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_resolver(Resolvers::MetadataResolver)
end
it 'authorizes with read_instance_metadata' do
is_expected.to require_graphql_authorizations(:read_instance_metadata)
end
end
end

View file

@ -5,6 +5,40 @@ describe API::Helpers::RelatedResourcesHelpers do
Class.new.include(described_class).new
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
let(:path) { '/api/v4/awesome_endpoint' }
subject(:url) { helpers.expose_url(path) }

View file

@ -83,6 +83,11 @@ describe Banzai::Filter::RelativeLinkFilter do
expect { filter(act) }.not_to raise_error
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
act = link("/uploads/d18213acd3732630991986120e167e3d/Landscape_8.jpg \nBut here's some more unexpected text :smile:)")
expect { filter(act) }.not_to raise_error

View file

@ -90,6 +90,27 @@ describe Gitlab::Ci::Config do
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
let(:yml) { 'before_script: "ls"' }

View file

@ -8,7 +8,7 @@ describe Gitlab::Config::Loader::Yaml do
describe '#valid?' do
it 'returns true' do
expect(loader.valid?).to be true
expect(loader).to be_valid
end
end
@ -24,7 +24,7 @@ describe Gitlab::Config::Loader::Yaml do
describe '#valid?' do
it 'returns false' do
expect(loader.valid?).to be false
expect(loader).not_to be_valid
end
end
@ -43,7 +43,10 @@ describe Gitlab::Config::Loader::Yaml do
describe '#initialize' 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
@ -53,7 +56,68 @@ describe Gitlab::Config::Loader::Yaml do
describe '#valid?' 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

View file

@ -50,5 +50,14 @@ describe Gitlab::DataBuilder::Pipeline do
it { expect(attributes[:variables]).to be_a(Array) }
it { expect(attributes[:variables]).to contain_exactly({ key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1' }) }
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

View file

@ -215,6 +215,18 @@ describe Gitlab::Git::Repository, :seed_helper do
it { is_expected.to be < 2 }
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
it { expect(repository).not_to be_empty }
end

View file

@ -19,7 +19,9 @@ describe Gitlab::Git::Tree, :seed_helper do
it 'returns a list of tree objects' do
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))
end

View file

@ -73,6 +73,17 @@ describe Gitlab::GitalyClient::RepositoryService do
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
let(:revision) { 'master' }

View file

@ -7,35 +7,39 @@ require 'spec_helper'
describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
def type(type_authorizations = [])
Class.new(Types::BaseObject) do
graphql_name "TestType"
graphql_name 'TestType'
authorize type_authorizations
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
graphql_name "TestTypeWithField"
field :test_field, field_type, null: true, authorize: field_authorizations, resolve: -> (_, _, _) { resolved_value}
graphql_name 'TestTypeWithField'
options.reverse_merge!(null: true)
field :test_field, field_type,
authorize: field_authorizations,
resolve: -> (_, _, _) { resolved_value },
**options
end
end
let(:current_user) { double(:current_user) }
subject(:service) { described_class.new(field) }
describe "#authorized_resolve" do
let(:presented_object) { double("presented object") }
let(:presented_type) { double("parent type", object: presented_object) }
describe '#authorized_resolve' do
let(:presented_object) { double('presented object') }
let(:presented_type) { double('parent type', object: presented_object) }
subject(:resolved) { service.authorized_resolve.call(presented_type, {}, { current_user: current_user }) }
context "scalar types" do
shared_examples "checking permissions on the presented object" do
it "checks the abilities on the object being presented and returns the value" do
context 'scalar types' do
shared_examples 'checking permissions on the presented object' do
it 'checks the abilities on the object being presented and returns the value' do
expected_permissions.each do |permission|
spy_ability_check_for(permission, presented_object, passed: true)
end
expect(resolved).to eq("Resolved value")
expect(resolved).to eq('Resolved value')
end
it "returns nil if the value wasn't authorized" do
@ -45,47 +49,57 @@ describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
end
end
context "when the field is a scalar type" do
let(:field) { type_with_field(GraphQL::STRING_TYPE, :read_field).fields["testField"].to_graphql }
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(:expected_permissions) { [:read_field] }
it_behaves_like "checking permissions on the presented object"
it_behaves_like 'checking permissions on the presented object'
end
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 }
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(: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 specific type" do
context 'when the field is a specific type' do
let(:custom_type) { type(:read_type) }
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(:object_in_field) { double('presented in field') }
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_type, object_in_field, passed: true)
expect(resolved).to eq(object_in_field)
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_type, object_in_field, passed: true)
expect(resolved).to be_nil
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 }
context 'when the field is not nullable' do
let(:field) { type_with_field(custom_type, [], object_in_field, null: false).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 }
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)
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 }
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 }
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
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
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) }
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[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") }
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[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
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
child_member.access_level = GroupMember::MAINTAINER

View file

@ -3188,61 +3188,105 @@ describe Project do
end
describe '.with_feature_available_for_user' do
let!(:user) { create(:user) }
let!(:feature) { MergeRequest }
let!(:project) { create(:project, :public, :merge_requests_enabled) }
let(:user) { create(:user) }
let(:feature) { MergeRequest }
subject { described_class.with_feature_available_for_user(feature, user) }
context 'when user has access to project' do
subject { described_class.with_feature_available_for_user(feature, user) }
shared_examples 'feature disabled' do
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
project.add_guest(user)
end
context 'when public project' do
context 'when feature is public' do
it 'returns project' do
is_expected.to include(project)
end
end
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) }
context 'when feature is private' do
let(:project) { create(:project, :public, :merge_requests_private) }
it 'returns project when user has access to the feature' do
project.add_maintainer(user)
is_expected.to include(project)
end
it 'does not return project when user does not have the minimum access level required' do
context 'when user does not has access to the feature' do
it 'does not return projects with the project feature private' do
is_expected.not_to include(project)
end
end
end
context 'when private project' do
let!(:project) { create(:project) }
context 'when user has access to the feature' do
it 'returns projects with the project feature private' do
project.add_reporter(user)
it 'returns project when user has access to the feature' do
project.add_maintainer(user)
is_expected.to include(project)
end
it 'does not return project when user does not have the minimum access level required' do
is_expected.not_to include(project)
is_expected.to include(project)
end
end
end
end
context 'when user does not have access to project' do
let!(:project) { create(:project) }
context 'user is an admin' do
let(:user) { create(:user, :admin) }
it 'does not return project when user cant access project' do
is_expected.not_to include(project)
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 'returns projects with the project feature private' do
is_expected.to include(project)
end
end
end
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)
end
end
end
end

View file

@ -136,24 +136,6 @@ describe Ci::BuildRunnerPresenter do
is_expected.to eq(1)
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
describe '#refspecs' do
@ -191,7 +173,9 @@ describe Ci::BuildRunnerPresenter do
it 'returns the correct refspecs' do
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
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 }
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
it 'creates the member if group level is lower', :nested_groups do

View file

@ -830,6 +830,31 @@ describe API::MergeRequests do
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
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)
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 }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array

View file

@ -661,4 +661,24 @@ describe 'project routing' do
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

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')
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
let(:file_path) { 'other.filetype' }

View file

@ -13,7 +13,7 @@ describe Projects::AfterImportService do
describe '#execute' do
before do
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)
.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)
end
describe 'bulk update' do
describe 'bulk update', :use_sql_query_cache do
let(:project_total) { 5 }
before do

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