Commit c6c74378 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent d80f3cd7
# frozen_string_literal: true # frozen_string_literal: true
module InvisibleCaptcha module InvisibleCaptchaOnSignup
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
class Projects::ReleasesController < Projects::ApplicationController class Projects::ReleasesController < Projects::ApplicationController
# Authorize # Authorize
before_action :require_non_empty_project, except: [:index] before_action :require_non_empty_project, except: [:index]
before_action :release, only: %i[edit show update] before_action :release, only: %i[edit show update downloads]
before_action :authorize_read_release! before_action :authorize_read_release!
before_action do before_action do
push_frontend_feature_flag(:release_issue_summary, project) push_frontend_feature_flag(:release_issue_summary, project)
...@@ -40,6 +40,10 @@ class Projects::ReleasesController < Projects::ApplicationController ...@@ -40,6 +40,10 @@ class Projects::ReleasesController < Projects::ApplicationController
end end
end end
def downloads
redirect_to link.url
end
protected protected
def releases def releases
...@@ -69,6 +73,14 @@ class Projects::ReleasesController < Projects::ApplicationController ...@@ -69,6 +73,14 @@ class Projects::ReleasesController < Projects::ApplicationController
@release ||= project.releases.find_by_tag!(sanitized_tag_name) @release ||= project.releases.find_by_tag!(sanitized_tag_name)
end end
def link
release.links.find_by_filepath!(sanitized_filepath)
end
def sanitized_filepath
CGI.unescape(params[:filepath])
end
def sanitized_tag_name def sanitized_tag_name
CGI.unescape(params[:tag]) CGI.unescape(params[:tag])
end end
......
...@@ -4,7 +4,7 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -4,7 +4,7 @@ class RegistrationsController < Devise::RegistrationsController
include Recaptcha::Verify include Recaptcha::Verify
include AcceptsPendingInvitations include AcceptsPendingInvitations
include RecaptchaExperimentHelper include RecaptchaExperimentHelper
include InvisibleCaptcha include InvisibleCaptchaOnSignup
layout :choose_layout layout :choose_layout
......
...@@ -17,12 +17,12 @@ module Mutations ...@@ -17,12 +17,12 @@ module Mutations
argument :due_date, argument :due_date,
Types::TimeType, Types::TimeType,
required: true, required: false,
description: copy_field_description(Types::IssueType, :due_date) description: copy_field_description(Types::IssueType, :due_date)
argument :confidential, argument :confidential,
GraphQL::BOOLEAN_TYPE, GraphQL::BOOLEAN_TYPE,
required: true, required: false,
description: copy_field_description(Types::IssueType, :confidential) description: copy_field_description(Types::IssueType, :confidential)
def resolve(project_path:, iid:, **args) def resolve(project_path:, iid:, **args)
......
...@@ -10,8 +10,9 @@ class MergeRequestDiffCommit < ApplicationRecord ...@@ -10,8 +10,9 @@ class MergeRequestDiffCommit < ApplicationRecord
sha_attribute :sha sha_attribute :sha
alias_attribute :id, :sha alias_attribute :id, :sha
# Deprecated; use `bulk_insert!` from `BulkInsertSafe` mixin instead.
# cf. https://gitlab.com/gitlab-org/gitlab/issues/207989 for progress
def self.create_bulk(merge_request_diff_id, commits) def self.create_bulk(merge_request_diff_id, commits)
warn 'Deprecated; use `bulk_insert` from `BulkInsertSafe` mixin instead'
rows = commits.map.with_index do |commit, index| rows = commits.map.with_index do |commit, index|
# See #parent_ids. # See #parent_ids.
commit_hash = commit.to_hash.except(:parent_ids) commit_hash = commit.to_hash.except(:parent_ids)
......
...@@ -28,7 +28,7 @@ class PostReceiveService ...@@ -28,7 +28,7 @@ class PostReceiveService
response.add_alert_message(message) response.add_alert_message(message)
end end
broadcast_message = BroadcastMessage.current&.last&.message broadcast_message = BroadcastMessage.current_banner_messages&.last&.message
response.add_alert_message(broadcast_message) response.add_alert_message(broadcast_message)
response.add_merge_request_urls(merge_request_urls) response.add_merge_request_urls(merge_request_urls)
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
- if note.emoji_awardable? - if note.emoji_awardable?
.note-actions-item .note-actions-item
= link_to '#', title: _('Add reaction'), class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do = link_to '#', title: _('Add reaction'), class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do
= icon('spinner spin')
%span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile') %span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile')
%span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley') %span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley')
%span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile') %span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile')
......
---
title: Add trigram index on snippet description
merge_request: 26341
author:
type: performance
---
title: Add filepath redirect url
merge_request: 25541
author:
type: added
---
title: Remove .fa-spinner from app/views/snippets/notes
merge_request: 25036
author: nuwe1
type: other
---
title: Limit notification-type broadcast display to web interface
merge_request: 26236
author: Aleksandrs Ļedovskis
type: changed
---
title: Add grape custom validator for sha params
merge_request: 26220
author: Rajendra Kadam
type: added
...@@ -171,6 +171,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -171,6 +171,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :releases, only: [:index, :show, :edit], param: :tag, constraints: { tag: %r{[^/]+} } do resources :releases, only: [:index, :show, :edit], param: :tag, constraints: { tag: %r{[^/]+} } do
member do member do
get :evidence get :evidence
get :downloads, path: 'downloads/*filepath', format: false
end end
end end
......
# frozen_string_literal: true
class AddIndexOnSnippetDescription < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'index_snippets_on_description_trigram'
disable_ddl_transaction!
def up
add_concurrent_index :snippets, :description, name: INDEX_NAME, using: :gin, opclass: { description: :gin_trgm_ops }
end
def down
remove_concurrent_index_by_name :snippets, INDEX_NAME
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_02_27_165129) do ActiveRecord::Schema.define(version: 2020_03_03_074328) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm" enable_extension "pg_trgm"
...@@ -3964,6 +3964,7 @@ ActiveRecord::Schema.define(version: 2020_02_27_165129) do ...@@ -3964,6 +3964,7 @@ ActiveRecord::Schema.define(version: 2020_02_27_165129) do
t.index ["author_id"], name: "index_snippets_on_author_id" t.index ["author_id"], name: "index_snippets_on_author_id"
t.index ["content"], name: "index_snippets_on_content_trigram", opclass: :gin_trgm_ops, using: :gin t.index ["content"], name: "index_snippets_on_content_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["created_at"], name: "index_snippets_on_created_at" t.index ["created_at"], name: "index_snippets_on_created_at"
t.index ["description"], name: "index_snippets_on_description_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["file_name"], name: "index_snippets_on_file_name_trigram", opclass: :gin_trgm_ops, using: :gin t.index ["file_name"], name: "index_snippets_on_file_name_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["project_id", "visibility_level"], name: "index_snippets_on_project_id_and_visibility_level" t.index ["project_id", "visibility_level"], name: "index_snippets_on_project_id_and_visibility_level"
t.index ["title"], name: "index_snippets_on_title_trigram", opclass: :gin_trgm_ops, using: :gin t.index ["title"], name: "index_snippets_on_title_trigram", opclass: :gin_trgm_ops, using: :gin
......
...@@ -107,6 +107,7 @@ recorded: ...@@ -107,6 +107,7 @@ recorded:
- User was deleted ([introduced](https://gitlab.com/gitlab-org/gitlab/issues/251) in GitLab 12.8) - User was deleted ([introduced](https://gitlab.com/gitlab-org/gitlab/issues/251) in GitLab 12.8)
- User was added ([introduced](https://gitlab.com/gitlab-org/gitlab/issues/251) in GitLab 12.8) - User was added ([introduced](https://gitlab.com/gitlab-org/gitlab/issues/251) in GitLab 12.8)
- User was blocked via Admin Area ([introduced](https://gitlab.com/gitlab-org/gitlab/issues/251) in GitLab 12.8) - User was blocked via Admin Area ([introduced](https://gitlab.com/gitlab-org/gitlab/issues/251) in GitLab 12.8)
- User was blocked via API ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25872) in GitLab 12.9)
It's possible to filter particular actions by choosing an audit data type from It's possible to filter particular actions by choosing an audit data type from
the filter dropdown box. You can further filter by specific group, project, or user the filter dropdown box. You can further filter by specific group, project, or user
......
...@@ -8011,7 +8011,7 @@ input UpdateIssueInput { ...@@ -8011,7 +8011,7 @@ input UpdateIssueInput {
""" """
Indicates the issue is confidential Indicates the issue is confidential
""" """
confidential: Boolean! confidential: Boolean
""" """
Description of the issue Description of the issue
...@@ -8021,7 +8021,7 @@ input UpdateIssueInput { ...@@ -8021,7 +8021,7 @@ input UpdateIssueInput {
""" """
Due date of the issue Due date of the issue
""" """
dueDate: Time! dueDate: Time
""" """
The desired health status The desired health status
......
...@@ -21114,13 +21114,9 @@ ...@@ -21114,13 +21114,9 @@
"name": "dueDate", "name": "dueDate",
"description": "Due date of the issue", "description": "Due date of the issue",
"type": { "type": {
"kind": "NON_NULL", "kind": "SCALAR",
"name": null, "name": "Time",
"ofType": { "ofType": null
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
}, },
"defaultValue": null "defaultValue": null
}, },
...@@ -21128,13 +21124,9 @@ ...@@ -21128,13 +21124,9 @@
"name": "confidential", "name": "confidential",
"description": "Indicates the issue is confidential", "description": "Indicates the issue is confidential",
"type": { "type": {
"kind": "NON_NULL", "kind": "SCALAR",
"name": null, "name": "Boolean",
"ofType": { "ofType": null
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
}, },
"defaultValue": null "defaultValue": null
}, },
......
...@@ -1168,8 +1168,11 @@ Parameters: ...@@ -1168,8 +1168,11 @@ Parameters:
- `id` (required) - id of specified user - `id` (required) - id of specified user
Will return `201 OK` on success, `404 User Not Found` is user cannot be found or Returns:
`403 Forbidden` when trying to block an already blocked user by LDAP synchronization.
- `201 OK` on success.
- `404 User Not Found` if user cannot be found.
- `403 Forbidden` when trying to block an already blocked user by LDAP synchronization.
## Unblock user ## Unblock user
......
...@@ -14,6 +14,17 @@ module API ...@@ -14,6 +14,17 @@ module API
end end
end end
class GitSha < Grape::Validations::Base
def validate_param!(attr_name, params)
sha = params[attr_name]
return if Commit::EXACT_COMMIT_SHA_PATTERN.match?(sha)
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)],
message: "should be a valid sha"
end
end
class Absence < Grape::Validations::Base class Absence < Grape::Validations::Base
def validate_param!(attr_name, params) def validate_param!(attr_name, params)
return if params.respond_to?(:key?) && !params.key?(attr_name) return if params.respond_to?(:key?) && !params.key?(attr_name)
...@@ -50,6 +61,7 @@ module API ...@@ -50,6 +61,7 @@ module API
end end
Grape::Validations.register_validator(:file_path, ::API::Helpers::CustomValidators::FilePath) Grape::Validations.register_validator(:file_path, ::API::Helpers::CustomValidators::FilePath)
Grape::Validations.register_validator(:git_sha, ::API::Helpers::CustomValidators::GitSha)
Grape::Validations.register_validator(:absence, ::API::Helpers::CustomValidators::Absence) Grape::Validations.register_validator(:absence, ::API::Helpers::CustomValidators::Absence)
Grape::Validations.register_validator(:integer_none_any, ::API::Helpers::CustomValidators::IntegerNoneAny) Grape::Validations.register_validator(:integer_none_any, ::API::Helpers::CustomValidators::IntegerNoneAny)
Grape::Validations.register_validator(:array_none_any, ::API::Helpers::CustomValidators::ArrayNoneAny) Grape::Validations.register_validator(:array_none_any, ::API::Helpers::CustomValidators::ArrayNoneAny)
...@@ -528,11 +528,18 @@ module API ...@@ -528,11 +528,18 @@ module API
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
not_found!('User') unless user not_found!('User') unless user
if !user.ldap_blocked? if user.ldap_blocked?
user.block
else
forbidden!('LDAP blocked users cannot be modified by the API') forbidden!('LDAP blocked users cannot be modified by the API')
end end
break if user.blocked?
result = ::Users::BlockService.new(current_user).execute(user)
if result[:status] == :success
true
else
render_api_error!(result[:message], result[:http_status])
end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -13,6 +13,8 @@ require_dependency 'declarative_policy/step' ...@@ -13,6 +13,8 @@ require_dependency 'declarative_policy/step'
require_dependency 'declarative_policy/base' require_dependency 'declarative_policy/base'
module DeclarativePolicy module DeclarativePolicy
extend PreferredScope
CLASS_CACHE_MUTEX = Mutex.new CLASS_CACHE_MUTEX = Mutex.new
CLASS_CACHE_IVAR = :@__DeclarativePolicy_CLASS_CACHE CLASS_CACHE_IVAR = :@__DeclarativePolicy_CLASS_CACHE
......
# frozen_string_literal: true # frozen_string_literal: true
module DeclarativePolicy module DeclarativePolicy
PREFERRED_SCOPE_KEY = :"DeclarativePolicy.preferred_scope" module PreferredScope
PREFERRED_SCOPE_KEY = :"DeclarativePolicy.preferred_scope"
class << self
def with_preferred_scope(scope) def with_preferred_scope(scope)
Thread.current[PREFERRED_SCOPE_KEY], old_scope = scope, Thread.current[PREFERRED_SCOPE_KEY] Thread.current[PREFERRED_SCOPE_KEY], old_scope = scope, Thread.current[PREFERRED_SCOPE_KEY]
yield yield
......
...@@ -4,95 +4,97 @@ module Gitlab ...@@ -4,95 +4,97 @@ module Gitlab
module BackgroundMigration module BackgroundMigration
module UserMentions module UserMentions
module Models module Models
# == IsolatedMentionable concern module Concerns
# # == IsolatedMentionable concern
# Shortcutted for isolation version of Mentionable to be used in mentions migrations #
# # Shortcutted for isolation version of Mentionable to be used in mentions migrations
module IsolatedMentionable #
extend ::ActiveSupport::Concern module IsolatedMentionable
extend ::ActiveSupport::Concern
class_methods do
# Indicate which attributes of the Mentionable to search for GFM references. class_methods do
def attr_mentionable(attr, options = {}) # Indicate which attributes of the Mentionable to search for GFM references.
attr = attr.to_s def attr_mentionable(attr, options = {})
mentionable_attrs << [attr, options] attr = attr.to_s
mentionable_attrs << [attr, options]
end
end end
end
included do included do
# Accessor for attributes marked mentionable. # Accessor for attributes marked mentionable.
cattr_accessor :mentionable_attrs, instance_accessor: false do cattr_accessor :mentionable_attrs, instance_accessor: false do
[] []
end end
if self < Participable if self < Participable
participant -> (user, ext) { all_references(user, extractor: ext) } participant -> (user, ext) { all_references(user, extractor: ext) }
end
end end
end
def all_references(current_user = nil, extractor: nil) def all_references(current_user = nil, extractor: nil)
# Use custom extractor if it's passed in the function parameters. # Use custom extractor if it's passed in the function parameters.
if extractor if extractor
extractors[current_user] = extractor extractors[current_user] = extractor
else else
extractor = extractors[current_user] ||= ::Gitlab::ReferenceExtractor.new(project, current_user) extractor = extractors[current_user] ||= ::Gitlab::ReferenceExtractor.new(project, current_user)
extractor.reset_memoized_values extractor.reset_memoized_values
end end
self.class.mentionable_attrs.each do |attr, options| self.class.mentionable_attrs.each do |attr, options|
text = __send__(attr) # rubocop:disable GitlabSecurity/PublicSend text = __send__(attr) # rubocop:disable GitlabSecurity/PublicSend
options = options.merge( options = options.merge(
cache_key: [self, attr], cache_key: [self, attr],
author: author, author: author,
skip_project_check: skip_project_check? skip_project_check: skip_project_check?
).merge(mentionable_params) ).merge(mentionable_params)
cached_html = self.try(:updated_cached_html_for, attr.to_sym) cached_html = self.try(:updated_cached_html_for, attr.to_sym)
options[:rendered] = cached_html if cached_html options[:rendered] = cached_html if cached_html
extractor.analyze(text, options) extractor.analyze(text, options)
end end
extractor extractor
end end
def extractors def extractors
@extractors ||= {} @extractors ||= {}
end end
def skip_project_check? def skip_project_check?
false false
end end
def build_mention_values(resource_foreign_key) def build_mention_values(resource_foreign_key)
refs = all_references(author) refs = all_references(author)
mentioned_users_ids = array_to_sql(refs.mentioned_users.pluck(:id)) mentioned_users_ids = array_to_sql(refs.mentioned_users.pluck(:id))
mentioned_projects_ids = array_to_sql(refs.mentioned_projects.pluck(:id)) mentioned_projects_ids = array_to_sql(refs.mentioned_projects.pluck(:id))
mentioned_groups_ids = array_to_sql(refs.mentioned_groups.pluck(:id)) mentioned_groups_ids = array_to_sql(refs.mentioned_groups.pluck(:id))
return if mentioned_users_ids.blank? && mentioned_projects_ids.blank? && mentioned_groups_ids.blank? return if mentioned_users_ids.blank? && mentioned_projects_ids.blank? && mentioned_groups_ids.blank?
{ {
"#{resource_foreign_key}": user_mention_resource_id, "#{resource_foreign_key}": user_mention_resource_id,
note_id: user_mention_note_id, note_id: user_mention_note_id,
mentioned_users_ids: mentioned_users_ids, mentioned_users_ids: mentioned_users_ids,
mentioned_projects_ids: mentioned_projects_ids, mentioned_projects_ids: mentioned_projects_ids,
mentioned_groups_ids: mentioned_groups_ids mentioned_groups_ids: mentioned_groups_ids
} }
end end
def array_to_sql(ids_array) def array_to_sql(ids_array)
return unless ids_array.present? return unless ids_array.present?
'{' + ids_array.join(", ") + '}' '{' + ids_array.join(", ") + '}'
end end
private private
def mentionable_params def mentionable_params
{} {}
end
end end
end end
end end
......
...@@ -4,17 +4,19 @@ module Gitlab ...@@ -4,17 +4,19 @@ module Gitlab
module BackgroundMigration module BackgroundMigration
module UserMentions module UserMentions
module Models module Models
# Extract common no_quote_columns method used in determining the columns that do not need module Concerns
# to be quoted for corresponding models # Extract common no_quote_columns method used in determining the columns that do not need
module MentionableMigrationMethods # to be quoted for corresponding models
extend ::ActiveSupport::Concern module MentionableMigrationMethods
extend ::ActiveSupport::Concern
class_methods do class_methods do
def no_quote_columns def no_quote_columns
[ [
:note_id, :note_id,
user_mention_model.resource_foreign_key user_mention_model.resource_foreign_key
] ]
end
end end
end end
end end
......
...@@ -7,7 +7,7 @@ module Gitlab ...@@ -7,7 +7,7 @@ module Gitlab
module Models module Models
module DesignManagement module DesignManagement
class Design < ActiveRecord::Base class Design < ActiveRecord::Base
include MentionableMigrationMethods include Concerns::MentionableMigrationMethods
def self.user_mention_model def self.user_mention_model
Gitlab::BackgroundMigration::UserMentions::Models::DesignUserMention Gitlab::BackgroundMigration::UserMentions::Models::DesignUserMention
......
...@@ -6,9 +6,9 @@ module Gitlab ...@@ -6,9 +6,9 @@ module Gitlab
module UserMentions module UserMentions
module Models module Models
class Epic < ActiveRecord::Base class Epic < ActiveRecord::Base
include IsolatedMentionable include Concerns::IsolatedMentionable
include Concerns::MentionableMigrationMethods
include CacheMarkdownField include CacheMarkdownField
include MentionableMigrationMethods
attr_mentionable :title, pipeline: :single_line attr_mentionable :title, pipeline: :single_line
attr_mentionable :description attr_mentionable :description
......
...@@ -6,7 +6,7 @@ module Gitlab ...@@ -6,7 +6,7 @@ module Gitlab
module UserMentions module UserMentions
module Models module Models
class Note < ActiveRecord::Base class Note < ActiveRecord::Base
include IsolatedMentionable include Concerns::IsolatedMentionable
include CacheMarkdownField include CacheMarkdownField
self.table_name = 'notes' self.table_name = 'notes'
......
...@@ -6,9 +6,9 @@ module Gitlab ...@@ -6,9 +6,9 @@ module Gitlab
module UserMentions module UserMentions
module Models module Models
class Snippet < ActiveRecord::Base class Snippet < ActiveRecord::Base
include IsolatedMentionable include Concerns::IsolatedMentionable
include Concerns::MentionableMigrationMethods
include CacheMarkdownField include CacheMarkdownField
include MentionableMigrationMethods
attr_mentionable :title, pipeline: :single_line attr_mentionable :title, pipeline: :single_line
attr_mentionable :description attr_mentionable :description
......
...@@ -44,8 +44,6 @@ module Gitlab ...@@ -44,8 +44,6 @@ module Gitlab
end end
end end
helpers :reports
def value def value
@config[:reports] = reports_value if @config.key?(:reports) @config[:reports] = reports_value if @config.key?(:reports)
@config @config
......
...@@ -49,8 +49,6 @@ module Gitlab ...@@ -49,8 +49,6 @@ module Gitlab
description: 'Environment variables available for this job.', description: 'Environment variables available for this job.',
inherit: false inherit: false
helpers :trigger, :needs, :variables
attributes :when, :allow_failure attributes :when, :allow_failure
def self.matching?(name, config) def self.matching?(name, config)
......
...@@ -28,8 +28,6 @@ module Gitlab ...@@ -28,8 +28,6 @@ module Gitlab
entry :paths, Entry::Paths, entry :paths, Entry::Paths,
description: 'Specify which paths should be cached across builds.' description: 'Specify which paths should be cached across builds.'
helpers :key
attributes :policy attributes :policy
def value def value
......
...@@ -61,8 +61,6 @@ module Gitlab ...@@ -61,8 +61,6 @@ module Gitlab
description: 'Default artifacts.', description: 'Default artifacts.',
inherit: false inherit: false
helpers :before_script, :image, :services, :after_script, :cache
private private
def overwrite_entry(deps, key, current_entry) def overwrite_entry(deps, key, current_entry)
......
...@@ -128,11 +128,6 @@ module Gitlab ...@@ -128,11 +128,6 @@ module Gitlab
description: 'This job will produce a release.', description: 'This job will produce a release.',
inherit: false inherit: false
helpers :before_script, :script, :type, :after_script,
:cache, :image, :services, :variables,
:artifacts, :environment, :coverage, :retry,
:needs, :interruptible, :release, :tags
attributes :script, :tags, :allow_failure, :when, :dependencies, attributes :script, :tags, :allow_failure, :when, :dependencies,
:needs, :retry, :parallel, :start_in, :needs, :retry, :parallel, :start_in,
:interruptible, :timeout, :resource_group, :release :interruptible, :timeout, :resource_group, :release
......
...@@ -54,8 +54,6 @@ module Gitlab ...@@ -54,8 +54,6 @@ module Gitlab
allowed_when: %w[on_success on_failure always never manual delayed].freeze allowed_when: %w[on_success on_failure always never manual delayed].freeze
} }
helpers :stage, :only, :except, :rules
attributes :extends, :rules attributes :extends, :rules
end end
......
...@@ -33,8 +33,6 @@ module Gitlab ...@@ -33,8 +33,6 @@ module Gitlab
validates :description, type: String, presence: true validates :description, type: String, presence: true
end end
helpers :assets
def value def value
@config[:assets] = assets_value if @config.key?(:assets) @config[:assets] = assets_value if @config.key?(:assets)
@config @config
......
...@@ -23,8 +23,6 @@ module Gitlab ...@@ -23,8 +23,6 @@ module Gitlab
validates :links, array_of_hashes: true, presence: true validates :links, array_of_hashes: true, presence: true
end end
helpers :links
def value def value
@config[:links] = links_value if @config.key?(:links) @config[:links] = links_value if @config.key?(:links)
@config @config
......
...@@ -67,9 +67,7 @@ module Gitlab ...@@ -67,9 +67,7 @@ module Gitlab
entry :workflow, Entry::Workflow, entry :workflow, Entry::Workflow,
description: 'List of evaluable rules to determine Pipeline status' description: 'List of evaluable rules to determine Pipeline status'
helpers :default, :stages, :types, :variables, :workflow dynamic_helpers :jobs
helpers :jobs, dynamic: true
delegate :before_script_value, delegate :before_script_value,
:image_value, :image_value,
......
...@@ -7,8 +7,13 @@ module Gitlab ...@@ -7,8 +7,13 @@ module Gitlab
## ##
# Entry that represents a configuration of Docker service. # Entry that represents a configuration of Docker service.
# #
class Service < Image # TODO: remove duplication with Image superclass by defining a common
# Imageable concern.
# https://gitlab.com/gitlab-org/gitlab/issues/208774
class Service < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
include ::Gitlab::Config::Entry::Configurable
ALLOWED_KEYS = %i[name entrypoint command alias ports].freeze ALLOWED_KEYS = %i[name entrypoint command alias ports].freeze
...@@ -16,9 +21,9 @@ module Gitlab ...@@ -16,9 +21,9 @@ module Gitlab
validates :config, hash_or_string: true validates :config, hash_or_string: true
validates :config, allowed_keys: ALLOWED_KEYS validates :config, allowed_keys: ALLOWED_KEYS
validates :config, disallowed_keys: %i[ports], unless: :with_image_ports? validates :config, disallowed_keys: %i[ports], unless: :with_image_ports?
validates :name, type: String, presence: true validates :name, type: String, presence: true
validates :entrypoint, array_of_strings: true, allow_nil: true validates :entrypoint, array_of_strings: true, allow_nil: true
validates :command, array_of_strings: true, allow_nil: true validates :command, array_of_strings: true, allow_nil: true
validates :alias, type: String, allow_nil: true validates :alias, type: String, allow_nil: true
validates :alias, type: String, presence: true, unless: ->(record) { record.ports.blank? } validates :alias, type: String, presence: true, unless: ->(record) { record.ports.blank? }
...@@ -27,6 +32,8 @@ module Gitlab ...@@ -27,6 +32,8 @@ module Gitlab
entry :ports, Entry::Ports, entry :ports, Entry::Ports,
description: 'Ports used to expose the service' description: 'Ports used to expose the service'
attributes :ports
def alias def alias
value[:alias] value[:alias]
end end
...@@ -34,6 +41,29 @@ module Gitlab ...@@ -34,6 +41,29 @@ module Gitlab
def command def command
value[:command] value[:command]
end end
def name
value[:name]
end
def entrypoint
value[:entrypoint]
end
def value
return { name: @config } if string?
return @config if hash?
{}
end
def with_image_ports?
opt(:with_image_ports)
end
def skip_config_hash_validation?
true
end
end end
end end
end end
......
...@@ -75,7 +75,8 @@ module Gitlab ...@@ -75,7 +75,8 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def entry(key, entry, description: nil, default: nil, inherit: nil, reserved: nil, metadata: {}) def entry(key, entry, description: nil, default: nil, inherit: nil, reserved: nil, metadata: {})
raise ArgumentError, "Entry #{key} already defined" if @nodes.to_h[key.to_sym] entry_name = key.to_sym
raise ArgumentError, "Entry #{key} already defined" if @nodes.to_h[entry_name]
factory = ::Gitlab::Config::Entry::Factory.new(entry) factory = ::Gitlab::Config::Entry::Factory.new(entry)
.with(description: description) .with(description: description)
...@@ -84,10 +85,17 @@ module Gitlab ...@@ -84,10 +85,17 @@ module Gitlab
.with(reserved: reserved) .with(reserved: reserved)
.metadata(metadata) .metadata(metadata)
(@nodes ||= {}).merge!(key.to_sym => factory) @nodes ||= {}
@nodes[entry_name] = factory
helpers(entry_name)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def dynamic_helpers(*nodes)
helpers(*nodes, dynamic: true)
end
def helpers(*nodes, dynamic: false) def helpers(*nodes, dynamic: false)
nodes.each do |symbol| nodes.each do |symbol|
if method_defined?("#{symbol}_defined?") || method_defined?("#{symbol}_value") if method_defined?("#{symbol}_defined?") || method_defined?("#{symbol}_value")
......
# frozen_string_literal: true
module Gitlab
module Email
ProcessingError = Class.new(StandardError)
EmailUnparsableError = Class.new(ProcessingError)
SentNotificationNotFoundError = Class.new(ProcessingError)
ProjectNotFound = Class.new(ProcessingError)
EmptyEmailError = Class.new(ProcessingError)
AutoGeneratedEmailError = Class.new(ProcessingError)
UserNotFoundError = Class.new(ProcessingError)
UserBlockedError = Class.new(ProcessingError)
UserNotAuthorizedError = Class.new(ProcessingError)
NoteableNotFoundError = Class.new(ProcessingError)
InvalidRecordError = Class.new(ProcessingError)
InvalidNoteError = Class.new(InvalidRecordError)
InvalidIssueError = Class.new(InvalidRecordError)
InvalidMergeRequestError = Class.new(InvalidRecordError)
UnknownIncomingEmail = Class.new(ProcessingError)
InvalidAttachment = Class.new(ProcessingError)
end
end
...@@ -5,23 +5,6 @@ require_dependency 'gitlab/email/handler' ...@@ -5,23 +5,6 @@ require_dependency 'gitlab/email/handler'
# Inspired in great part by Discourse's Email::Receiver # Inspired in great part by Discourse's Email::Receiver
module Gitlab module Gitlab
module Email module Email
ProcessingError = Class.new(StandardError)
EmailUnparsableError = Class.new(ProcessingError)
SentNotificationNotFoundError = Class.new(ProcessingError)
ProjectNotFound = Class.new(ProcessingError)
EmptyEmailError = Class.new(ProcessingError)
AutoGeneratedEmailError = Class.new(ProcessingError)
UserNotFoundError = Class.new(ProcessingError)
UserBlockedError = Class.new(ProcessingError)
UserNotAuthorizedError = Class.new(ProcessingError)
NoteableNotFoundError = Class.new(ProcessingError)
InvalidRecordError = Class.new(ProcessingError)
InvalidNoteError = Class.new(InvalidRecordError)
InvalidIssueError = Class.new(InvalidRecordError)
InvalidMergeRequestError = Class.new(InvalidRecordError)
UnknownIncomingEmail = Class.new(ProcessingError)
InvalidAttachment = Class.new(ProcessingError)
class Receiver class Receiver
def initialize(raw) def initialize(raw)
@raw = raw @raw = raw
......
...@@ -14,7 +14,8 @@ module Gitlab ...@@ -14,7 +14,8 @@ module Gitlab
end end
def drop_jobs!(search_metadata, timeout:) def drop_jobs!(search_metadata, timeout:)
completed = false start_time = Gitlab::Metrics::System.monotonic_time
completed = true
deleted_jobs = 0 deleted_jobs = 0
job_search_metadata = job_search_metadata =
...@@ -27,18 +28,16 @@ module Gitlab ...@@ -27,18 +28,16 @@ module Gitlab
raise NoMetadataError if job_search_metadata.empty? raise NoMetadataError if job_search_metadata.empty?
raise InvalidQueueError unless queue raise InvalidQueueError unless queue
begin queue.each do |job|
Timeout.timeout(timeout) do if timeout_exceeded?(start_time, timeout)
queue.each do |job| completed = false
next unless job_matches?(job, job_search_metadata) break
end
job.delete next unless job_matches?(job, job_search_metadata)
deleted_jobs += 1
end
completed = true job.delete
end deleted_jobs += 1
rescue Timeout::Error
end end
{ {
...@@ -48,6 +47,8 @@ module Gitlab ...@@ -48,6 +47,8 @@ module Gitlab
} }
end end
private
def queue def queue
strong_memoize(:queue) do strong_memoize(:queue) do
# Sidekiq::Queue.new always returns a queue, even if it doesn't # Sidekiq::Queue.new always returns a queue, even if it doesn't
...@@ -59,5 +60,9 @@ module Gitlab ...@@ -59,5 +60,9 @@ module Gitlab
def job_matches?(job, job_search_metadata) def job_matches?(job, job_search_metadata)
job_search_metadata.all? { |key, value| job[key] == value } job_search_metadata.all? { |key, value| job[key] == value }
end end
def timeout_exceeded?(start_time, timeout)
(Gitlab::Metrics::System.monotonic_time - start_time) > timeout
end
end end
end end
# frozen_string_literal: true
module Gitlab
module Utils
# This class estimates the JSON blob byte size of a ruby object using as
# little allocations as possible.
# The estimation should be quite accurate when using simple objects.
#
# Example:
#
# Gitlab::Utils::JsonSizeEstimator.estimate(["a", { b: 12, c: nil }])
class JsonSizeEstimator
ARRAY_BRACKETS_SIZE = 2 # []
OBJECT_BRACKETS_SIZE = 2 # {}
DOUBLEQUOTE_SIZE = 2 # ""
COLON_SIZE = 1 # : character size from {"a": 1}
MINUS_SIGN_SIZE = 1 # - character size from -1
NULL_SIZE = 4 # null
class << self
# Returns: integer (number of bytes)
def estimate(object)
case object
when Hash
estimate_hash(object)
when Array
estimate_array(object)
when String
estimate_string(object)
when Integer
estimate_integer(object)
when Float
estimate_float(object)
when DateTime, Time
estimate_time(object)
when NilClass
NULL_SIZE
else
# might be incorrect, but #to_s is safe, #to_json might be disabled for some objects: User
estimate_string(object.to_s)
end
end
private
def estimate_hash(hash)
size = 0
item_count = 0
hash.each do |key, value|
item_count += 1
size += estimate(key.to_s) + COLON_SIZE + estimate(value)
end
size + OBJECT_BRACKETS_SIZE + comma_count(item_count)
end
def estimate_array(array)
size = 0
item_count = 0
array.each do |item|
item_count += 1
size += estimate(item)
end
size + ARRAY_BRACKETS_SIZE + comma_count(item_count)
end
def estimate_string(string)
string.bytesize + DOUBLEQUOTE_SIZE
end
def estimate_float(float)
float.to_s.bytesize
end
def estimate_integer(integer)
if integer > 0
integer_string_size(integer)
elsif integer < 0
integer_string_size(integer.abs) + MINUS_SIGN_SIZE
else # 0
1
end
end
def estimate_time(time)
time.to_json.size
end
def integer_string_size(integer)
Math.log10(integer).floor + 1
end
def comma_count(item_count)
item_count == 0 ? 0 : item_count - 1
end
end
end
end
end
...@@ -13,7 +13,7 @@ module Gitlab ...@@ -13,7 +13,7 @@ module Gitlab
total_length = 0 total_length = 0
limited_array = array.take_while do |arg| limited_array = array.take_while do |arg|
total_length += arg.to_json.length total_length += JsonSizeEstimator.estimate(arg)
total_length <= MAXIMUM_ARRAY_LENGTH total_length <= MAXIMUM_ARRAY_LENGTH
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module QA module QA
context 'Release' do context 'Release' do
describe 'Deploy key creation' do describe 'Deploy key creation' do
it 'user adds a deploy key' do it 'user adds a deploy key', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/208761' do
Flow::Login.sign_in Flow::Login.sign_in
key = Runtime::Key::RSA.new key = Runtime::Key::RSA.new
......
...@@ -35,7 +35,7 @@ module QA ...@@ -35,7 +35,7 @@ module QA
] ]
keys.each do |(key_class, bits)| keys.each do |(key_class, bits)|
it "user sets up a deploy key with #{key_class}(#{bits}) to clone code using pipelines" do it "user sets up a deploy key with #{key_class}(#{bits}) to clone code using pipelines", quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/208761' do
key = key_class.new(*bits) key = key_class.new(*bits)
Resource::DeployKey.fabricate_via_browser_ui! do |resource| Resource::DeployKey.fabricate_via_browser_ui! do |resource|
......
...@@ -198,6 +198,63 @@ describe Projects::ReleasesController do ...@@ -198,6 +198,63 @@ describe Projects::ReleasesController do
end end
end end
context 'GET #downloads' do
subject do
get :downloads, params: {
namespace_id: project.namespace,
project_id: project,
tag: tag,
filepath: filepath
}
end
before do
sign_in(user)
end
let(:release) { create(:release, project: project, tag: tag ) }
let(:tag) { 'v11.9.0-rc2' }
let(:db_filepath) { '/binaries/linux-amd64' }
let!(:link) do
create :release_link,
release: release,
name: 'linux-amd64 binaries',
filepath: db_filepath,
url: 'https://downloads.example.com/bin/gitlab-linux-amd64'
end
context 'valid filepath' do
let(:filepath) { CGI.escape('/binaries/linux-amd64') }
it 'redirects to the asset direct link' do
subject
expect(response).to redirect_to(link.url)
end
end
context 'invalid filepath' do
let(:filepath) { CGI.escape('/binaries/win32') }
it 'is not found' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'ignores filepath extension' do
let(:db_filepath) { '/binaries/linux-amd64.json' }
let(:filepath) { CGI.escape(db_filepath) }
it 'redirects to the asset direct link' do
subject
expect(response).to redirect_to(link.url)
end
end
end
describe 'GET #evidence' do describe 'GET #evidence' do
let_it_be(:tag_name) { "v1.1.0-evidence" } let_it_be(:tag_name) { "v1.1.0-evidence" }
let!(:release) { create(:release, :with_evidence, project: project, tag: tag_name) } let!(:release) { create(:release, :with_evidence, project: project, tag: tag_name) }
......
...@@ -6,6 +6,8 @@ FactoryBot.define do ...@@ -6,6 +6,8 @@ FactoryBot.define do
starts_at { 1.day.ago } starts_at { 1.day.ago }
ends_at { 1.day.from_now } ends_at { 1.day.from_now }
broadcast_type { :banner }
trait :expired do trait :expired do
starts_at { 5.days.ago } starts_at { 5.days.ago }
ends_at { 3.days.ago } ends_at { 3.days.ago }
...@@ -15,5 +17,9 @@ FactoryBot.define do ...@@ -15,5 +17,9 @@ FactoryBot.define do
starts_at { 5.days.from_now } starts_at { 5.days.from_now }
ends_at { 6.days.from_now } ends_at { 6.days.from_now }
end end
trait :notification do
broadcast_type { :notification }
end
end end
end end
...@@ -29,6 +29,38 @@ describe API::Helpers::CustomValidators do ...@@ -29,6 +29,38 @@ describe API::Helpers::CustomValidators do
end end
end end
describe API::Helpers::CustomValidators::GitSha do
let(:sha) { RepoHelpers.sample_commit.id }
let(:short_sha) { sha[0, Gitlab::Git::Commit::MIN_SHA_LENGTH] }
let(:too_short_sha) { sha[0, Gitlab::Git::Commit::MIN_SHA_LENGTH - 1] }
subject do
described_class.new(['test'], {}, false, scope.new)
end
context 'valid sha' do
it 'does not raise a validation error' do
expect_no_validation_error('test' => sha)
expect_no_validation_error('test' => short_sha)
end
end
context 'empty params' do
it 'raises a validation error' do
expect_validation_error('test' => nil)
expect_validation_error('test' => '')
end
end
context 'invalid sha' do
it 'raises a validation error' do
expect_validation_error('test' => "#{sha}2") # Sha length > 40
expect_validation_error('test' => 'somestring')
expect_validation_error('test' => too_short_sha) # sha length < MIN_SHA_LENGTH (7)
end
end
end
describe API::Helpers::CustomValidators::FilePath do describe API::Helpers::CustomValidators::FilePath do
subject do subject do
described_class.new(['test'], {}, false, scope.new) described_class.new(['test'], {}, false, scope.new)
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Lograge::CustomOptions do
describe '.call' do
let(:params) do
{
'controller' => 'ApplicationController',
'action' => 'show',
'format' => 'html',
'a' => 'b'
}
end
let(:event) do
ActiveSupport::Notifications::Event.new(
'test',
1,
2,
'transaction_id',
{ params: params, user_id: 'test' }
)
end
subject { described_class.call(event) }
it 'ignores some parameters' do
param_keys = subject[:params].map { |param| param[:key] }
expect(param_keys).not_to include(*described_class::IGNORE_PARAMS)
end
it 'formats the parameters' do
expect(subject[:params]).to eq([{ key: 'a', value: 'b' }])
end
it 'adds the current time' do
travel_to(5.days.ago) do
expected_time = Time.now.utc.iso8601(3)
expect(subject[:time]).to eq(expected_time)
end
end
it 'adds the user id' do
expect(subject[:user_id]).to eq('test')
end
end
end
...@@ -31,14 +31,7 @@ describe Gitlab::SidekiqQueue do ...@@ -31,14 +31,7 @@ describe Gitlab::SidekiqQueue do
context 'when the queue is not processed in time' do context 'when the queue is not processed in time' do
before do before do
calls = 0 allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(1, 2, 12)
allow(sidekiq_queue).to receive(:job_matches?).and_wrap_original do |m, *args|
raise Timeout::Error if calls > 0
calls += 1
m.call(*args)
end
end end
it 'returns a non-completion flag, the number of jobs deleted, and the remaining queue size' do it 'returns a non-completion flag, the number of jobs deleted, and the remaining queue size' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Utils::JsonSizeEstimator do
RSpec::Matchers.define :match_json_bytesize_of do |expected|
match do |actual|
actual == expected.to_json.bytesize
end
end
def estimate(object)
described_class.estimate(object)
end
[
[],
[[[[]]]],
[1, "str", 3.14, ["str", { a: -1 }]],
{},
{ a: {} },
{ a: { b: { c: [1, 2, 3], e: Time.now, f: nil } } },
{ 100 => 500 },
{ '狸' => '狸' },
nil
].each do |example|
it { expect(estimate(example)).to match_json_bytesize_of(example) }
end
it 'calls #to_s on unknown object' do
klass = Class.new do
def to_s
'hello'
end
end
expect(estimate(klass.new)).to match_json_bytesize_of(klass.new.to_s) # "hello"
end
end
...@@ -2165,14 +2165,20 @@ describe API::Users, :do_not_mock_admin_mode do ...@@ -2165,14 +2165,20 @@ describe API::Users, :do_not_mock_admin_mode do
end end
describe 'POST /users/:id/block' do describe 'POST /users/:id/block' do
let(:blocked_user) { create(:user, state: 'blocked') }
before do before do
admin admin
end end
it 'blocks existing user' do it 'blocks existing user' do
post api("/users/#{user.id}/block", admin) post api("/users/#{user.id}/block", admin)
expect(response).to have_gitlab_http_status(:created)
expect(user.reload.state).to eq('blocked') aggregate_failures do
expect(response).to have_gitlab_http_status(:created)
expect(response.body).to eq('true')
expect(user.reload.state).to eq('blocked')
end
end end
it 'does not re-block ldap blocked users' do it 'does not re-block ldap blocked users' do
...@@ -2192,6 +2198,15 @@ describe API::Users, :do_not_mock_admin_mode do ...@@ -2192,6 +2198,15 @@ describe API::Users, :do_not_mock_admin_mode do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found') expect(json_response['message']).to eq('404 User Not Found')
end end
it 'returns a 201 if user is already blocked' do
post api("/users/#{blocked_user.id}/block", admin)
aggregate_failures do
expect(response).to have_gitlab_http_status(:created)
expect(response.body).to eq('null')
end
end
end end
describe 'POST /users/:id/unblock' do describe 'POST /users/:id/unblock' do
......
...@@ -130,14 +130,22 @@ describe PostReceiveService do ...@@ -130,14 +130,22 @@ describe PostReceiveService do
end end
end end
context 'broadcast message exists' do context 'broadcast message banner exists' do
it 'outputs a broadcast message' do it 'outputs a broadcast message' do
broadcast_message = create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now) broadcast_message = create(:broadcast_message)
expect(subject).to include(build_alert_message(broadcast_message.message)) expect(subject).to include(build_alert_message(broadcast_message.message))
end end
end end
context 'broadcast message notification exists' do
it 'does not output a broadcast message' do
create(:broadcast_message, :notification)
expect(has_alert_messages?(subject)).to be_falsey
end
end
context 'broadcast message does not exist' do context 'broadcast message does not exist' do
it 'does not output a broadcast message' do it 'does not output a broadcast message' do
expect(has_alert_messages?(subject)).to be_falsey expect(has_alert_messages?(subject)).to be_falsey
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment