diff options
author | Alex Groleau <agroleau@gitlab.com> | 2019-08-27 12:41:39 -0400 |
---|---|---|
committer | Alex Groleau <agroleau@gitlab.com> | 2019-08-27 12:41:39 -0400 |
commit | aa01f092829facd1044ad02f334422b7dbdc8b0e (patch) | |
tree | a754bf2497820432df7da0f2108bb7527a8dd7b8 /app/controllers | |
parent | a1d9c9994a9a4d79b824c3fd9322688303ac8b03 (diff) | |
parent | 6b10779053ff4233c7a64c5ab57754fce63f6710 (diff) | |
download | gitlab-ce-aa01f092829facd1044ad02f334422b7dbdc8b0e.tar.gz |
Merge branch 'master' of gitlab_gitlab:gitlab-org/gitlab-cerunner-metrics-extractor
Diffstat (limited to 'app/controllers')
65 files changed, 766 insertions, 296 deletions
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index a570da61d54..99411641874 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -103,8 +103,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController [ *::ApplicationSettingsHelper.visible_attributes, *::ApplicationSettingsHelper.external_authorization_service_attributes, - *lets_encrypt_visible_attributes, + :lets_encrypt_notification_email, + :lets_encrypt_terms_of_service_accepted, :domain_blacklist_file, + :raw_blob_request_limit, disabled_oauth_sign_in_sources: [], import_sources: [], repository_storages: [], @@ -143,13 +145,4 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController render action end - - def lets_encrypt_visible_attributes - return [] unless Feature.enabled?(:pages_auto_ssl) - - [ - :lets_encrypt_notification_email, - :lets_encrypt_terms_of_service_accepted - ] - end end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 15f7ef881c8..6317fa7c8d1 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -90,7 +90,8 @@ class Admin::GroupsController < Admin::ApplicationController :visibility_level, :require_two_factor_authentication, :two_factor_grace_period, - :project_creation_level + :project_creation_level, + :subgroup_creation_level ] end end diff --git a/app/controllers/admin/requests_profiles_controller.rb b/app/controllers/admin/requests_profiles_controller.rb index 89d4c4f18d9..24383455064 100644 --- a/app/controllers/admin/requests_profiles_controller.rb +++ b/app/controllers/admin/requests_profiles_controller.rb @@ -3,17 +3,17 @@ class Admin::RequestsProfilesController < Admin::ApplicationController def index @profile_token = Gitlab::RequestProfiler.profile_token - @profiles = Gitlab::RequestProfiler::Profile.all.group_by(&:request_path) + @profiles = Gitlab::RequestProfiler.all.group_by(&:request_path) end def show clean_name = Rack::Utils.clean_path_info(params[:name]) - profile = Gitlab::RequestProfiler::Profile.find(clean_name) + profile = Gitlab::RequestProfiler.find(clean_name) - if profile - render html: profile.content.html_safe - else - redirect_to admin_requests_profiles_path, alert: 'Profile not found' + unless profile && profile.content_type + return redirect_to admin_requests_profiles_path, alert: 'Profile not found' end + + send_file profile.file_path, type: "#{profile.content_type}; charset=utf-8", disposition: 'inline' end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index a02d0843615..98883af6286 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -39,7 +39,7 @@ class Admin::UsersController < Admin::ApplicationController warden.set_user(user, scope: :user) - Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username }) + log_impersonation_event flash[:alert] = _("You are now impersonating %{username}") % { username: user.username } @@ -236,4 +236,8 @@ class Admin::UsersController < Admin::ApplicationController def check_impersonation_availability access_denied! unless Gitlab.config.gitlab.impersonation_enabled end + + def log_impersonation_event + Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username }) + end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 75108bf2646..af6644b8fcc 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base include EnforcesTwoFactorAuthentication include WithPerformanceBar include SessionlessAuthentication + include ConfirmEmailWarning before_action :authenticate_user! before_action :enforce_terms!, if: :should_enforce_terms? @@ -116,7 +117,7 @@ class ApplicationController < ActionController::Base def render(*args) super.tap do # Set a header for custom error pages to prevent them from being intercepted by gitlab-workhorse - if response.content_type == 'text/html' && (400..599).cover?(response.status) + if (400..599).cover?(response.status) && workhorse_excluded_content_types.include?(response.content_type) response.headers['X-GitLab-Custom-Error'] = '1' end end @@ -124,6 +125,10 @@ class ApplicationController < ActionController::Base protected + def workhorse_excluded_content_types + @workhorse_excluded_content_types ||= %w(text/html application/json) + end + def append_info_to_payload(payload) super @@ -421,7 +426,7 @@ class ApplicationController < ActionController::Base end def manifest_import_enabled? - Group.supports_nested_objects? && Gitlab::CurrentSettings.import_sources.include?('manifest') + Gitlab::CurrentSettings.import_sources.include?('manifest') end def phabricator_import_enabled? @@ -499,9 +504,7 @@ class ApplicationController < ActionController::Base end def stop_impersonation - impersonated_user = current_user - - Gitlab::AppLogger.info("User #{impersonator.username} has stopped impersonating #{impersonated_user.username}") + log_impersonation_event warden.set_user(impersonator, scope: :user) session[:impersonator_id] = nil @@ -509,6 +512,14 @@ class ApplicationController < ActionController::Base impersonated_user end + def impersonated_user + current_user + end + + def log_impersonation_event + Gitlab::AppLogger.info("User #{impersonator.username} has stopped impersonating #{impersonated_user.username}") + end + def impersonator @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id] end diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 091327931c2..30a567c3bef 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -16,7 +16,7 @@ class AutocompleteController < ApplicationController .new(params: params, current_user: current_user, project: project, group: group) .execute - render json: UserSerializer.new.represent(users) + render json: UserSerializer.new(params).represent(users, project: project) end def user @@ -36,7 +36,7 @@ class AutocompleteController < ApplicationController end def award_emojis - render json: AwardedEmojiFinder.new(current_user).execute + render json: AwardEmojis::CollectUserEmojiService.new(current_user).execute end def merge_request_target_branches diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb index 0dd7500623d..1d1a72d21f1 100644 --- a/app/controllers/boards/issues_controller.rb +++ b/app/controllers/boards/issues_controller.rb @@ -2,23 +2,31 @@ module Boards class IssuesController < Boards::ApplicationController + # This is the maximum amount of issues which can be moved by one request to + # bulk_move for now. This is temporary and might be removed in future by + # introducing an alternative (async?) approach. + # (related: https://gitlab.com/groups/gitlab-org/-/epics/382) + MAX_MOVE_ISSUES_COUNT = 50 + include BoardsResponses include ControllerWithCrossProjectAccessCheck requires_cross_project_access if: -> { board&.group_board? } - before_action :whitelist_query_limiting, only: [:index, :update] + before_action :whitelist_query_limiting, only: [:index, :update, :bulk_move] before_action :authorize_read_issue, only: [:index] before_action :authorize_create_issue, only: [:create] before_action :authorize_update_issue, only: [:update] skip_before_action :authenticate_user!, only: [:index] + before_action :validate_id_list, only: [:bulk_move] + before_action :can_move_issues?, only: [:bulk_move] # rubocop: disable CodeReuse/ActiveRecord def index list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params) issues = list_service.execute issues = issues.page(params[:page]).per(params[:per] || 20).without_count - Issue.move_to_end(issues) if Gitlab::Database.read_write? + Issue.move_nulls_to_end(issues) if Gitlab::Database.read_write? issues = issues.preload(:milestone, :assignees, project: [ @@ -46,6 +54,14 @@ module Boards end end + def bulk_move + service = Boards::Issues::MoveService.new(board_parent, current_user, move_params(true)) + + issues = Issue.find(params[:ids]) + + render json: service.execute_multiple(issues) + end + def update service = Boards::Issues::MoveService.new(board_parent, current_user, move_params) @@ -58,6 +74,10 @@ module Boards private + def can_move_issues? + head(:forbidden) unless can?(current_user, :admin_issue, board) + end + def render_issues(issues, metadata) data = { issues: serialize_as_json(issues) } data.merge!(metadata) @@ -90,8 +110,9 @@ module Boards end end - def move_params - params.permit(:board_id, :id, :from_list_id, :to_list_id, :move_before_id, :move_after_id) + def move_params(multiple = false) + id_param = multiple ? :ids : :id + params.permit(id_param, :board_id, :from_list_id, :to_list_id, :move_before_id, :move_after_id) end def issue_params @@ -112,5 +133,10 @@ module Boards # Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42439 Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42428') end + + def validate_id_list + head(:bad_request) unless params[:ids].is_a?(Array) + head(:unprocessable_entity) if params[:ids].size > MAX_MOVE_ISSUES_COUNT + end end end diff --git a/app/controllers/chaos_controller.rb b/app/controllers/chaos_controller.rb index 8d518c14b90..ac008165c16 100644 --- a/app/controllers/chaos_controller.rb +++ b/app/controllers/chaos_controller.rb @@ -1,56 +1,83 @@ # frozen_string_literal: true class ChaosController < ActionController::Base - before_action :validate_request + before_action :validate_chaos_secret, unless: :development_or_test? def leakmem - memory_mb = (params[:memory_mb]&.to_i || 100) - duration_s = (params[:duration_s]&.to_i || 30).seconds + do_chaos :leak_mem, Chaos::LeakMemWorker, memory_mb, duration_s + end - start = Time.now - retainer = [] - # Add `n` 1mb chunks of memory to the retainer array - memory_mb.times { retainer << "x" * 1.megabyte } + def cpu_spin + do_chaos :cpu_spin, Chaos::CpuSpinWorker, duration_s + end - duration_taken = (Time.now - start).seconds - Kernel.sleep duration_s - duration_taken if duration_s > duration_taken + def db_spin + do_chaos :db_spin, Chaos::DbSpinWorker, duration_s, interval_s + end - render plain: "OK" + def sleep + do_chaos :sleep, Chaos::SleepWorker, duration_s end - def cpuspin - duration_s = (params[:duration_s]&.to_i || 30).seconds - end_time = Time.now + duration_s.seconds + def kill + do_chaos :kill, Chaos::KillWorker + end - rand while Time.now < end_time + private + + def do_chaos(method, worker, *args) + if async + worker.perform_async(*args) + else + Gitlab::Chaos.public_send(method, *args) # rubocop: disable GitlabSecurity/PublicSend + end render plain: "OK" end - def sleep - duration_s = (params[:duration_s]&.to_i || 30).seconds - Kernel.sleep duration_s + def validate_chaos_secret + unless chaos_secret_configured + render plain: "chaos misconfigured: please configure GITLAB_CHAOS_SECRET", + status: :internal_server_error + return + end - render plain: "OK" + unless Devise.secure_compare(chaos_secret_configured, chaos_secret_request) + render plain: "To experience chaos, please set a valid `X-Chaos-Secret` header or `token` param", + status: :unauthorized + return + end end - def kill - Process.kill("KILL", Process.pid) + def chaos_secret_configured + ENV['GITLAB_CHAOS_SECRET'] end - private + def chaos_secret_request + request.headers["HTTP_X_CHAOS_SECRET"] || params[:token] + end - def validate_request - secret = ENV['GITLAB_CHAOS_SECRET'] - # GITLAB_CHAOS_SECRET is required unless you're running in Development mode - if !secret && !Rails.env.development? - render plain: "chaos misconfigured: please configure GITLAB_CHAOS_SECRET when using GITLAB_ENABLE_CHAOS_ENDPOINTS outside of a development environment", status: :internal_server_error - end + def interval_s + interval_s = params[:interval_s] || 1 + interval_s.to_f.seconds + end - return unless secret + def duration_s + duration_s = params[:duration_s] || 30 + duration_s.to_i.seconds + end - unless request.headers["HTTP_X_CHAOS_SECRET"] == secret - render plain: "To experience chaos, please set X-Chaos-Secret header", status: :unauthorized - end + def memory_mb + memory_mb = params[:memory_mb] || 100 + memory_mb.to_i + end + + def async + async = params[:async] || false + Gitlab::Utils.to_boolean(async) + end + + def development_or_test? + Rails.env.development? || Rails.env.test? end end diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb index 4926062f9ca..8c8f0b3a22e 100644 --- a/app/controllers/concerns/authenticates_with_two_factor.rb +++ b/app/controllers/concerns/authenticates_with_two_factor.rb @@ -55,7 +55,7 @@ module AuthenticatesWithTwoFactor remember_me(user) if user_params[:remember_me] == '1' user.save! - sign_in(user, message: :two_factor_authenticated) + 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") @@ -72,7 +72,7 @@ module AuthenticatesWithTwoFactor session.delete(:challenge) remember_me(user) if user_params[:remember_me] == '1' - sign_in(user, message: :two_factor_authenticated) + 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=U2F") diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb new file mode 100644 index 00000000000..5a4b5897a4f --- /dev/null +++ b/app/controllers/concerns/confirm_email_warning.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module ConfirmEmailWarning + extend ActiveSupport::Concern + + included do + before_action :set_confirm_warning, if: -> { Feature.enabled?(:soft_email_confirmation) } + end + + protected + + def set_confirm_warning + return unless current_user + return if current_user.confirmed? + return if peek_request? || json_request? || !request.get? + + email = current_user.unconfirmed_email || current_user.email + + flash.now[:warning] = _("Please check your email (%{email}) to verify that you own this address. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.").html_safe % { + email: email, + resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post), + update_link: view_context.link_to(_('Update it'), profile_path) + } + end +end diff --git a/app/controllers/concerns/group_tree.rb b/app/controllers/concerns/group_tree.rb index e9a7d6a3152..d076c62c707 100644 --- a/app/controllers/concerns/group_tree.rb +++ b/app/controllers/concerns/group_tree.rb @@ -32,18 +32,14 @@ module GroupTree def filtered_groups_with_ancestors(groups) filtered_groups = groups.search(params[:filter]).page(params[:page]) - if Group.supports_nested_objects? - # We find the ancestors by ID of the search results here. - # Otherwise the ancestors would also have filters applied, - # which would cause them not to be preloaded. - # - # Pagination needs to be applied before loading the ancestors to - # make sure ancestors are not cut off by pagination. - Gitlab::ObjectHierarchy.new(Group.where(id: filtered_groups.select(:id))) - .base_and_ancestors - else - filtered_groups - end + # We find the ancestors by ID of the search results here. + # Otherwise the ancestors would also have filters applied, + # which would cause them not to be preloaded. + # + # Pagination needs to be applied before loading the ancestors to + # make sure ancestors are not cut off by pagination. + Gitlab::ObjectHierarchy.new(Group.where(id: filtered_groups.select(:id))) + .base_and_ancestors end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/controllers/concerns/invisible_captcha.rb b/app/controllers/concerns/invisible_captcha.rb new file mode 100644 index 00000000000..45c0a5c58ef --- /dev/null +++ b/app/controllers/concerns/invisible_captcha.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module InvisibleCaptcha + extend ActiveSupport::Concern + + included do + invisible_captcha only: :create, on_spam: :on_honeypot_spam_callback, on_timestamp_spam: :on_timestamp_spam_callback + end + + def on_honeypot_spam_callback + return unless Feature.enabled?(:invisible_captcha) + + invisible_captcha_honeypot_counter.increment + log_request('Invisible_Captcha_Honeypot_Request') + + head(200) + end + + def on_timestamp_spam_callback + return unless Feature.enabled?(:invisible_captcha) + + invisible_captcha_timestamp_counter.increment + log_request('Invisible_Captcha_Timestamp_Request') + + redirect_to new_user_session_path, alert: InvisibleCaptcha.timestamp_error_message + end + + def invisible_captcha_honeypot_counter + @invisible_captcha_honeypot_counter ||= + Gitlab::Metrics.counter(:bot_blocked_by_invisible_captcha_honeypot, + 'Counter of blocked sign up attempts with filled honeypot') + end + + def invisible_captcha_timestamp_counter + @invisible_captcha_timestamp_counter ||= + Gitlab::Metrics.counter(:bot_blocked_by_invisible_captcha_timestamp, + 'Counter of blocked sign up attempts with invalid timestamp') + end + + def log_request(message) + request_information = { + message: message, + env: :invisible_captcha_signup_bot_detected, + remote_ip: request.ip, + request_method: request.request_method, + path: request.fullpath + } + + Gitlab::AuthLogger.error(request_information) + end +end diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 6fa2f75be33..b86e4451a7e 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -98,13 +98,12 @@ module IssuableActions render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" } end - # rubocop: disable CodeReuse/ActiveRecord + # rubocop:disable CodeReuse/ActiveRecord def discussions - notes = issuable.discussion_notes - .inc_relations_for_view - .with_notes_filter(notes_filter) - .includes(:noteable) - .fresh + notes = NotesFinder.new(current_user, finder_params_for_issuable).execute + .inc_relations_for_view + .includes(:noteable) + .fresh if notes_filter != UserPreference::NOTES_FILTERS[:only_comments] notes = ResourceEvents::MergeIntoNotesService.new(issuable, current_user).execute(notes) @@ -117,7 +116,7 @@ module IssuableActions render json: discussion_serializer.represent(discussions, context: self) end - # rubocop: enable CodeReuse/ActiveRecord + # rubocop:enable CodeReuse/ActiveRecord private @@ -128,7 +127,8 @@ module IssuableActions # GitLab Geo does not expect database UPDATE or INSERT statements to happen # on GET requests. # This is just a fail-safe in case notes_filter is sent via GET request in GitLab Geo. - if Gitlab::Database.read_only? + # In some cases, we also force the filter to not be persisted with the `persist_filter` param + if Gitlab::Database.read_only? || params[:persist_filter] == 'false' notes_filter_param || current_user&.notes_filter_for(issuable) else notes_filter = current_user&.set_notes_filter(notes_filter_param, issuable) || notes_filter_param @@ -222,4 +222,13 @@ module IssuableActions def parent @project || @group # rubocop:disable Gitlab/ModuleWithInstanceVariables end + + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def finder_params_for_issuable + { + target: @issuable, + notes_filter: notes_filter + }.tap { |new_params| new_params[:project] = project if respond_to?(:project, true) } + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables end diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 21b3949e361..8ea77b994de 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -2,8 +2,8 @@ module IssuableCollections extend ActiveSupport::Concern - include CookiesHelper include SortingHelper + include SortingPreference include Gitlab::IssuableMetadata include Gitlab::Utils::StrongMemoize @@ -127,47 +127,8 @@ module IssuableCollections 'opened' end - def set_sort_order - set_sort_order_from_user_preference || set_sort_order_from_cookie || default_sort_order - end - - def set_sort_order_from_user_preference - return unless current_user - return unless issuable_sorting_field - - user_preference = current_user.user_preference - - sort_param = params[:sort] - sort_param ||= user_preference[issuable_sorting_field] - - return sort_param if Gitlab::Database.read_only? - - if user_preference[issuable_sorting_field] != sort_param - user_preference.update(issuable_sorting_field => sort_param) - end - - sort_param - end - - # Implement issuable_sorting_field method on controllers - # to choose which column to store the sorting parameter. - def issuable_sorting_field - nil - end - - def set_sort_order_from_cookie - sort_param = params[:sort] if params[:sort].present? - # fallback to legacy cookie value for backward compatibility - sort_param ||= cookies['issuable_sort'] - sort_param ||= cookies[remember_sorting_key] - - sort_value = update_cookie_value(sort_param) - set_secure_cookie(remember_sorting_key, sort_value) - sort_value - end - - def remember_sorting_key - @remember_sorting_key ||= "#{collection_type.downcase}_sort" + def legacy_sort_cookie_name + 'issuable_sort' end def default_sort_order @@ -178,26 +139,14 @@ module IssuableCollections end end - # Update old values to the actual ones. - def update_cookie_value(value) - case value - when 'id_asc' then sort_value_oldest_created - when 'id_desc' then sort_value_recently_created - when 'downvotes_asc' then sort_value_popularity - when 'downvotes_desc' then sort_value_popularity - else value - end - end - def finder @finder ||= issuable_finder_for(finder_type) end def collection_type - @collection_type ||= case finder_type.name - when 'IssuesFinder' + @collection_type ||= if finder_type <= IssuesFinder 'Issue' - when 'MergeRequestsFinder' + elsif finder_type <= MergeRequestsFinder 'MergeRequest' end end diff --git a/app/controllers/concerns/issuable_collections_action.rb b/app/controllers/concerns/issuable_collections_action.rb index 4ad287c4a13..0a6f684a9fc 100644 --- a/app/controllers/concerns/issuable_collections_action.rb +++ b/app/controllers/concerns/issuable_collections_action.rb @@ -32,7 +32,7 @@ module IssuableCollectionsAction private - def issuable_sorting_field + def sorting_field case action_name when 'issues' Issue::SORTING_PREFERENCE_FIELD diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index 0098c4cdf4c..4b7899d469b 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -73,6 +73,11 @@ module NotesActions # rubocop:disable Gitlab/ModuleWithInstanceVariables def update @note = Notes::UpdateService.new(project, current_user, update_note_params).execute(note) + unless @note + head :gone + return + end + prepare_notes_for_rendering([@note]) respond_to do |format| @@ -243,7 +248,7 @@ module NotesActions end def notes_finder - @notes_finder ||= NotesFinder.new(project, current_user, finder_params) + @notes_finder ||= NotesFinder.new(current_user, finder_params) end def note_serializer diff --git a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb index f47ead2f0da..2e9905997db 100644 --- a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb +++ b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb @@ -28,7 +28,7 @@ module RequiresWhitelistedMonitoringClient def valid_token? token = params[:token].presence || request.headers['TOKEN'] token.present? && - ActiveSupport::SecurityUtils.variable_size_secure_compare( + ActiveSupport::SecurityUtils.secure_compare( token, Gitlab::CurrentSettings.health_check_access_token ) diff --git a/app/controllers/concerns/sessionless_authentication.rb b/app/controllers/concerns/sessionless_authentication.rb index 590eefc6dab..4304b8565ce 100644 --- a/app/controllers/concerns/sessionless_authentication.rb +++ b/app/controllers/concerns/sessionless_authentication.rb @@ -13,7 +13,7 @@ module SessionlessAuthentication end def sessionless_user? - current_user && !session.keys.include?('warden.user.user.key') + current_user && !session.key?('warden.user.user.key') end def sessionless_sign_in(user) diff --git a/app/controllers/concerns/sorting_preference.rb b/app/controllers/concerns/sorting_preference.rb new file mode 100644 index 00000000000..a51b68147d5 --- /dev/null +++ b/app/controllers/concerns/sorting_preference.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module SortingPreference + include SortingHelper + include CookiesHelper + + def set_sort_order + set_sort_order_from_user_preference || set_sort_order_from_cookie || params[:sort] || default_sort_order + end + + # Implement sorting_field method on controllers + # to choose which column to store the sorting parameter. + def sorting_field + nil + end + + # Implement default_sort_order method on controllers + # to choose which default sort should be applied if + # sort param is not provided. + def default_sort_order + nil + end + + # Implement legacy_sort_cookie_name method on controllers + # to set sort from cookie for backwards compatibility. + def legacy_sort_cookie_name + nil + end + + private + + def set_sort_order_from_user_preference + return unless current_user + return unless sorting_field + + user_preference = current_user.user_preference + + sort_param = params[:sort] + sort_param ||= user_preference[sorting_field] + + return sort_param if Gitlab::Database.read_only? + + if user_preference[sorting_field] != sort_param + user_preference.update(sorting_field => sort_param) + end + + sort_param + end + + def set_sort_order_from_cookie + return unless legacy_sort_cookie_name + + sort_param = params[:sort] if params[:sort].present? + # fallback to legacy cookie value for backward compatibility + sort_param ||= cookies[legacy_sort_cookie_name] + sort_param ||= cookies[remember_sorting_key] + + sort_value = update_cookie_value(sort_param) + set_secure_cookie(remember_sorting_key, sort_value) + sort_value + end + + # Convert sorting_field to legacy cookie name for backwards compatibility + # :merge_requests_sort => 'mergerequest_sort' + # :issues_sort => 'issue_sort' + def remember_sorting_key + @remember_sorting_key ||= sorting_field + .to_s + .split('_')[0..-2] + .map(&:singularize) + .join('') + .concat('_sort') + end + + # Update old values to the actual ones. + def update_cookie_value(value) + case value + when 'id_asc' then sort_value_oldest_created + when 'id_desc' then sort_value_recently_created + when 'downvotes_asc' then sort_value_popularity + when 'downvotes_desc' then sort_value_popularity + else value + end + end +end diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb index 97b343f8b1a..24d178781d6 100644 --- a/app/controllers/concerns/toggle_award_emoji.rb +++ b/app/controllers/concerns/toggle_award_emoji.rb @@ -7,12 +7,9 @@ module ToggleAwardEmoji authenticate_user! name = params.require(:name) - if awardable.user_can_award?(current_user) - awardable.toggle_award_emoji(name, current_user) - - todoable = to_todoable(awardable) - TodoService.new.new_award_emoji(todoable, current_user) if todoable + service = AwardEmojis::ToggleService.new(awardable, name, current_user).execute + if service[:status] == :success render json: { ok: true } else render json: { ok: false } @@ -21,18 +18,6 @@ module ToggleAwardEmoji private - def to_todoable(awardable) - case awardable - when Note - # we don't create todos for personal snippet comments for now - awardable.for_personal_snippet? ? nil : awardable.noteable - when MergeRequest, Issue - awardable - when Snippet - nil - end - end - def awardable raise NotImplementedError end diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb index 59f6d3452a3..f5d35379e10 100644 --- a/app/controllers/concerns/uploads_actions.rb +++ b/app/controllers/concerns/uploads_actions.rb @@ -90,7 +90,7 @@ module UploadsActions return unless uploader = build_uploader upload_paths = uploader.upload_paths(params[:filename]) - upload = Upload.find_by(uploader: uploader_class.to_s, path: upload_paths) + upload = Upload.find_by(model: model, uploader: uploader_class.to_s, path: upload_paths) upload&.build_uploader end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/controllers/concerns/with_performance_bar.rb b/app/controllers/concerns/with_performance_bar.rb index 77c3d476ac6..4e0ae3c59eb 100644 --- a/app/controllers/concerns/with_performance_bar.rb +++ b/app/controllers/concerns/with_performance_bar.rb @@ -3,10 +3,6 @@ module WithPerformanceBar extend ActiveSupport::Concern - included do - include Peek::Rblineprof::CustomControllerHelpers - end - def peek_enabled? return false unless Gitlab::PerformanceBar.enabled?(current_user) diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index 2ae500a2fdf..b192189ba3c 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -11,7 +11,7 @@ class ConfirmationsController < Devise::ConfirmationsController protected def after_resending_confirmation_instructions_path_for(resource) - users_almost_there_path + Feature.enabled?(:soft_email_confirmation) ? stored_location_for(resource) || dashboard_projects_path : users_almost_there_path end def after_confirmation_path_for(resource_name, resource) diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index daeb8fda417..1dc89943f7f 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -4,10 +4,12 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController include ParamsBackwardCompatibility include RendersMemberAccess include OnboardingExperimentHelper + include SortingHelper + include SortingPreference prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) } before_action :set_non_archived_param - before_action :default_sorting + before_action :set_sorting before_action :projects, only: [:index] skip_cross_project_access_check :index, :starred @@ -59,11 +61,6 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController end end - def default_sorting - params[:sort] ||= 'latest_activity_desc' - @sort = params[:sort] - end - # rubocop: disable CodeReuse/ActiveRecord def load_projects(finder_params) @total_user_projects_count = ProjectsFinder.new(params: { non_public: true }, current_user: current_user).execute @@ -73,6 +70,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController .new(params: finder_params, current_user: current_user) .execute .includes(:route, :creator, :group, namespace: [:route, :owner]) + .preload(:project_feature) .page(finder_params[:page]) prepare_projects_for_rendering(projects) @@ -88,4 +86,17 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?) end + + def set_sorting + params[:sort] = set_sort_order + @sort = params[:sort] + end + + def default_sort_order + sort_value_latest_activity + end + + def sorting_field + Project::SORTING_PREFERENCE_FIELD + end end diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index ef86d5f981a..271f2b4b57d 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -3,12 +3,13 @@ class Explore::ProjectsController < Explore::ApplicationController include ParamsBackwardCompatibility include RendersMemberAccess + include SortingHelper + include SortingPreference before_action :set_non_archived_param + before_action :set_sorting def index - params[:sort] ||= 'latest_activity_desc' - @sort = params[:sort] @projects = load_projects respond_to do |format| @@ -23,7 +24,6 @@ class Explore::ProjectsController < Explore::ApplicationController def trending params[:trending] = true - @sort = params[:sort] @projects = load_projects respond_to do |format| @@ -67,4 +67,17 @@ class Explore::ProjectsController < Explore::ApplicationController prepare_projects_for_rendering(projects) end # rubocop: enable CodeReuse/ActiveRecord + + def set_sorting + params[:sort] = set_sort_order + @sort = params[:sort] + end + + def default_sort_order + sort_value_latest_activity + end + + def sorting_field + Project::SORTING_PREFERENCE_FIELD + end end diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 9fbbe373b0d..72d40f709e6 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -30,6 +30,10 @@ class GraphqlController < ApplicationController render_error(exception.message, status: :unprocessable_entity) end + rescue_from Gitlab::Graphql::Errors::ArgumentError do |exception| + render_error(exception.message, status: :unprocessable_entity) + end + private def execute_multiplex diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index f1d6fb00cfc..557888711ec 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -5,6 +5,8 @@ class Groups::GroupMembersController < Groups::ApplicationController include MembersPresentation include SortingHelper + MEMBER_PER_PAGE_LIMIT = 50 + def self.admin_not_required_endpoints %i[index leave request_access] end @@ -24,7 +26,14 @@ class Groups::GroupMembersController < Groups::ApplicationController @project = @group.projects.find(params[:project_id]) if params[:project_id] @members = GroupMembersFinder.new(@group).execute - @members = @members.non_invite unless can_manage_members + + if can_manage_members + @invited_members = @members.invite + @invited_members = @invited_members.search_invite_email(params[:search_invited]) if params[:search_invited].present? + @invited_members = present_members(@invited_members.page(params[:invited_members_page]).per(MEMBER_PER_PAGE_LIMIT)) + end + + @members = @members.non_invite @members = @members.search(params[:search]) if params[:search].present? @members = @members.sort_by_attribute(@sort) @@ -32,7 +41,7 @@ class Groups::GroupMembersController < Groups::ApplicationController @members = @members.filter_by_2fa(params[:two_factor]) end - @members = @members.page(params[:page]).per(50) + @members = @members.page(params[:page]).per(MEMBER_PER_PAGE_LIMIT) @members = present_members(@members) @requesters = present_members( diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 797833e3f91..886d1f99d69 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -7,10 +7,6 @@ class GroupsController < Groups::ApplicationController include PreviewMarkdown include RecordUserLastActivity - before_action do - push_frontend_feature_flag(:manual_sorting) - end - respond_to :html prepend_before_action(only: [:show, :issues]) { authenticate_sessionless_user!(:rss) } @@ -107,7 +103,7 @@ class GroupsController < Groups::ApplicationController if Groups::UpdateService.new(@group, current_user, group_params).execute redirect_to edit_group_path(@group, anchor: params[:update_section]), notice: "Group '#{@group.name}' was successfully updated." else - @group.restore_path! + @group.path = @group.path_before_last_save || @group.path_was render action: "edit" end @@ -180,6 +176,7 @@ class GroupsController < Groups::ApplicationController [ :avatar, :description, + :emails_disabled, :lfs_enabled, :name, :path, @@ -192,7 +189,8 @@ class GroupsController < Groups::ApplicationController :chat_team_name, :require_two_factor_authentication, :two_factor_grace_period, - :project_creation_level + :project_creation_level, + :subgroup_creation_level ] end diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb index eeeebe430a7..af1e6cc703b 100644 --- a/app/controllers/ide_controller.rb +++ b/app/controllers/ide_controller.rb @@ -4,5 +4,6 @@ class IdeController < ApplicationController layout 'fullscreen' def index + Gitlab::UsageDataCounters::WebIdeCounter.increment_views_count end end diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb index f71ea8642cd..dc72a4e4fd9 100644 --- a/app/controllers/import/bitbucket_server_controller.rb +++ b/app/controllers/import/bitbucket_server_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Import::BitbucketServerController < Import::BaseController + include ActionView::Helpers::SanitizeHelper + before_action :verify_bitbucket_server_import_enabled before_action :bitbucket_auth, except: [:new, :configure] before_action :validate_import_params, only: [:create] @@ -57,7 +59,7 @@ class Import::BitbucketServerController < Import::BaseController # rubocop: disable CodeReuse/ActiveRecord def status - @collection = bitbucket_client.repos(page_offset: page_offset, limit: limit_per_page) + @collection = bitbucket_client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param) @repos, @incompatible_repos = @collection.partition { |repo| repo.valid? } # Use the import URL to filter beyond what BaseService#find_already_added_projects @@ -147,4 +149,8 @@ class Import::BitbucketServerController < Import::BaseController def limit_per_page BitbucketServer::Paginator::PAGE_LENGTH end + + def sanitized_filter_param + sanitize(params[:filter]) + end end diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index aa4aa0fbdac..ebb50fc8b10 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -10,7 +10,7 @@ class Import::GithubController < Import::BaseController rescue_from Octokit::Unauthorized, with: :provider_unauthorized def new - if github_import_configured? && logged_in_with_provider? + if !ci_cd_only? && github_import_configured? && logged_in_with_provider? go_to_provider_for_permissions elsif session[access_token_key] redirect_to status_import_url @@ -169,11 +169,15 @@ class Import::GithubController < Import::BaseController # rubocop: enable CodeReuse/ActiveRecord def provider_auth - if session[access_token_key].blank? + if !ci_cd_only? && session[access_token_key].blank? go_to_provider_for_permissions end end + def ci_cd_only? + %w[1 true].include?(params[:ci_cd_only]) + end + def client_options {} end diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 5ecf4f114cf..da39d64c93d 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class JwtController < ApplicationController + skip_around_action :set_session_storage skip_before_action :authenticate_user! skip_before_action :verify_authenticity_token before_action :authenticate_project_or_user diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 2a8dd997d04..b1efa767154 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -139,7 +139,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController if user.two_factor_enabled? && !auth_user.bypass_two_factor? prompt_for_two_factor(user) else - sign_in_and_redirect(user) + sign_in_and_redirect(user, event: :authentication) end else fail_login(user) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 2ef18d900f2..da8a371acaa 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -92,7 +92,10 @@ class Projects::ArtifactsController < Projects::ApplicationController def build_from_ref return unless @ref_name - project.latest_successful_build_for(params[:job], @ref_name) + commit = project.commit(@ref_name) + return unless commit + + project.latest_successful_build_for_sha(params[:job], commit.id) end def artifacts_file diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb index 09a384e89ab..66b51b17790 100644 --- a/app/controllers/projects/badges_controller.rb +++ b/app/controllers/projects/badges_controller.rb @@ -3,7 +3,8 @@ class Projects::BadgesController < Projects::ApplicationController layout 'project_settings' before_action :authorize_admin_project!, only: [:index] - before_action :no_cache_headers, except: [:index] + before_action :no_cache_headers, only: [:pipeline, :coverage] + before_action :authorize_read_build!, only: [:pipeline, :coverage] def pipeline pipeline_status = Gitlab::Badge::Pipeline::Status diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index b04ffe80db4..4125f44d00a 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -92,7 +92,7 @@ class Projects::BlobController < Projects::ApplicationController def diff apply_diff_view_cookie! - @form = Blobs::UnfoldPresenter.new(blob, params.to_unsafe_h) + @form = Blobs::UnfoldPresenter.new(blob, diff_params) # keep only json rendering when # https://gitlab.com/gitlab-org/gitlab-ce/issues/44988 is done @@ -239,4 +239,8 @@ class Projects::BlobController < Projects::ApplicationController def tree_path @path.rpartition('/').first end + + def diff_params + params.permit(:full, :since, :to, :bottom, :unfold, :offset, :indent) + end end diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 141a7dfb923..e7bdb4b2042 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -49,7 +49,7 @@ class Projects::BranchesController < Projects::ApplicationController branches = BranchesFinder.new(repository, params.permit(names: [])).execute Gitlab::GitalyClient.allow_n_plus_1_calls do - render json: branches.to_h { |branch| [branch.name, service.call(branch)] } + render json: branches.map { |branch| [branch.name, service.call(branch)] }.to_h end end end diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb index 4274c356227..99f4524eec5 100644 --- a/app/controllers/projects/build_artifacts_controller.rb +++ b/app/controllers/projects/build_artifacts_controller.rb @@ -51,6 +51,6 @@ class Projects::BuildArtifactsController < Projects::ApplicationController def job_from_ref return unless @ref_name - project.latest_successful_build_for(params[:job], @ref_name) + project.latest_successful_build_for_ref(params[:job], @ref_name) end end diff --git a/app/controllers/projects/cycle_analytics/events_controller.rb b/app/controllers/projects/cycle_analytics/events_controller.rb index fb43356ff10..926592b9681 100644 --- a/app/controllers/projects/cycle_analytics/events_controller.rb +++ b/app/controllers/projects/cycle_analytics/events_controller.rb @@ -23,7 +23,7 @@ module Projects end def test - options(events_params)[:branch] = events_params[:branch_name] + options(cycle_analytics_params)[:branch] = cycle_analytics_params[:branch_name] render_events(cycle_analytics[:test].events) end @@ -50,13 +50,13 @@ module Projects end def cycle_analytics - @cycle_analytics ||= ::CycleAnalytics.new(project, options(events_params)) + @cycle_analytics ||= ::CycleAnalytics::ProjectLevel.new(project, options: options(cycle_analytics_params)) end - def events_params - return {} unless params[:events].present? + def cycle_analytics_params + return {} unless params[:cycle_analytics].present? - params[:events].permit(:start_date, :branch_name) + params[:cycle_analytics].permit(:start_date, :branch_name) end end end diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb index 8c071496ba9..3b0abecf2c9 100644 --- a/app/controllers/projects/cycle_analytics_controller.rb +++ b/app/controllers/projects/cycle_analytics_controller.rb @@ -9,13 +9,19 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController before_action :authorize_read_cycle_analytics! def show - @cycle_analytics = ::CycleAnalytics.new(@project, options(cycle_analytics_params)) + @cycle_analytics = ::CycleAnalytics::ProjectLevel.new(@project, options: options(cycle_analytics_params)) @cycle_analytics_no_data = @cycle_analytics.no_stats? respond_to do |format| - format.html - format.json { render json: cycle_analytics_json } + format.html do + Gitlab::UsageDataCounters::CycleAnalyticsCounter.count(:views) + + render :show + end + format.json do + render json: cycle_analytics_json + end end end diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index ecf05e6ea64..5a1f93dc609 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -11,8 +11,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController before_action :verify_api_request!, only: :terminal_websocket_authorize before_action :expire_etag_cache, only: [:index] before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do - push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint) + push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint, default_enabled: true) push_frontend_feature_flag(:environment_metrics_show_multiple_dashboards) + push_frontend_feature_flag(:environment_metrics_additional_panel_types) push_frontend_feature_flag(:prometheus_computed_alerts) end @@ -159,20 +160,23 @@ class Projects::EnvironmentsController < Projects::ApplicationController end def metrics_dashboard - return render_403 unless Feature.enabled?(:environment_metrics_use_prometheus_endpoint, project) - - if Feature.enabled?(:environment_metrics_show_multiple_dashboards, project) + if params[:embedded] result = dashboard_finder.find( project, current_user, environment, dashboard_path: params[:dashboard], - embedded: params[:embedded] + **dashboard_params.to_h.symbolize_keys + ) + elsif Feature.enabled?(:environment_metrics_show_multiple_dashboards, project) + result = dashboard_finder.find( + project, + current_user, + environment, + dashboard_path: params[:dashboard] ) - unless params[:embedded] - result[:all_dashboards] = dashboard_finder.find_all_paths(project) - end + result[:all_dashboards] = dashboard_finder.find_all_paths(project) else result = dashboard_finder.find(project, current_user, environment) end @@ -230,6 +234,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController params.require([:start, :end]) end + def dashboard_params + params.permit(:embedded, :group, :title, :y_label) + end + def dashboard_finder Gitlab::Metrics::Dashboard::Finder end diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 956093b972b..abf8407a51c 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -49,7 +49,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController send_final_spnego_response return # Allow access end - elsif project && download_request? && Guest.can?(:download_code, project) + elsif project && download_request? && http_allowed? && Guest.can?(:download_code, project) + @authentication_result = Gitlab::Auth::Result.new(nil, project, :none, [:download_code]) return # Allow access @@ -113,4 +114,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController def ci? authentication_result.ci?(project) end + + def http_allowed? + Gitlab::ProtocolAccess.allowed?('http') + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 228de8bc6f3..b7fd286bfe0 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -10,10 +10,6 @@ class Projects::IssuesController < Projects::ApplicationController include SpammableActions include RecordUserLastActivity - before_action do - push_frontend_feature_flag(:manual_sorting) - end - def issue_except_actions %i[index calendar new create bulk_update import_csv] end @@ -194,7 +190,7 @@ class Projects::IssuesController < Projects::ApplicationController protected - def issuable_sorting_field + def sorting_field Issue::SORTING_PREFERENCE_FIELD end diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index 02ff6e872c9..adbc0159358 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -94,7 +94,7 @@ class Projects::JobsController < Projects::ApplicationController def play return respond_422 unless @build.playable? - build = @build.play(current_user) + build = @build.play(current_user, play_params[:job_variables_attributes]) redirect_to build_path(build) end @@ -190,6 +190,10 @@ class Projects::JobsController < Projects::ApplicationController { query: { 'response-content-type' => 'text/plain; charset=utf-8', 'response-content-disposition' => 'inline' } } end + def play_params + params.permit(job_variables_attributes: %i[key secret_value]) + end + def trace_artifact_file @trace_artifact_file ||= build.job_artifacts_trace&.file end diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb index dcc272aecff..006731c0e66 100644 --- a/app/controllers/projects/merge_requests/application_controller.rb +++ b/app/controllers/projects/merge_requests/application_controller.rb @@ -45,7 +45,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont def set_pipeline_variables @pipelines = - if can?(current_user, :read_pipeline, @project) + if can?(current_user, :read_pipeline, @merge_request.source_project) @merge_request.all_pipelines else Ci::Pipeline.none diff --git a/app/controllers/projects/merge_requests/content_controller.rb b/app/controllers/projects/merge_requests/content_controller.rb index 6e026b83ee3..eec5c1a4355 100644 --- a/app/controllers/projects/merge_requests/content_controller.rb +++ b/app/controllers/projects/merge_requests/content_controller.rb @@ -7,16 +7,33 @@ class Projects::MergeRequests::ContentController < Projects::MergeRequests::Appl # for other types of serialization before_action :close_merge_request_if_no_source_project + before_action :set_polling_header around_action :allow_gitaly_ref_name_caching def widget respond_to do |format| format.json do - Gitlab::PollingInterval.set_header(response, interval: 10_000) + render json: serializer(MergeRequestPollWidgetEntity) + end + end + end - serializer = MergeRequestSerializer.new(current_user: current_user, project: merge_request.project) - render json: serializer.represent(merge_request, serializer: 'widget') + def cached_widget + respond_to do |format| + format.json do + render json: serializer(MergeRequestPollCachedWidgetEntity) end end end + + private + + def set_polling_header + Gitlab::PollingInterval.set_header(response, interval: 10_000) + end + + def serializer(entity) + serializer = MergeRequestSerializer.new(current_user: current_user, project: merge_request.project) + serializer.represent(merge_request, {}, entity) + end end diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index 32cefe54613..6ac5bb90706 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -23,6 +23,8 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap @merge_request = ::MergeRequests::CreateService.new(project, current_user, merge_request_params).execute if @merge_request.valid? + incr_count_webide_merge_request + redirect_to(merge_request_path(@merge_request)) else @source_project = @merge_request.source_project @@ -135,4 +137,10 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap def whitelist_query_limiting Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42384') end + + def incr_count_webide_merge_request + return if params[:nav_source] != 'webide' + + Gitlab::UsageDataCounters::WebIdeCounter.increment_merge_requests_count + end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2aa2508be16..f4cc0a5851b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -82,7 +82,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end def pipelines - @pipelines = @merge_request.all_pipelines.page(params[:page]).per(30) + set_pipeline_variables + @pipelines = @pipelines.page(params[:page]).per(30) Gitlab::PollingInterval.set_header(response, interval: 10_000) @@ -218,7 +219,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo alias_method :issuable, :merge_request alias_method :awardable, :merge_request - def issuable_sorting_field + def sorting_field MergeRequest::SORTING_PREFERENCE_FIELD end diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 3152a38fd8e..13e8453ed00 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -6,7 +6,7 @@ class Projects::NotesController < Projects::ApplicationController include NotesHelper include ToggleAwardEmoji - before_action :whitelist_query_limiting, only: [:create] + before_action :whitelist_query_limiting, only: [:create, :update] before_action :authorize_read_note! before_action :authorize_create_note!, only: [:create] before_action :authorize_resolve_note!, only: [:resolve, :unresolve] @@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController alias_method :awardable, :note def finder_params - params.merge(last_fetched_at: last_fetched_at, notes_filter: notes_filter) + params.merge(project: project, last_fetched_at: last_fetched_at, notes_filter: notes_filter) end def authorize_admin_note! diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index db3b7c8b177..499d4918899 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -3,6 +3,7 @@ class Projects::PipelinesController < Projects::ApplicationController before_action :whitelist_query_limiting, only: [:create, :retry] before_action :pipeline, except: [:index, :new, :create, :charts] + before_action :set_pipeline_path, only: [:show] before_action :authorize_read_pipeline! before_action :authorize_read_build!, only: [:index] before_action :authorize_create_pipeline!, only: [:new, :create] @@ -174,14 +175,36 @@ class Projects::PipelinesController < Projects::ApplicationController # rubocop: disable CodeReuse/ActiveRecord def pipeline - @pipeline ||= project - .all_pipelines - .includes(user: :status) - .find_by!(id: params[:id]) - .present(current_user: current_user) + @pipeline ||= if params[:id].blank? && params[:latest] + latest_pipeline + else + project + .all_pipelines + .includes(builds: :tags, user: :status) + .find_by!(id: params[:id]) + .present(current_user: current_user) + end end # rubocop: enable CodeReuse/ActiveRecord + def set_pipeline_path + @pipeline_path ||= if params[:id].blank? && params[:latest] + latest_project_pipelines_path(@project, params['ref']) + else + project_pipeline_path(@project, @pipeline) + end + end + + def latest_pipeline + ref = params['ref'].presence || @project.default_branch + sha = @project.commit(ref)&.sha + + @project.ci_pipelines + .newest_first(ref: ref, sha: sha) + .first + &.present(current_user: current_user) + end + def whitelist_query_limiting # Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42343 Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42339') diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index 42ae5b0ef3c..c94fdd9483d 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -8,10 +8,30 @@ class Projects::RawController < Projects::ApplicationController before_action :require_non_empty_project before_action :assign_ref_vars before_action :authorize_download_code! + before_action :show_rate_limit, only: [:show] def show @blob = @repository.blob_at(@commit.id, @path) send_blob(@repository, @blob, inline: (params[:inline] != 'false')) end + + private + + def show_rate_limit + limiter = ::Gitlab::ActionRateLimiter.new(action: :show_raw_controller) + + return unless limiter.throttled?([@project, @commit, @path], raw_blob_request_limit) + + limiter.log_request(request, :raw_blob_request_limit, current_user) + + flash[:alert] = _('You cannot access the raw file. Please wait a minute.') + redirect_to project_blob_path(@project, File.join(@ref, @path)), status: :too_many_requests + end + + def raw_blob_request_limit + Gitlab::CurrentSettings + .current_application_settings + .raw_blob_request_limit + end end diff --git a/app/controllers/projects/registry/tags_controller.rb b/app/controllers/projects/registry/tags_controller.rb index bf1d8d8b5fc..54e2faa2dd7 100644 --- a/app/controllers/projects/registry/tags_controller.rb +++ b/app/controllers/projects/registry/tags_controller.rb @@ -5,6 +5,8 @@ module Projects class TagsController < ::Projects::Registry::ApplicationController before_action :authorize_destroy_container_image!, only: [:destroy] + LIMIT = 15 + def index respond_to do |format| format.json do @@ -28,10 +30,40 @@ module Projects end end + def bulk_destroy + unless params[:ids].present? + head :bad_request + return + end + + tag_names = params[:ids] || [] + if tag_names.size > LIMIT + head :bad_request + return + end + + @tags = tag_names.map { |tag_name| image.tag(tag_name) } + unless @tags.all? { |tag| tag.valid_name? } + head :bad_request + return + end + + success_count = 0 + @tags.each do |tag| + if tag.delete + success_count += 1 + end + end + + respond_to do |format| + format.json { head(success_count == @tags.size ? :no_content : :bad_request) } + end + end + private def tags - Kaminari::PaginatableArray.new(image.tags, limit: 15) + Kaminari::PaginatableArray.new(image.tags, limit: LIMIT) end def image diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index 3b4215b766e..a51759641e4 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -6,6 +6,7 @@ class Projects::RepositoriesController < Projects::ApplicationController # Authorize before_action :require_non_empty_project, except: :create before_action :assign_archive_vars, only: :archive + before_action :assign_append_sha, only: :archive before_action :authorize_download_code! before_action :authorize_admin_project!, only: :create @@ -16,19 +17,64 @@ class Projects::RepositoriesController < Projects::ApplicationController end def archive - append_sha = params[:append_sha] + set_cache_headers + return if archive_not_modified? - if @ref - shortname = "#{@project.path}-#{@ref.tr('/', '-')}" - append_sha = false if @filename == shortname - end - - send_git_archive @repository, ref: @ref, path: params[:path], format: params[:format], append_sha: append_sha + send_git_archive @repository, **repo_params rescue => ex logger.error("#{self.class.name}: #{ex}") git_not_found! end + private + + def repo_params + @repo_params ||= { ref: @ref, path: params[:path], format: params[:format], append_sha: @append_sha } + end + + def set_cache_headers + expires_in cache_max_age(archive_metadata['CommitId']), public: project.public? + fresh_when(etag: archive_metadata['ArchivePath']) + end + + def archive_not_modified? + # Check response freshness (Last-Modified and ETag) + # against request If-Modified-Since and If-None-Match conditions. + request.fresh?(response) + end + + def archive_metadata + @archive_metadata ||= @repository.archive_metadata( + @ref, + '', # Where archives are stored isn't really important for ETag purposes + repo_params[:format], + path: repo_params[:path], + append_sha: @append_sha + ) + end + + def cache_max_age(commit_id) + if @ref == commit_id + # This is a link to an archive by a commit SHA. That means that the archive + # is immutable. The only reason to invalidate the cache is if the commit + # was deleted or if the user lost access to the repository. + Repository::ARCHIVE_CACHE_TIME_IMMUTABLE + else + # A branch or tag points at this archive. That means that the expected archive + # content may change over time. + Repository::ARCHIVE_CACHE_TIME + end + end + + def assign_append_sha + @append_sha = params[:append_sha] + + if @ref + shortname = "#{@project.path}-#{@ref.tr('/', '-')}" + @append_sha = false if @filename == shortname + end + end + def assign_archive_vars if params[:id] @ref, @filename = extract_ref(params[:id]) diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 255f1f3569a..59f948959d6 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -7,7 +7,8 @@ class Projects::SnippetsController < Projects::ApplicationController include SnippetsActions include RendersBlob - skip_before_action :verify_authenticity_token, only: [:show], if: :js_request? + skip_before_action :verify_authenticity_token, + if: -> { action_name == 'show' && js_request? } before_action :check_snippets_available! before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] diff --git a/app/controllers/projects/starrers_controller.rb b/app/controllers/projects/starrers_controller.rb new file mode 100644 index 00000000000..4efe956e973 --- /dev/null +++ b/app/controllers/projects/starrers_controller.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class Projects::StarrersController < Projects::ApplicationController + include SortingHelper + + def index + @starrers = UsersStarProjectsFinder.new(@project, params, current_user: @current_user).execute + @sort = params[:sort].presence || sort_value_name + @starrers = @starrers.preload_users.sort_by_attribute(@sort).page(params[:page]) + @public_count = @project.starrers.with_public_profile.size + @total_count = @project.starrers.size + @private_count = @total_count - @public_count + end + + private + + def has_starred_project?(starrers) + starrers.first { |starrer| starrer.user_id == current_user.id } + end +end diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb index 284e119ca06..7159d0243a3 100644 --- a/app/controllers/projects/triggers_controller.rb +++ b/app/controllers/projects/triggers_controller.rb @@ -4,7 +4,7 @@ class Projects::TriggersController < Projects::ApplicationController before_action :authorize_admin_build! before_action :authorize_manage_trigger!, except: [:index, :create] before_action :authorize_admin_trigger!, only: [:edit, :update] - before_action :trigger, only: [:take_ownership, :edit, :update, :destroy] + before_action :trigger, only: [:edit, :update, :destroy] layout 'project_settings' @@ -24,16 +24,6 @@ class Projects::TriggersController < Projects::ApplicationController redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers') end - def take_ownership - if trigger.update(owner: current_user) - flash[:notice] = _('Trigger was re-assigned.') - else - flash[:alert] = _('You could not take ownership of trigger.') - end - - redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers') - end - def edit end diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb index 646728e8167..1dffc57fcf0 100644 --- a/app/controllers/projects/variables_controller.rb +++ b/app/controllers/projects/variables_controller.rb @@ -38,6 +38,6 @@ class Projects::VariablesController < Projects::ApplicationController end def variable_params_attributes - %i[id variable_type key secret_value protected masked _destroy] + %i[id variable_type key secret_value protected masked environment_scope _destroy] end end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index fa5bdbc7d49..b187fdb2723 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -6,15 +6,20 @@ class Projects::WikisController < Projects::ApplicationController include Gitlab::Utils::StrongMemoize before_action :authorize_read_wiki! - before_action :authorize_create_wiki!, only: [:edit, :create, :history] + before_action :authorize_create_wiki!, only: [:edit, :create] before_action :authorize_admin_wiki!, only: :destroy before_action :load_project_wiki before_action :load_page, only: [:show, :edit, :update, :history, :destroy] - before_action :valid_encoding?, only: [:show, :edit, :update], if: :load_page + before_action :valid_encoding?, + if: -> { %w[show edit update].include?(action_name) && load_page } before_action only: [:edit, :update], unless: :valid_encoding? do redirect_to(project_wiki_path(@project, @page)) end + def new + redirect_to project_wiki_path(@project, SecureRandom.uuid, random_title: true) + end + def pages @wiki_pages = Kaminari.paginate_array( @project_wiki.list_pages(sort: params[:sort], direction: params[:direction]) @@ -23,17 +28,25 @@ class Projects::WikisController < Projects::ApplicationController @wiki_entries = WikiPage.group_by_directory(@wiki_pages) end + # `#show` handles a number of scenarios: + # + # - If `id` matches a WikiPage, then show the wiki page. + # - If `id` is a file in the wiki repository, then send the file. + # - If we know the user wants to create a new page with the given `id`, + # then display a create form. + # - Otherwise show the empty wiki page and invite the user to create a page. def show - view_param = @project_wiki.empty? ? params[:view] : 'create' - if @page set_encoding_error unless valid_encoding? render 'show' elsif file_blob send_blob(@project_wiki.repository, file_blob) - elsif can?(current_user, :create_wiki, @project) && view_param == 'create' - @page = build_page(title: params[:id]) + elsif show_create_form? + # Assign a title to the WikiPage unless `id` is a randomly generated slug from #new + title = params[:id] unless params[:random_title].present? + + @page = build_page(title: title) render 'edit' else @@ -109,6 +122,15 @@ class Projects::WikisController < Projects::ApplicationController private + def show_create_form? + can?(current_user, :create_wiki, @project) && + @page.nil? && + # Always show the create form when the wiki has had at least one page created. + # Otherwise, we only show the form when the user has navigated from + # the 'empty wiki' page + (@project_wiki.exists? || params[:view] == 'create') + end + def load_project_wiki @project_wiki = load_wiki @@ -134,7 +156,7 @@ class Projects::WikisController < Projects::ApplicationController params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha) end - def build_page(args) + def build_page(args = {}) WikiPage.new(@project_wiki).tap do |page| page.update_attributes(args) # rubocop:disable Rails/ActiveRecordAliases end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index feefc7f8137..5f335de4d6b 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -18,15 +18,18 @@ class ProjectsController < Projects::ApplicationController before_action :redirect_git_extension, only: [:show] before_action :project, except: [:index, :new, :create, :resolve] before_action :repository, except: [:index, :new, :create, :resolve] - before_action :assign_ref_vars, only: [:show], if: :repo_exists? - before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?] - before_action :lfs_blob_ids, only: [:show], if: [:repo_exists?, :project_view_files?] + before_action :assign_ref_vars, if: -> { action_name == 'show' && repo_exists? } + before_action :tree, + if: -> { action_name == 'show' && repo_exists? && project_view_files? } + before_action :lfs_blob_ids, + if: -> { action_name == 'show' && repo_exists? && project_view_files? } before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export] before_action :present_project, only: [:edit] before_action :authorize_download_code!, only: [:refs] # Authorize before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] + before_action :authorize_archive_project!, only: [:archive, :unarchive] before_action :event_filter, only: [:show, :activity] layout :determine_layout @@ -162,8 +165,6 @@ class ProjectsController < Projects::ApplicationController end def archive - return access_denied! unless can?(current_user, :archive_project, @project) - ::Projects::UpdateService.new(@project, current_user, archived: true).execute respond_to do |format| @@ -172,8 +173,6 @@ class ProjectsController < Projects::ApplicationController end def unarchive - return access_denied! unless can?(current_user, :archive_project, @project) - ::Projects::UpdateService.new(@project, current_user, archived: false).execute respond_to do |format| @@ -282,6 +281,18 @@ class ProjectsController < Projects::ApplicationController end # rubocop: enable CodeReuse/ActiveRecord + def resolve + @project = Project.find(params[:id]) + + if can?(current_user, :read_project, @project) + redirect_to @project + else + render_404 + end + end + + private + # Render project landing depending of which features are available # So if page is not available in the list it renders the next page # @@ -347,6 +358,7 @@ class ProjectsController < Projects::ApplicationController :container_registry_enabled, :default_branch, :description, + :emails_disabled, :external_authorization_classification_label, :import_url, :issues_tracker, @@ -451,14 +463,4 @@ class ProjectsController < Projects::ApplicationController def present_project @project = @project.present(current_user: current_user) end - - def resolve - @project = Project.find(params[:id]) - - if can?(current_user, :read_project, @project) - redirect_to @project - else - render_404 - end - end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index b2b151bbcf0..e773ec09924 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -4,12 +4,12 @@ class RegistrationsController < Devise::RegistrationsController include Recaptcha::Verify include AcceptsPendingInvitations include RecaptchaExperimentHelper + include InvisibleCaptcha prepend_before_action :check_captcha, only: :create before_action :whitelist_query_limiting, only: [:destroy] before_action :ensure_terms_accepted, - if: -> { Gitlab::CurrentSettings.current_application_settings.enforce_terms? }, - only: [:create] + if: -> { action_name == 'create' && Gitlab::CurrentSettings.current_application_settings.enforce_terms? } def new redirect_to(new_user_session_path) @@ -69,12 +69,12 @@ class RegistrationsController < Devise::RegistrationsController def after_sign_up_path_for(user) Gitlab::AppLogger.info(user_created_message(confirmed: user.confirmed?)) - user.confirmed? ? stored_location_for(user) || dashboard_projects_path : users_almost_there_path + confirmed_or_unconfirmed_access_allowed(user) ? stored_location_or_dashboard(user) : users_almost_there_path end def after_inactive_sign_up_path_for(resource) Gitlab::AppLogger.info(user_created_message) - users_almost_there_path + Feature.enabled?(:soft_email_confirmation) ? dashboard_projects_path : users_almost_there_path end private @@ -135,4 +135,12 @@ class RegistrationsController < Devise::RegistrationsController def terms_accepted? Gitlab::Utils.to_boolean(params[:terms_opt_in]) end + + def confirmed_or_unconfirmed_access_allowed(user) + user.confirmed? || Feature.enabled?(:soft_email_confirmation) + end + + def stored_location_or_dashboard(user) + stored_location_for(user) || dashboard_projects_path + end end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 8c674be58c5..e30935be4b6 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -31,9 +31,20 @@ class SearchController < ApplicationController render_commits if @scope == 'commits' eager_load_user_status if @scope == 'users' + increment_navbar_searches_counter + check_single_commit_result end + def count + params.require([:search, :scope]) + + scope = search_service.scope + count = search_service.search_results.formatted_count(scope) + + render json: { count: count } + end + # rubocop: disable CodeReuse/ActiveRecord def autocomplete term = params[:term] @@ -70,4 +81,10 @@ class SearchController < ApplicationController redirect_to project_commit_path(@project, only_commit) if found_by_commit_sha end end + + def increment_navbar_searches_counter + return if params[:nav_source] != 'navbar' + + Gitlab::UsageDataCounters::SearchCounter.increment_navbar_searches_count + end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index a841859621e..1880bead3ee 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -13,20 +13,30 @@ class SessionsController < Devise::SessionsController prepend_before_action :check_initial_setup, only: [:new] prepend_before_action :authenticate_with_two_factor, - if: :two_factor_enabled?, only: [:create] + if: -> { action_name == 'create' && two_factor_enabled? } prepend_before_action :check_captcha, only: [:create] prepend_before_action :store_redirect_uri, only: [:new] prepend_before_action :ldap_servers, only: [:new, :create] prepend_before_action :require_no_authentication_without_flash, only: [:new, :create] - prepend_before_action :ensure_password_authentication_enabled!, if: :password_based_login?, only: [:create] + prepend_before_action :ensure_password_authentication_enabled!, if: -> { action_name == 'create' && password_based_login? } before_action :auto_sign_in_with_provider, only: [:new] before_action :load_recaptcha - after_action :log_failed_login, only: [:new], if: :failed_login? - + after_action :log_failed_login, if: -> { action_name == 'new' && failed_login? } helper_method :captcha_enabled? + # protect_from_forgery is already prepended in ApplicationController but + # authenticate_with_two_factor which signs in the user is prepended before + # that here. + # We need to make sure CSRF token is verified before authenticating the user + # because Devise.clean_up_csrf_token_on_authentication is set to true by + # default to avoid CSRF token fixation attacks. Authenticating the user first + # would cause the CSRF token to be cleared and then + # RequestForgeryProtection#verify_authenticity_token would fail because of + # token mismatch. + protect_from_forgery with: :exception, prepend: true + CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha'.freeze def new diff --git a/app/controllers/snippets/notes_controller.rb b/app/controllers/snippets/notes_controller.rb index 612897f27e6..551b37cb3d3 100644 --- a/app/controllers/snippets/notes_controller.rb +++ b/app/controllers/snippets/notes_controller.rb @@ -27,7 +27,9 @@ class Snippets::NotesController < ApplicationController alias_method :noteable, :snippet def finder_params - params.merge(last_fetched_at: last_fetched_at, target_id: snippet.id, target_type: 'personal_snippet') + params.merge(last_fetched_at: last_fetched_at, target_id: snippet.id, target_type: 'personal_snippet').tap do |merged_params| + merged_params[:project] = project if respond_to?(:project) + end end def authorize_read_snippet! diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index fad036b8df8..869655e9550 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -8,7 +8,8 @@ class SnippetsController < ApplicationController include RendersBlob include PreviewMarkdown - skip_before_action :verify_authenticity_token, only: [:show], if: :js_request? + skip_before_action :verify_authenticity_token, + if: -> { action_name == 'show' && js_request? } before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 072d62ddf38..91e0efcf45f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -17,7 +17,7 @@ class UsersController < ApplicationController prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) } before_action :user, except: [:exists] before_action :authorize_read_user_profile!, - only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :snippets] + only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :starred_projects, :snippets] def show respond_to do |format| @@ -57,27 +57,30 @@ class UsersController < ApplicationController def projects load_projects - skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination]) - skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace]) - compact_mode = Gitlab::Utils.to_boolean(params[:compact_mode]) - - respond_to do |format| - format.html { render 'show' } - format.json do - pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination, skip_namespace: skip_namespace, compact_mode: compact_mode) - end - end + present_projects(@projects) end def contributed load_contributed_projects + present_projects(@contributed_projects) + end + + def starred + load_starred_projects + + present_projects(@starred_projects) + end + + def present_projects(projects) + skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination]) + skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace]) + compact_mode = Gitlab::Utils.to_boolean(params[:compact_mode]) + respond_to do |format| format.html { render 'show' } format.json do - render json: { - html: view_to_html_string("shared/projects/_list", projects: @contributed_projects) - } + pager_json("shared/projects/_list", projects.count, projects: projects, skip_pagination: skip_pagination, skip_namespace: skip_namespace, compact_mode: compact_mode) end end end @@ -120,6 +123,10 @@ class UsersController < ApplicationController ContributedProjectsFinder.new(user).execute(current_user) end + def starred_projects + StarredProjectsFinder.new(user, current_user: current_user).execute + end + def contributions_calendar @contributions_calendar ||= Gitlab::ContributionsCalendar.new(user, current_user) end @@ -145,6 +152,12 @@ class UsersController < ApplicationController prepare_projects_for_rendering(@contributed_projects) end + def load_starred_projects + @starred_projects = starred_projects + + prepare_projects_for_rendering(@starred_projects) + end + def load_groups @groups = JoinedGroupsFinder.new(user).execute(current_user) |