diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-21 10:08:36 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-21 10:08:36 +0300 |
commit | 48aff82709769b098321c738f3444b9bdaa694c6 (patch) | |
tree | e00c7c43e2d9b603a5a6af576b1685e400410dee /app/controllers/concerns | |
parent | 879f5329ee916a948223f8f43d77fba4da6cd028 (diff) |
Add latest changes from gitlab-org/gitlab@13-5-stable-eev13.5.0-rc42
Diffstat (limited to 'app/controllers/concerns')
17 files changed, 248 insertions, 119 deletions
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb index 9ff97f398f5..5c74d79951f 100644 --- a/app/controllers/concerns/authenticates_with_two_factor.rb +++ b/app/controllers/concerns/authenticates_with_two_factor.rb @@ -89,10 +89,7 @@ module AuthenticatesWithTwoFactor user.save! sign_in(user, message: :two_factor_authenticated, event: :authentication) else - user.increment_failed_attempts! - Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP") - flash.now[:alert] = _('Invalid two-factor code.') - prompt_for_two_factor(user) + handle_two_factor_failure(user, 'OTP', _('Invalid two-factor code.')) end end @@ -101,7 +98,7 @@ module AuthenticatesWithTwoFactor if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge]) handle_two_factor_success(user) else - handle_two_factor_failure(user, 'U2F') + handle_two_factor_failure(user, 'U2F', _('Authentication via U2F device failed.')) end end @@ -109,7 +106,7 @@ module AuthenticatesWithTwoFactor if Webauthn::AuthenticateService.new(user, user_params[:device_response], session[:challenge]).execute handle_two_factor_success(user) else - handle_two_factor_failure(user, 'WebAuthn') + handle_two_factor_failure(user, 'WebAuthn', _('Authentication via WebAuthn device failed.')) end end @@ -152,13 +149,19 @@ module AuthenticatesWithTwoFactor sign_in(user, message: :two_factor_authenticated, event: :authentication) end - def handle_two_factor_failure(user, method) + def handle_two_factor_failure(user, method, message) user.increment_failed_attempts! + log_failed_two_factor(user, method, request.remote_ip) + Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=#{method}") - flash.now[:alert] = _('Authentication via %{method} device failed.') % { method: method } + flash.now[:alert] = message prompt_for_two_factor(user) end + def log_failed_two_factor(user, method, ip_address) + # overridden in EE + end + def handle_changed_user(user) clear_two_factor_attempt! @@ -173,3 +176,5 @@ module AuthenticatesWithTwoFactor Digest::SHA256.hexdigest(user.encrypted_password) != session[:user_password_hash] end end + +AuthenticatesWithTwoFactor.prepend_if_ee('EE::AuthenticatesWithTwoFactor') diff --git a/app/controllers/concerns/authenticates_with_two_factor_for_admin_mode.rb b/app/controllers/concerns/authenticates_with_two_factor_for_admin_mode.rb new file mode 100644 index 00000000000..a8155f1e639 --- /dev/null +++ b/app/controllers/concerns/authenticates_with_two_factor_for_admin_mode.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +module AuthenticatesWithTwoFactorForAdminMode + extend ActiveSupport::Concern + + included do + include AuthenticatesWithTwoFactor + end + + def admin_mode_prompt_for_two_factor(user) + return handle_locked_user(user) unless user.can?(:log_in) + + session[:otp_user_id] = user.id + push_frontend_feature_flag(:webauthn) + + if user.two_factor_webauthn_enabled? + setup_webauthn_authentication(user) + else + setup_u2f_authentication(user) + end + + render 'admin/sessions/two_factor', layout: 'application' + end + + def admin_mode_authenticate_with_two_factor + user = current_user + + return handle_locked_user(user) unless user.can?(:log_in) + + if user_params[:otp_attempt].present? && session[:otp_user_id] + admin_mode_authenticate_with_two_factor_via_otp(user) + elsif user_params[:device_response].present? && session[:otp_user_id] + if user.two_factor_webauthn_enabled? + admin_mode_authenticate_with_two_factor_via_webauthn(user) + else + admin_mode_authenticate_with_two_factor_via_u2f(user) + end + elsif user && user.valid_password?(user_params[:password]) + admin_mode_prompt_for_two_factor(user) + else + invalid_login_redirect + end + end + + def admin_mode_authenticate_with_two_factor_via_otp(user) + if valid_otp_attempt?(user) + # Remove any lingering user data from login + session.delete(:otp_user_id) + + user.save! unless Gitlab::Database.read_only? + + # The admin user has successfully passed 2fa, enable admin mode ignoring password + enable_admin_mode + else + admin_handle_two_factor_failure(user, 'OTP', _('Invalid two-factor code.')) + end + end + + def admin_mode_authenticate_with_two_factor_via_u2f(user) + if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge]) + admin_handle_two_factor_success + else + admin_handle_two_factor_failure(user, 'U2F', _('Authentication via U2F device failed.')) + end + end + + def admin_mode_authenticate_with_two_factor_via_webauthn(user) + if Webauthn::AuthenticateService.new(user, user_params[:device_response], session[:challenge]).execute + admin_handle_two_factor_success + else + admin_handle_two_factor_failure(user, 'WebAuthn', _('Authentication via WebAuthn device failed.')) + end + end + + private + + def enable_admin_mode + if current_user_mode.enable_admin_mode!(skip_password_validation: true) + redirect_to redirect_path, notice: _('Admin mode enabled') + else + invalid_login_redirect + end + end + + def invalid_login_redirect + flash.now[:alert] = _('Invalid login or password') + render :new + end + + def admin_handle_two_factor_success + # Remove any lingering user data from login + session.delete(:otp_user_id) + session.delete(:challenge) + + # The admin user has successfully passed 2fa, enable admin mode ignoring password + enable_admin_mode + end + + def admin_handle_two_factor_failure(user, method, message) + user.increment_failed_attempts! + log_failed_two_factor(user, method, request.remote_ip) + + Gitlab::AppLogger.info("Failed Admin Mode Login: user=#{user.username} ip=#{request.remote_ip} method=#{method}") + flash.now[:alert] = message + admin_mode_prompt_for_two_factor(user) + end +end diff --git a/app/controllers/concerns/boards_actions.rb b/app/controllers/concerns/boards_actions.rb index 9d40b9e8c88..b382e338a78 100644 --- a/app/controllers/concerns/boards_actions.rb +++ b/app/controllers/concerns/boards_actions.rb @@ -9,7 +9,7 @@ module BoardsActions before_action :boards, only: :index before_action :board, only: :show - before_action :push_wip_limits, only: [:index, :show] + before_action :push_licensed_features, only: [:index, :show] before_action do push_frontend_feature_flag(:not_issuable_queries, parent, default_enabled: true) end @@ -29,7 +29,7 @@ module BoardsActions private # Noop on FOSS - def push_wip_limits + def push_licensed_features end def boards diff --git a/app/controllers/concerns/controller_with_feature_category.rb b/app/controllers/concerns/controller_with_feature_category.rb index f8985cf0950..c1ff9ef2e69 100644 --- a/app/controllers/concerns/controller_with_feature_category.rb +++ b/app/controllers/concerns/controller_with_feature_category.rb @@ -5,35 +5,38 @@ module ControllerWithFeatureCategory include Gitlab::ClassAttributes class_methods do - def feature_category(category, config = {}) - validate_config!(config) + def feature_category(category, actions = []) + feature_category_configuration[category] ||= [] + feature_category_configuration[category] += actions.map(&:to_s) - category_config = Config.new(category, config[:only], config[:except], config[:if], config[:unless]) - # Add the config to the beginning. That way, the last defined one takes precedence. - feature_category_configuration.unshift(category_config) + validate_config!(feature_category_configuration) end def feature_category_for_action(action) - category_config = feature_category_configuration.find { |config| config.matches?(action) } + category_config = feature_category_configuration.find do |_, actions| + actions.empty? || actions.include?(action) + end - category_config&.category || superclass_feature_category_for_action(action) + category_config&.first || superclass_feature_category_for_action(action) end private def validate_config!(config) - invalid_keys = config.keys - [:only, :except, :if, :unless] - if invalid_keys.any? - raise ArgumentError, "unknown arguments: #{invalid_keys} " + empty = config.find { |_, actions| actions.empty? } + duplicate_actions = config.values.flatten.group_by(&:itself).select { |_, v| v.count > 1 }.keys + + if config.length > 1 && empty + raise ArgumentError, "#{empty.first} is defined for all actions, but other categories are set" end - if config.key?(:only) && config.key?(:except) - raise ArgumentError, "cannot configure both `only` and `except`" + if duplicate_actions.any? + raise ArgumentError, "Actions have multiple feature categories: #{duplicate_actions.join(', ')}" end end def feature_category_configuration - class_attributes[:feature_category_config] ||= [] + class_attributes[:feature_category_config] ||= {} end def superclass_feature_category_for_action(action) diff --git a/app/controllers/concerns/controller_with_feature_category/config.rb b/app/controllers/concerns/controller_with_feature_category/config.rb deleted file mode 100644 index 624691ee4f6..00000000000 --- a/app/controllers/concerns/controller_with_feature_category/config.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module ControllerWithFeatureCategory - class Config - attr_reader :category - - def initialize(category, only, except, if_proc, unless_proc) - @category = category.to_sym - @only, @except = only&.map(&:to_s), except&.map(&:to_s) - @if_proc, @unless_proc = if_proc, unless_proc - end - - def matches?(action) - included?(action) && !excluded?(action) && - if_proc?(action) && !unless_proc?(action) - end - - private - - attr_reader :only, :except, :if_proc, :unless_proc - - def if_proc?(action) - if_proc.nil? || if_proc.call(action) - end - - def unless_proc?(action) - unless_proc.present? && unless_proc.call(action) - end - - def included?(action) - only.nil? || only.include?(action) - end - - def excluded?(action) - except.present? && except.include?(action) - end - end -end diff --git a/app/controllers/concerns/hooks_execution.rb b/app/controllers/concerns/hooks_execution.rb index ad1f8341109..87d215f50e7 100644 --- a/app/controllers/concerns/hooks_execution.rb +++ b/app/controllers/concerns/hooks_execution.rb @@ -5,6 +5,21 @@ module HooksExecution private + def destroy_hook(hook) + result = WebHooks::DestroyService.new(current_user).execute(hook) + + if result[:status] == :success + flash[:notice] = + if result[:async] + _("%{hook_type} was scheduled for deletion") % { hook_type: hook.model_name.human } + else + _("%{hook_type} was deleted") % { hook_type: hook.model_name.human } + end + else + flash[:alert] = result[:message] + end + end + def set_hook_execution_notice(result) http_status = result[:http_status] message = result[:message] diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb index 6060dc729af..39f63bbaaec 100644 --- a/app/controllers/concerns/integrations_actions.rb +++ b/app/controllers/concerns/integrations_actions.rb @@ -20,7 +20,7 @@ module IntegrationsActions respond_to do |format| format.html do if saved - PropagateIntegrationWorker.perform_async(integration.id, false) + PropagateIntegrationWorker.perform_async(integration.id) redirect_to scoped_edit_integration_path(integration), notice: success_message else render 'shared/integrations/edit' diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 89ba2175b60..0d7af57328a 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -41,10 +41,13 @@ module IssuableCollections end def set_pagination + row_count = finder.row_count + @issuables = @issuables.page(params[:page]) @issuables = per_page_for_relative_position if params[:sort] == 'relative_position' + @issuables = @issuables.without_count if row_count == -1 @issuable_meta_data = Gitlab::IssuableMetadata.new(current_user, @issuables).data - @total_pages = issuable_page_count(@issuables) + @total_pages = page_count_for_relation(@issuables, row_count) end # rubocop:enable Gitlab/ModuleWithInstanceVariables @@ -58,14 +61,11 @@ module IssuableCollections end # rubocop: enable CodeReuse/ActiveRecord - def issuable_page_count(relation) - page_count_for_relation(relation, finder.row_count) - end - def page_count_for_relation(relation, row_count) limit = relation.limit_value.to_f return 1 if limit == 0 + return (params[:page] || 1).to_i + 1 if row_count == -1 (row_count.to_f / limit).ceil end diff --git a/app/controllers/concerns/issuable_collections_action.rb b/app/controllers/concerns/issuable_collections_action.rb index e3ac117660b..7ed66027da3 100644 --- a/app/controllers/concerns/issuable_collections_action.rb +++ b/app/controllers/concerns/issuable_collections_action.rb @@ -59,6 +59,9 @@ module IssuableCollectionsAction end def finder_options - super.merge(non_archived: true) + super.merge( + non_archived: true, + issue_types: Issue::TYPES_FOR_LIST + ) end end diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index 8c7f156f7f8..816a93f14c6 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -22,10 +22,14 @@ module MembershipActions .new(current_user, update_params) .execute(member) - member = present_members([member]).first - - respond_to do |format| - format.js { render 'shared/members/update', locals: { member: member } } + if member.expires? + render json: { + expires_in: helpers.distance_of_time_in_words_to_now(member.expires_at), + expires_soon: member.expires_soon?, + expires_at_formatted: member.expires_at.to_time.in_time_zone.to_s(:medium) + } + else + render json: {} end end @@ -101,7 +105,7 @@ module MembershipActions # rubocop: enable CodeReuse/ActiveRecord def resend_invite - member = membershipable.members.find(params[:id]) + member = membershipable_members.find(params[:id]) if member.invite? member.resend_invite @@ -118,6 +122,10 @@ module MembershipActions raise NotImplementedError end + def membershipable_members + raise NotImplementedError + end + def root_params_key case membershipable when Namespace diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb index 29138e7b014..6470c75dfbd 100644 --- a/app/controllers/concerns/milestone_actions.rb +++ b/app/controllers/concerns/milestone_actions.rb @@ -3,13 +3,25 @@ module MilestoneActions extend ActiveSupport::Concern + def issues + respond_to do |format| + format.html { redirect_to milestone_redirect_path } + format.json do + render json: tabs_json("shared/milestones/_issues_tab", { + issues: @milestone.sorted_issues(current_user), # rubocop:disable Gitlab/ModuleWithInstanceVariables + show_project_name: Gitlab::Utils.to_boolean(params[:show_project_name]) + }) + end + end + end + def merge_requests respond_to do |format| format.html { redirect_to milestone_redirect_path } format.json do render json: tabs_json("shared/milestones/_merge_requests_tab", { merge_requests: @milestone.sorted_merge_requests(current_user), # rubocop:disable Gitlab/ModuleWithInstanceVariables - show_project_name: true + show_project_name: Gitlab::Utils.to_boolean(params[:show_project_name]) }) end end diff --git a/app/controllers/concerns/multiple_boards_actions.rb b/app/controllers/concerns/multiple_boards_actions.rb index 95a6800f55c..370b8c72bfe 100644 --- a/app/controllers/concerns/multiple_boards_actions.rb +++ b/app/controllers/concerns/multiple_boards_actions.rb @@ -21,11 +21,13 @@ module MultipleBoardsActions end def create - board = Boards::CreateService.new(parent, current_user, board_params).execute + response = Boards::CreateService.new(parent, current_user, board_params).execute respond_to do |format| format.json do - if board.persisted? + board = response.payload + + if response.success? extra_json = { board_path: board_path(board) } render json: serialize_as_json(board).merge(extra_json) else diff --git a/app/controllers/concerns/redis_tracking.rb b/app/controllers/concerns/redis_tracking.rb index fa5eef981d1..d81bd10d5bb 100644 --- a/app/controllers/concerns/redis_tracking.rb +++ b/app/controllers/concerns/redis_tracking.rb @@ -11,12 +11,17 @@ # # if the feature flag is enabled by default you should use # track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score', feature: :my_feature, feature_default_enabled: true +# +# You can also pass custom conditions using `if:`, using the same format as with Rails callbacks. module RedisTracking extend ActiveSupport::Concern class_methods do - def track_redis_hll_event(*controller_actions, name:, feature:, feature_default_enabled: false) - after_action only: controller_actions, if: -> { request.format.html? && request.headers['DNT'] != '1' } do + def track_redis_hll_event(*controller_actions, name:, feature:, feature_default_enabled: false, if: nil) + custom_conditions = Array.wrap(binding.local_variable_get('if')) + conditions = [:trackable_request?, *custom_conditions] + + after_action only: controller_actions, if: conditions do track_unique_redis_hll_event(name, feature, feature_default_enabled) end end @@ -26,12 +31,15 @@ module RedisTracking def track_unique_redis_hll_event(event_name, feature, feature_default_enabled) return unless metric_feature_enabled?(feature, feature_default_enabled) - return unless Gitlab::CurrentSettings.usage_ping_enabled? return unless visitor_id Gitlab::UsageDataCounters::HLLRedisCounter.track_event(visitor_id, event_name) end + def trackable_request? + request.format.html? && request.headers['DNT'] != '1' + end + def metric_feature_enabled?(feature, default_enabled) Feature.enabled?(feature, default_enabled: default_enabled) end diff --git a/app/controllers/concerns/runner_setup_scripts.rb b/app/controllers/concerns/runner_setup_scripts.rb new file mode 100644 index 00000000000..c0e657a32d1 --- /dev/null +++ b/app/controllers/concerns/runner_setup_scripts.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module RunnerSetupScripts + extend ActiveSupport::Concern + + private + + def private_runner_setup_scripts(**kwargs) + instructions = Gitlab::Ci::RunnerInstructions.new(current_user: current_user, os: script_params[:os], arch: script_params[:arch], **kwargs) + output = { + install: instructions.install_script, + register: instructions.register_command + } + + if instructions.errors.any? + render json: { errors: instructions.errors }, status: :bad_request + else + render json: output + end + end + + def script_params + params.permit(:os, :arch) + end +end diff --git a/app/controllers/concerns/show_inherited_labels_checker.rb b/app/controllers/concerns/show_inherited_labels_checker.rb new file mode 100644 index 00000000000..9847226f599 --- /dev/null +++ b/app/controllers/concerns/show_inherited_labels_checker.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ShowInheritedLabelsChecker + extend ActiveSupport::Concern + + private + + def show_inherited_labels?(include_ancestor_groups) + Feature.enabled?(:show_inherited_labels, @project || @group, default_enabled: true) || include_ancestor_groups # rubocop:disable Gitlab/ModuleWithInstanceVariables + end +end diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb index 4548595d968..e4c3df6ccc3 100644 --- a/app/controllers/concerns/snippets_actions.rb +++ b/app/controllers/concerns/snippets_actions.rb @@ -17,13 +17,7 @@ module SnippetsActions respond_to :html end - def edit - # We need to load some info from the existing blob - snippet.content = blob.data - snippet.file_name = blob.path - - render 'edit' - end + def edit; end # This endpoint is being replaced by Snippets::BlobController#raw # Support for old raw links will be maintainted via this action but @@ -55,7 +49,6 @@ module SnippetsActions def show respond_to do |format| format.html do - conditionally_expand_blob(blob) @note = Note.new(noteable: @snippet, project: @snippet.project) @noteable = @snippet @@ -80,29 +73,6 @@ module SnippetsActions end end end - - def update - update_params = snippet_params.merge(spammable_params) - - service_response = Snippets::UpdateService.new(@snippet.project, current_user, update_params).execute(@snippet) - @snippet = service_response.payload[:snippet] - - handle_repository_error(:edit) - end - - def destroy - service_response = Snippets::DestroyService.new(current_user, @snippet).execute - - if service_response.success? - redirect_to gitlab_dashboard_snippets_path(@snippet), status: :found - elsif service_response.http_status == 403 - access_denied! - else - redirect_to gitlab_snippet_path(@snippet), - status: :found, - alert: service_response.message - end - end # rubocop:enable Gitlab/ModuleWithInstanceVariables private @@ -124,12 +94,4 @@ module SnippetsActions def convert_line_endings(content) params[:line_ending] == 'raw' ? content : content.gsub(/\r\n/, "\n") end - - def handle_repository_error(action) - errors = Array(snippet.errors.delete(:repository)) - - flash.now[:alert] = errors.first if errors.present? - - recaptcha_check_with_fallback(errors.empty?) { render action } - end end diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb index 5a5b634da40..aed109309e3 100644 --- a/app/controllers/concerns/wiki_actions.rb +++ b/app/controllers/concerns/wiki_actions.rb @@ -5,6 +5,7 @@ module WikiActions include PreviewMarkdown include SendsBlob include Gitlab::Utils::StrongMemoize + include RedisTracking extend ActiveSupport::Concern included do @@ -31,6 +32,11 @@ module WikiActions end end + # NOTE: We want to include wiki page views in the same counter as the other + # Event-based wiki actions tracked through TrackUniqueEvents, so we use the same event name. + track_redis_hll_event :show, name: Gitlab::UsageDataCounters::TrackUniqueEvents::WIKI_ACTION.to_s, + feature: :track_unique_wiki_page_views, feature_default_enabled: true + helper_method :view_file_button, :diff_file_html_data end @@ -44,7 +50,7 @@ module WikiActions wiki.list_pages(sort: params[:sort], direction: params[:direction]) ).page(params[:page]) - @wiki_entries = WikiPage.group_by_directory(@wiki_pages) + @wiki_entries = WikiDirectory.group_pages(@wiki_pages) render 'shared/wikis/pages' end |