diff options
Diffstat (limited to 'app/controllers')
74 files changed, 601 insertions, 336 deletions
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 786ba73a96f..56ec10fa43a 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -53,7 +53,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController def integrations return not_found unless instance_level_integrations? - @integrations = Service.find_or_initialize_all(Service.for_instance).sort_by(&:title) + @integrations = Service.find_or_initialize_all_non_project_specific(Service.for_instance).sort_by(&:title) end def update @@ -216,10 +216,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController remove_blank_params_for!(:elasticsearch_aws_secret_access_key, :eks_secret_access_key) - # TODO Remove domain_blacklist_raw in APIv5 (See https://gitlab.com/gitlab-org/gitlab-foss/issues/67204) - params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file] - params.delete(:domain_blacklist_raw) if params[:domain_blacklist] - params.delete(:domain_whitelist_raw) if params[:domain_whitelist] + # TODO Remove domain_denylist_raw in APIv5 (See https://gitlab.com/gitlab-org/gitlab-foss/issues/67204) + params.delete(:domain_denylist_raw) if params[:domain_denylist_file] + params.delete(:domain_denylist_raw) if params[:domain_denylist] + params.delete(:domain_allowlist_raw) if params[:domain_allowlist] params.require(:application_setting).permit( visible_application_setting_attributes @@ -240,7 +240,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController *ApplicationSetting.repository_storages_weighted_attributes, :lets_encrypt_notification_email, :lets_encrypt_terms_of_service_accepted, - :domain_blacklist_file, + :domain_denylist_file, :raw_blob_request_limit, :issues_create_limit, :default_branch_name, diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index 7d981d67840..33a8cc4ae42 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -16,6 +16,7 @@ class Admin::DashboardController < Admin::ApplicationController @groups = Group.order_id_desc.with_route.limit(10) @notices = Gitlab::ConfigChecker::PumaRuggedChecker.check @notices += Gitlab::ConfigChecker::ExternalDatabaseChecker.check + @redis_versions = [Gitlab::Redis::Queues, Gitlab::Redis::SharedState, Gitlab::Redis::Cache].map(&:version).uniq end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/controllers/admin/instance_statistics_controller.rb b/app/controllers/admin/instance_statistics_controller.rb index dfbd704cb0c..05a0a1ce314 100644 --- a/app/controllers/admin/instance_statistics_controller.rb +++ b/app/controllers/admin/instance_statistics_controller.rb @@ -13,6 +13,6 @@ class Admin::InstanceStatisticsController < Admin::ApplicationController end def check_feature_flag - render_404 unless Feature.enabled?(:instance_statistics) + render_404 unless Feature.enabled?(:instance_statistics, default_enabled: true) end end diff --git a/app/controllers/admin/integrations_controller.rb b/app/controllers/admin/integrations_controller.rb index 9a1d5a11f7f..aab8705f5cb 100644 --- a/app/controllers/admin/integrations_controller.rb +++ b/app/controllers/admin/integrations_controller.rb @@ -8,8 +8,8 @@ class Admin::IntegrationsController < Admin::ApplicationController private - def find_or_initialize_integration(name) - Service.find_or_initialize_integration(name, instance: true) + def find_or_initialize_non_project_specific_integration(name) + Service.find_or_initialize_non_project_specific_integration(name, instance: true) end def integrations_enabled? diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index bd7b69384b2..2d0bb0bfebc 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -6,7 +6,6 @@ class Admin::UsersController < Admin::ApplicationController before_action :user, except: [:index, :new, :create] before_action :check_impersonation_availability, only: :impersonate before_action :ensure_destroy_prerequisites_met, only: [:destroy] - before_action :check_admin_approval_feature_available!, only: [:approve] feature_category :users @@ -298,10 +297,6 @@ class Admin::UsersController < Admin::ApplicationController 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 - - def check_admin_approval_feature_available! - access_denied! unless Feature.enabled?(:admin_approval_for_new_user_signups, default_enabled: true) - end end Admin::UsersController.prepend_if_ee('EE::Admin::UsersController') diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 05f496c3b99..c38c6abddc1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -22,7 +22,7 @@ class ApplicationController < ActionController::Base include Impersonation include Gitlab::Logging::CloudflareHelper include Gitlab::Utils::StrongMemoize - include ControllerWithFeatureCategory + include ::Gitlab::WithFeatureCategory before_action :authenticate_user!, except: [:route_not_found] before_action :enforce_terms!, if: :should_enforce_terms? @@ -121,7 +121,7 @@ class ApplicationController < ActionController::Base end def route_not_found - if current_user + if current_user || browser.bot.search_engine? not_found else store_location_for(:user, request.fullpath) unless request.xhr? @@ -266,6 +266,12 @@ class ApplicationController < ActionController::Base end end + def stream_headers + headers['Content-Length'] = nil + headers['X-Accel-Buffering'] = 'no' # Disable buffering on Nginx + headers['Last-Modified'] = '0' # Prevent buffering via Rack::ETag middleware + end + def default_headers headers['X-Frame-Options'] = 'DENY' headers['X-XSS-Protection'] = '1; mode=block' diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index ac4ee14c6a9..9ee69c7c07f 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -53,7 +53,7 @@ class AutocompleteController < ApplicationController end def deploy_keys_with_owners - deploy_keys = DeployKeys::CollectKeysService.new(project, current_user).execute + deploy_keys = DeployKey.with_write_access_for_project(project) render json: DeployKeySerializer.new.represent(deploy_keys, { with_owner: true, user: current_user }) end diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 52719e90e04..9800d94964d 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -272,7 +272,7 @@ class Clusters::ClustersController < Clusters::BaseController end def aws_role_params - params.require(:cluster).permit(:role_arn) + params.require(:cluster).permit(:role_arn, :region) end def generate_gcp_authorize_url diff --git a/app/controllers/concerns/controller_with_feature_category.rb b/app/controllers/concerns/controller_with_feature_category.rb deleted file mode 100644 index c1ff9ef2e69..00000000000 --- a/app/controllers/concerns/controller_with_feature_category.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -module ControllerWithFeatureCategory - extend ActiveSupport::Concern - include Gitlab::ClassAttributes - - class_methods do - def feature_category(category, actions = []) - feature_category_configuration[category] ||= [] - feature_category_configuration[category] += actions.map(&:to_s) - - validate_config!(feature_category_configuration) - end - - def feature_category_for_action(action) - category_config = feature_category_configuration.find do |_, actions| - actions.empty? || actions.include?(action) - end - - category_config&.first || superclass_feature_category_for_action(action) - end - - private - - def validate_config!(config) - 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 duplicate_actions.any? - raise ArgumentError, "Actions have multiple feature categories: #{duplicate_actions.join(', ')}" - end - end - - def feature_category_configuration - class_attributes[:feature_category_config] ||= {} - end - - def superclass_feature_category_for_action(action) - return unless superclass.respond_to?(:feature_category_for_action) - - superclass.feature_category_for_action(action) - end - end -end diff --git a/app/controllers/concerns/dependency_proxy_access.rb b/app/controllers/concerns/dependency_proxy_access.rb new file mode 100644 index 00000000000..5036d0cfce4 --- /dev/null +++ b/app/controllers/concerns/dependency_proxy_access.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module DependencyProxyAccess + extend ActiveSupport::Concern + + included do + before_action :verify_dependency_proxy_enabled! + before_action :authorize_read_dependency_proxy! + end + + private + + def verify_dependency_proxy_enabled! + render_404 unless group.dependency_proxy_feature_available? + end + + def authorize_read_dependency_proxy! + access_denied! unless can?(current_user, :read_dependency_proxy, group) + end + + def authorize_admin_dependency_proxy! + access_denied! unless can?(current_user, :admin_dependency_proxy, group) + end +end diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb index 39f63bbaaec..8e9b038437d 100644 --- a/app/controllers/concerns/integrations_actions.rb +++ b/app/controllers/concerns/integrations_actions.rb @@ -52,7 +52,7 @@ module IntegrationsActions def integration # Using instance variable `@service` still required as it's used in ServiceParams. # Should be removed once that is refactored to use `@integration`. - @integration = @service ||= find_or_initialize_integration(params[:id]) # rubocop:disable Gitlab/ModuleWithInstanceVariables + @integration = @service ||= find_or_initialize_non_project_specific_integration(params[:id]) # rubocop:disable Gitlab/ModuleWithInstanceVariables end def success_message diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index a1a2740cde2..3b46a547d47 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -8,9 +8,6 @@ module IssuableActions before_action :authorize_destroy_issuable!, only: :destroy before_action :check_destroy_confirmation!, only: :destroy before_action :authorize_admin_issuable!, only: :bulk_update - before_action only: :show do - push_frontend_feature_flag(:scoped_labels, type: :licensed, default_enabled: true) - end before_action do push_frontend_feature_flag(:not_issuable_queries, @project, default_enabled: true) end diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index 2844acea271..bc3fd32759f 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true # This concern assumes: +# - a `#container` accessor # - a `#project` accessor # - a `#user` accessor # - a `#authentication_result` accessor @@ -11,6 +12,7 @@ # - a `#has_authentication_ability?(ability)` method module LfsRequest extend ActiveSupport::Concern + include Gitlab::Utils::StrongMemoize CONTENT_TYPE = 'application/vnd.git-lfs+json' @@ -29,16 +31,19 @@ module LfsRequest message: _('Git LFS is not enabled on this GitLab server, contact your admin.'), documentation_url: help_url }, + content_type: CONTENT_TYPE, status: :not_implemented ) end def lfs_check_access! - return render_lfs_not_found unless project + return render_lfs_not_found unless container&.lfs_enabled? return if download_request? && lfs_download_access? return if upload_request? && lfs_upload_access? - if project.public? || can?(user, :read_project, project) + # Only return a 403 response if the user has download access permission, + # otherwise return a 404 to avoid exposing the existence of the container. + if lfs_download_access? lfs_forbidden! else render_lfs_not_found @@ -72,9 +77,9 @@ module LfsRequest end def lfs_download_access? - return false unless project.lfs_enabled? - - ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? || deploy_token_can_download_code? + strong_memoize(:lfs_download_access) do + ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? || deploy_token_can_download_code? + end end def deploy_token_can_download_code? @@ -93,11 +98,12 @@ module LfsRequest end def lfs_upload_access? - return false unless project.lfs_enabled? - return false unless has_authentication_ability?(:push_code) - return false if limit_exceeded? + strong_memoize(:lfs_upload_access) do + next false unless has_authentication_ability?(:push_code) + next false if limit_exceeded? - lfs_deploy_token? || can?(user, :push_code, project) + lfs_deploy_token? || can?(user, :push_code, project) + end end def lfs_deploy_token? diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index 7a5b470f366..bfa7a30bc65 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -31,6 +31,10 @@ module NotesActions # We know there's more data, so tell the frontend to poll again after 1ms set_polling_interval_header(interval: 1) if meta[:more] + # Only present an ETag for the empty response to ensure pagination works + # as expected + ::Gitlab::EtagCaching::Middleware.skip!(response) if notes.present? + render json: meta.merge(notes: notes) end @@ -115,7 +119,7 @@ module NotesActions end def gather_some_notes - paginator = Gitlab::UpdatedNotesPaginator.new( + paginator = ::Gitlab::UpdatedNotesPaginator.new( notes_finder.execute.inc_relations_for_view, last_fetched_at: last_fetched_at ) diff --git a/app/controllers/concerns/routable_actions.rb b/app/controllers/concerns/routable_actions.rb index 1b2e6461dee..bc2e7fba288 100644 --- a/app/controllers/concerns/routable_actions.rb +++ b/app/controllers/concerns/routable_actions.rb @@ -51,7 +51,7 @@ module RoutableActions flash[:notice] = "#{routable.class.to_s.titleize} '#{requested_full_path}' was moved to '#{canonical_path}'. Please update any links and bookmarks that may still have the old path." end - redirect_to build_canonical_path(routable) + redirect_to build_canonical_path(routable), status: :moved_permanently end end end diff --git a/app/controllers/concerns/send_file_upload.rb b/app/controllers/concerns/send_file_upload.rb index 2f06cd84ee5..8b053ef7c59 100644 --- a/app/controllers/concerns/send_file_upload.rb +++ b/app/controllers/concerns/send_file_upload.rb @@ -70,16 +70,7 @@ module SendFileUpload Avatarable::ALLOWED_IMAGE_SCALER_WIDTHS.include?(params[:width]&.to_i) end - # We use two separate feature gates to allow image resizing. - # The first, `:dynamic_image_resizing_requester`, based on the content requester. - # Enabling it for the user would allow that user to send resizing requests for any avatar. - # The second, `:dynamic_image_resizing_owner`, based on the content owner. - # Enabling it for the user would allow anyone to send resizing requests against the mentioned user avatar only. - # This flag allows us to operate on trusted data only, more in https://gitlab.com/gitlab-org/gitlab/-/issues/241533. - # Because of this, you need to enable BOTH to serve resized image, - # as you would need at least one allowed requester and at least one allowed avatar. def scaling_allowed_by_feature_flags?(file_upload) - Feature.enabled?(:dynamic_image_resizing_requester, current_user) && - Feature.enabled?(:dynamic_image_resizing_owner, file_upload.model) + Feature.enabled?(:dynamic_image_resizing, default_enabled: true, type: :ops) end end diff --git a/app/controllers/concerns/sends_blob.rb b/app/controllers/concerns/sends_blob.rb index 9bba61fda84..381f2eba352 100644 --- a/app/controllers/concerns/sends_blob.rb +++ b/app/controllers/concerns/sends_blob.rb @@ -44,7 +44,6 @@ module SendsBlob Blob::CACHE_TIME end - response.etag = blob.id !stale end diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb index e4c3df6ccc3..0153ede2821 100644 --- a/app/controllers/concerns/snippets_actions.rb +++ b/app/controllers/concerns/snippets_actions.rb @@ -57,11 +57,6 @@ module SnippetsActions render 'show' end - format.json do - conditionally_expand_blob(blob) - render_blob_json(blob) - end - format.js do if @snippet.embeddable? conditionally_expand_blobs(blobs) diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb index aed109309e3..6abb2e16226 100644 --- a/app/controllers/concerns/wiki_actions.rb +++ b/app/controllers/concerns/wiki_actions.rb @@ -103,9 +103,10 @@ module WikiActions @page = response.payload[:page] if response.success? + flash[:toast] = _('Wiki page was successfully updated.') + redirect_to( - wiki_page_path(wiki, page), - notice: _('Wiki was successfully updated.') + wiki_page_path(wiki, page) ) else render 'shared/wikis/edit' @@ -122,9 +123,10 @@ module WikiActions @page = response.payload[:page] if response.success? + flash[:toast] = _('Wiki page was successfully created.') + redirect_to( - wiki_page_path(wiki, page), - notice: _('Wiki was successfully updated.') + wiki_page_path(wiki, page) ) else render 'shared/wikis/edit' @@ -169,9 +171,10 @@ module WikiActions response = WikiPages::DestroyService.new(container: container, current_user: current_user).execute(page) if response.success? + flash[:toast] = _("Wiki page was successfully deleted.") + redirect_to wiki_path(wiki), - status: :found, - notice: _("Page was successfully deleted") + status: :found else @error = response render 'shared/wikis/edit' diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb index b971c5783a8..c2d72610c66 100644 --- a/app/controllers/groups/boards_controller.rb +++ b/app/controllers/groups/boards_controller.rb @@ -7,9 +7,8 @@ class Groups::BoardsController < Groups::ApplicationController before_action :authorize_read_board!, only: [:index, :show] before_action :assign_endpoint_vars before_action do - push_frontend_feature_flag(:multi_select_board, default_enabled: true) push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false) - push_frontend_feature_flag(:boards_with_swimlanes, group, default_enabled: false) + push_frontend_feature_flag(:boards_with_swimlanes, group, default_enabled: true) end feature_category :boards diff --git a/app/controllers/groups/dependency_proxies_controller.rb b/app/controllers/groups/dependency_proxies_controller.rb new file mode 100644 index 00000000000..367dbafdd59 --- /dev/null +++ b/app/controllers/groups/dependency_proxies_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Groups + class DependencyProxiesController < Groups::ApplicationController + include DependencyProxyAccess + + before_action :authorize_admin_dependency_proxy!, only: :update + before_action :dependency_proxy + + feature_category :package_registry + + def show + @blobs_count = group.dependency_proxy_blobs.count + @blobs_total_size = group.dependency_proxy_blobs.total_size + end + + def update + dependency_proxy.update(dependency_proxy_params) + + redirect_to group_dependency_proxy_path(group) + end + + private + + def dependency_proxy + @dependency_proxy ||= + group.dependency_proxy_setting || group.create_dependency_proxy_setting + end + + def dependency_proxy_params + params.require(:dependency_proxy_group_setting).permit(:enabled) + end + end +end diff --git a/app/controllers/groups/dependency_proxy_for_containers_controller.rb b/app/controllers/groups/dependency_proxy_for_containers_controller.rb new file mode 100644 index 00000000000..f46902ef90f --- /dev/null +++ b/app/controllers/groups/dependency_proxy_for_containers_controller.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +class Groups::DependencyProxyForContainersController < Groups::ApplicationController + include DependencyProxyAccess + include SendFileUpload + + before_action :ensure_token_granted! + before_action :ensure_feature_enabled! + + attr_reader :token + + feature_category :package_registry + + def manifest + result = DependencyProxy::PullManifestService.new(image, tag, token).execute + + if result[:status] == :success + render json: result[:manifest] + else + render status: result[:http_status], json: result[:message] + end + end + + def blob + result = DependencyProxy::FindOrCreateBlobService + .new(group, image, token, params[:sha]).execute + + if result[:status] == :success + send_upload(result[:blob].file) + else + head result[:http_status] + end + end + + private + + def image + params[:image] + end + + def tag + params[:tag] + end + + def dependency_proxy + @dependency_proxy ||= + group.dependency_proxy_setting || group.create_dependency_proxy_setting + end + + def ensure_feature_enabled! + render_404 unless dependency_proxy.enabled + end + + def ensure_token_granted! + result = DependencyProxy::RequestTokenService.new(image).execute + + if result[:status] == :success + @token = result[:token] + else + render status: result[:http_status], json: result[:message] + end + end +end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 173a24ceb74..03d41f1dd6d 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -6,7 +6,7 @@ class Groups::MilestonesController < Groups::ApplicationController before_action :milestone, only: [:edit, :show, :update, :issues, :merge_requests, :participants, :labels, :destroy] before_action :authorize_admin_milestones!, only: [:edit, :new, :create, :update, :destroy] before_action do - push_frontend_feature_flag(:burnup_charts, @group) + push_frontend_feature_flag(:burnup_charts, @group, default_enabled: true) end feature_category :issue_tracking diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb index 0c72c8a037b..723edc4b7e9 100644 --- a/app/controllers/groups/settings/ci_cd_controller.rb +++ b/app/controllers/groups/settings/ci_cd_controller.rb @@ -8,9 +8,6 @@ module Groups skip_cross_project_access_check :show before_action :authorize_admin_group! before_action :authorize_update_max_artifacts_size!, only: [:update] - before_action do - push_frontend_feature_flag(:new_variables_ui, @group, default_enabled: true) - end before_action :define_variables, only: [:show] feature_category :continuous_integration diff --git a/app/controllers/groups/settings/integrations_controller.rb b/app/controllers/groups/settings/integrations_controller.rb index b089cfdf341..a66372b3571 100644 --- a/app/controllers/groups/settings/integrations_controller.rb +++ b/app/controllers/groups/settings/integrations_controller.rb @@ -10,7 +10,7 @@ module Groups feature_category :integrations def index - @integrations = Service.find_or_initialize_all(Service.for_group(group)).sort_by(&:title) + @integrations = Service.find_or_initialize_all_non_project_specific(Service.for_group(group)).sort_by(&:title) end def edit @@ -21,12 +21,12 @@ module Groups private - def find_or_initialize_integration(name) - Service.find_or_initialize_integration(name, group_id: group.id) + def find_or_initialize_non_project_specific_integration(name) + Service.find_or_initialize_non_project_specific_integration(name, group_id: group.id) end def integrations_enabled? - Feature.enabled?(:group_level_integrations, group) + Feature.enabled?(:group_level_integrations, group, default_enabled: true) end def scoped_edit_integration_path(integration) diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 6f8dc75f6bd..8d528e123e1 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -30,7 +30,6 @@ class GroupsController < Groups::ApplicationController before_action do push_frontend_feature_flag(:vue_issuables_list, @group) - push_frontend_feature_flag(:deployment_filters) end before_action do @@ -133,13 +132,23 @@ class GroupsController < Groups::ApplicationController def update 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." + notice = "Group '#{@group.name}' was successfully updated." + + redirect_to edit_group_origin_location, notice: notice else @group.reset render action: "edit" end end + def edit_group_origin_location + if params.dig(:group, :redirect_target) == 'repository_settings' + group_settings_repository_path(@group, anchor: 'js-default-branch-name') + else + edit_group_path(@group, anchor: params[:update_section]) + end + end + def destroy Groups::DestroyService.new(@group, current_user).async_execute @@ -181,8 +190,6 @@ class GroupsController < Groups::ApplicationController end def unfoldered_environment_names - return render_404 unless Feature.enabled?(:deployment_filters) - respond_to do |format| format.json do render json: EnvironmentNamesFinder.new(@group, current_user).execute @@ -193,6 +200,8 @@ class GroupsController < Groups::ApplicationController protected def render_show_html + record_experiment_user(:invite_members_empty_group_version_a) if ::Gitlab.com? + render 'groups/show', locals: { trial: params[:trial] } end diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index 151ba46e629..87cda723895 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -48,18 +48,14 @@ class Import::BaseController < ApplicationController private - def filter_attribute - :name - end - def sanitized_filter_param - @filter ||= sanitize(params[:filter]) + @filter ||= sanitize(params[:filter])&.downcase end def filtered(collection) return collection unless sanitized_filter_param - collection.select { |item| item[filter_attribute].include?(sanitized_filter_param) } + collection.select { |item| item[:name].to_s.downcase.include?(sanitized_filter_param) } end def serialized_provider_repos diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index 0ffd9ef8bdd..57bd39bbe06 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -132,8 +132,4 @@ class Import::BitbucketController < Import::BaseController refresh_token: session[:bitbucket_refresh_token] } end - - def sanitized_filter_param - @filter ||= sanitize(params[:filter]) - end end diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb index bee78cb3283..1846b1e0cec 100644 --- a/app/controllers/import/bitbucket_server_controller.rb +++ b/app/controllers/import/bitbucket_server_controller.rb @@ -170,10 +170,6 @@ class Import::BitbucketServerController < Import::BaseController BitbucketServer::Paginator::PAGE_LENGTH end - def sanitized_filter_param - sanitize(params[:filter]) - end - def bitbucket_connection_error(error) flash[:alert] = _("Unable to connect to server: %{error}") % { error: error } clear_session_data diff --git a/app/controllers/import/bulk_imports_controller.rb b/app/controllers/import/bulk_imports_controller.rb index cb2922c2d47..78f4a0cffca 100644 --- a/app/controllers/import/bulk_imports_controller.rb +++ b/app/controllers/import/bulk_imports_controller.rb @@ -6,13 +6,13 @@ class Import::BulkImportsController < ApplicationController feature_category :importers - rescue_from Gitlab::BulkImport::Client::ConnectionError, with: :bulk_import_connection_error + rescue_from BulkImports::Clients::Http::ConnectionError, with: :bulk_import_connection_error def configure - session[access_token_key] = params[access_token_key]&.strip - session[url_key] = params[url_key] + session[access_token_key] = configure_params[access_token_key]&.strip + session[url_key] = configure_params[url_key] - redirect_to status_import_bulk_import_url + redirect_to status_import_bulk_imports_url end def status @@ -25,6 +25,12 @@ class Import::BulkImportsController < ApplicationController end end + def create + BulkImportService.new(current_user, create_params, credentials).execute + + render json: :ok + end + private def serialized_importable_data @@ -36,20 +42,33 @@ class Import::BulkImportsController < ApplicationController end def importable_data - client.get('groups', top_level_only: true) + client.get('groups', top_level_only: true).parsed_response end def client - @client ||= Gitlab::BulkImport::Client.new( + @client ||= BulkImports::Clients::Http.new( uri: session[url_key], token: session[access_token_key] ) end - def import_params + def configure_params params.permit(access_token_key, url_key) end + def create_params + params.permit(:bulk_import, [*bulk_import_params]) + end + + def bulk_import_params + %i[ + source_type + source_full_path + destination_name + destination_namespace + ] + end + def ensure_group_import_enabled render_404 unless Feature.enabled?(:bulk_import) end @@ -106,4 +125,11 @@ class Import::BulkImportsController < ApplicationController session[url_key] = nil session[access_token_key] = nil end + + def credentials + { + url: session[url_key], + access_token: [access_token_key] + } + end end diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index a1adc6e062a..8ac93aeb9c0 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -15,6 +15,7 @@ class Import::GithubController < Import::BaseController rescue_from OAuthConfigMissingError, with: :missing_oauth_config rescue_from Octokit::Unauthorized, with: :provider_unauthorized rescue_from Octokit::TooManyRequests, with: :provider_rate_limit + rescue_from Gitlab::GithubImport::RateLimitError, with: :rate_limit_threshold_exceeded def new if !ci_cd_only? && github_import_configured? && logged_in_with_provider? @@ -114,7 +115,7 @@ class Import::GithubController < Import::BaseController def client_repos @client_repos ||= if Feature.enabled?(:remove_legacy_github_client) - filtered(concatenated_repos) + concatenated_repos else filtered(client.repos) end @@ -122,8 +123,15 @@ class Import::GithubController < Import::BaseController def concatenated_repos return [] unless client.respond_to?(:each_page) + return client.each_page(:repos).flat_map(&:objects) unless sanitized_filter_param - client.each_page(:repos).flat_map(&:objects) + client.search_repos_by_name(sanitized_filter_param).flat_map(&:objects).flat_map(&:items) + end + + def sanitized_filter_param + super + + @filter = @filter&.tr(' ', '')&.tr(':', '') end def oauth_client @@ -246,12 +254,8 @@ class Import::GithubController < Import::BaseController {} end - def sanitized_filter_param - @filter ||= sanitize(params[:filter]) - end - - def filter_attribute - :name + def rate_limit_threshold_exceeded + head :too_many_requests end end diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index c7b8486d1c9..26fc1c11f6d 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -15,13 +15,11 @@ class InvitesController < ApplicationController feature_category :authentication_and_authorization def show - track_new_user_invite_experiment('opened') accept if skip_invitation_prompt? end def accept if member.accept_invite!(current_user) - track_new_user_invite_experiment('accepted') track_invitation_reminders_experiment('accepted') redirect_to invite_details[:path], notice: _("You have been granted %{member_human_access} access to %{title} %{name}.") % { member_human_access: member.human_access, title: invite_details[:title], name: invite_details[:name] } @@ -110,25 +108,13 @@ class InvitesController < ApplicationController end end - def track_new_user_invite_experiment(action) - return unless params[:new_user_invite] - - property = params[:new_user_invite] == 'experiment' ? 'experiment_group' : 'control_group' - - track_experiment(:invite_email, action, property) - end - def track_invitation_reminders_experiment(action) return unless Gitlab::Experimentation.enabled?(:invitation_reminders) property = Gitlab::Experimentation.enabled_for_attribute?(:invitation_reminders, member.invite_email) ? 'experimental_group' : 'control_group' - track_experiment(:invitation_reminders, action, property) - end - - def track_experiment(experiment_key, action, property) Gitlab::Tracking.event( - Gitlab::Experimentation.experiment(experiment_key).tracking_category, + Gitlab::Experimentation.experiment(:invitation_reminders).tracking_category, action, property: property, label: Digest::MD5.hexdigest(member.to_global_id.to_s) diff --git a/app/controllers/jwks_controller.rb b/app/controllers/jwks_controller.rb new file mode 100644 index 00000000000..e7b839f5590 --- /dev/null +++ b/app/controllers/jwks_controller.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class JwksController < ActionController::Base # rubocop:disable Rails/ApplicationController + def index + render json: { keys: keys } + end + + private + + def keys + [ + # We keep openid_connect_signing_key so that we can seamlessly + # replace it with ci_jwt_signing_key and remove it on the next release. + # TODO: Remove openid_connect_signing_key in 13.7 + # https://gitlab.com/gitlab-org/gitlab/-/issues/221031 + Rails.application.secrets.openid_connect_signing_key, + Gitlab::CurrentSettings.ci_jwt_signing_key + ].compact.map do |key_data| + OpenSSL::PKey::RSA.new(key_data) + .public_key + .to_jwk + .slice(:kty, :kid, :e, :n) + .merge(use: 'sig', alg: 'RS256') + end + end +end diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb index b005347c43a..a45205c5da7 100644 --- a/app/controllers/profiles/personal_access_tokens_controller.rb +++ b/app/controllers/profiles/personal_access_tokens_controller.rb @@ -9,9 +9,13 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController end def create - @personal_access_token = finder.build(personal_access_token_params) + result = ::PersonalAccessTokens::CreateService.new( + current_user: current_user, target_user: current_user, params: personal_access_token_params + ).execute - if @personal_access_token.save + @personal_access_token = result.payload[:personal_access_token] + + if result.success? PersonalAccessToken.redis_store!(current_user.id, @personal_access_token.token) redirect_to profile_personal_access_tokens_path, notice: _("Your new personal access token has been created.") else diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index c85c83688a4..afebeafff7c 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -127,7 +127,7 @@ class ProfilesController < Profiles::ApplicationController :include_private_contributions, :timezone, :job_title, - status: [:emoji, :message] + status: [:emoji, :message, :availability] ) end end diff --git a/app/controllers/projects/alert_management_controller.rb b/app/controllers/projects/alert_management_controller.rb index 0d0ef9b05cb..8ecf8fadefd 100644 --- a/app/controllers/projects/alert_management_controller.rb +++ b/app/controllers/projects/alert_management_controller.rb @@ -10,6 +10,5 @@ class Projects::AlertManagementController < Projects::ApplicationController def details @alert_id = params[:id] - push_frontend_feature_flag(:expose_environment_path_in_alert_details, @project) end end diff --git a/app/controllers/projects/alerting/notifications_controller.rb b/app/controllers/projects/alerting/notifications_controller.rb index 2241ded2db8..a3f4d784f25 100644 --- a/app/controllers/projects/alerting/notifications_controller.rb +++ b/app/controllers/projects/alerting/notifications_controller.rb @@ -14,7 +14,7 @@ module Projects def create token = extract_alert_manager_token(request) - result = notify_service.execute(token) + result = notify_service.execute(token, integration) head result.http_status end @@ -45,6 +45,18 @@ module Projects end end + def integration + AlertManagement::HttpIntegrationsFinder.new( + project, + endpoint_identifier: endpoint_identifier, + active: true + ).execute.first + end + + def endpoint_identifier + params[:endpoint_identifier] || AlertManagement::HttpIntegration::LEGACY_IDENTIFIER + end + def notification_payload @notification_payload ||= params.permit![:notification] end diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb index e9c533daa80..001967b8bb4 100644 --- a/app/controllers/projects/autocomplete_sources_controller.rb +++ b/app/controllers/projects/autocomplete_sources_controller.rb @@ -39,7 +39,7 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController private def autocomplete_service - @autocomplete_service ||= ::Projects::AutocompleteService.new(@project, current_user) + @autocomplete_service ||= ::Projects::AutocompleteService.new(@project, current_user, params) end def target diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index f228206032d..fb113df137f 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -3,6 +3,8 @@ class Projects::AvatarsController < Projects::ApplicationController include SendsBlob + skip_before_action :default_cache_headers, only: :show + before_action :authorize_admin_project!, only: [:destroy] feature_category :projects diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index c6251d27b05..02e941db636 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -33,7 +33,7 @@ class Projects::BlobController < Projects::ApplicationController before_action :set_last_commit_sha, only: [:edit, :update] before_action only: :show do - push_frontend_experiment(:suggest_pipeline) + push_frontend_feature_flag(:suggest_pipeline, default_enabled: true) push_frontend_feature_flag(:gitlab_ci_yml_preview, @project, default_enabled: false) end diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 193352ffa70..fe4502a0e06 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -8,8 +8,7 @@ class Projects::BoardsController < Projects::ApplicationController before_action :authorize_read_board!, only: [:index, :show] before_action :assign_endpoint_vars before_action do - push_frontend_feature_flag(:multi_select_board, default_enabled: true) - push_frontend_feature_flag(:boards_with_swimlanes, project, default_enabled: false) + push_frontend_feature_flag(:boards_with_swimlanes, project, default_enabled: true) end feature_category :boards diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 9124728ee25..cf1efda5d13 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -27,7 +27,7 @@ class Projects::BranchesController < Projects::ApplicationController @refs_pipelines = @project.ci_pipelines.latest_successful_for_refs(@branches.map(&:name)) @merged_branch_names = repository.merged_branch_names(@branches.map(&:name)) - @branch_pipeline_statuses = branch_pipeline_statuses + @branch_pipeline_statuses = Ci::CommitStatusesFinder.new(@project, repository, current_user, @branches).execute # https://gitlab.com/gitlab-org/gitlab/-/issues/22851 Gitlab::GitalyClient.allow_n_plus_1_calls do @@ -197,15 +197,4 @@ class Projects::BranchesController < Projects::ApplicationController confidential_issue_project end - - def branch_pipeline_statuses - latest_commits = @branches.map do |branch| - [branch.name, repository.commit(branch.dereferenced_target).sha] - end.to_h - - latest_pipelines = project.ci_pipelines.latest_pipeline_per_commit(latest_commits.values) - latest_commits.transform_values do |commit_sha| - latest_pipelines[commit_sha]&.detailed_status(current_user) - end.compact - end end diff --git a/app/controllers/projects/ci/lints_controller.rb b/app/controllers/projects/ci/lints_controller.rb index 7e900fc6051..9dc3194df85 100644 --- a/app/controllers/projects/ci/lints_controller.rb +++ b/app/controllers/projects/ci/lints_controller.rb @@ -2,28 +2,22 @@ class Projects::Ci::LintsController < Projects::ApplicationController before_action :authorize_create_pipeline! - before_action do - push_frontend_feature_flag(:ci_lint_vue, project) - end feature_category :pipeline_authoring + respond_to :json, only: [:create] + def show end def create - @content = params[:content] - @dry_run = params[:dry_run] + content = params[:content] + dry_run = params[:dry_run] - @result = Gitlab::Ci::Lint + result = Gitlab::Ci::Lint .new(project: @project, current_user: current_user) - .validate(@content, dry_run: @dry_run) + .validate(content, dry_run: dry_run) - respond_to do |format| - format.html { render :show } - format.json do - render json: ::Ci::Lint::ResultSerializer.new.represent(@result) - end - end + render json: ::Ci::Lint::ResultSerializer.new.represent(result) end end diff --git a/app/controllers/projects/ci/pipeline_editor_controller.rb b/app/controllers/projects/ci/pipeline_editor_controller.rb new file mode 100644 index 00000000000..c2428270fa6 --- /dev/null +++ b/app/controllers/projects/ci/pipeline_editor_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Projects::Ci::PipelineEditorController < Projects::ApplicationController + before_action :check_can_collaborate! + + feature_category :pipeline_authoring + + def show + render_404 unless ::Gitlab::Ci::Features.ci_pipeline_editor_page_enabled?(@project) + end + + private + + def check_can_collaborate! + render_404 unless can_collaborate_with_project?(@project) + end +end diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index 6cdd1c0bc8c..c8528ad6d28 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -55,7 +55,7 @@ class Projects::ImportsController < Projects::ApplicationController end def require_namespace_project_creation_permission - render_404 unless current_user.can?(:admin_project, @project) || current_user.can?(:create_projects, @project.namespace) + render_404 unless can?(current_user, :admin_project, @project) || can?(current_user, :create_projects, @project.namespace) end def redirect_if_progress diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 9a8965dbeb6..3a1b4f380a2 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -44,22 +44,19 @@ class Projects::IssuesController < Projects::ApplicationController push_frontend_feature_flag(:vue_issuable_sidebar, project.group) push_frontend_feature_flag(:tribute_autocomplete, @project) push_frontend_feature_flag(:vue_issuables_list, project) + push_frontend_feature_flag(:vue_issue_header, @project, default_enabled: true) end before_action only: :show do real_time_feature_flag = :real_time_issue_sidebar real_time_enabled = Gitlab::ActionCable::Config.in_app? || Feature.enabled?(real_time_feature_flag, @project) - gon.push({ features: { real_time_feature_flag.to_s.camelize(:lower) => real_time_enabled } }, true) + push_to_gon_features(real_time_feature_flag, real_time_enabled) record_experiment_user(:invite_members_version_a) record_experiment_user(:invite_members_version_b) end - before_action only: :index do - push_frontend_feature_flag(:scoped_labels, @project, type: :licensed) - end - around_action :allow_gitaly_ref_name_caching, only: [:discussions] respond_to :html diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index 3ceb60a6aef..07e38c80291 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -14,6 +14,9 @@ class Projects::JobsController < Projects::ApplicationController before_action :verify_api_request!, only: :terminal_websocket_authorize before_action :authorize_create_proxy_build!, only: :proxy_websocket_authorize before_action :verify_proxy_request!, only: :proxy_websocket_authorize + before_action do + push_frontend_feature_flag(:ci_job_line_links, @project) + end layout 'project' diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index 07c38431f0f..7fbeac12644 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -20,7 +20,10 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic end def diffs_batch - diffs = @compare.diffs_in_batch(params[:page], params[:per_page], diff_options: diff_options) + diff_options_hash = diff_options + diff_options_hash[:paths] = params[:paths] if params[:paths] + + diffs = @compare.diffs_in_batch(params[:page], params[:per_page], diff_options: diff_options_hash) positions = @merge_request.note_positions_for_paths(diffs.diff_file_paths, current_user) environment = @merge_request.environments_for(current_user, latest: true).last @@ -31,6 +34,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic environment: environment, merge_request: @merge_request, diff_view: diff_view, + merge_ref_head_diff: render_merge_ref_head_diff?, pagination_data: diffs.pagination_data } @@ -64,7 +68,10 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic render: ->(partial, locals) { view_to_html_string(partial, locals) } } - options = additional_attributes.merge(diff_view: Feature.enabled?(:unified_diff_lines, @merge_request.project, default_enabled: true) ? "inline" : diff_view) + options = additional_attributes.merge( + diff_view: unified_diff_lines_view_type(@merge_request.project), + merge_ref_head_diff: render_merge_ref_head_diff? + ) if @merge_request.project.context_commits_enabled? options[:context_commits] = @merge_request.recent_context_commits @@ -113,7 +120,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic end end - if Gitlab::Utils.to_boolean(params[:diff_head]) && @merge_request.diffable_merge_ref? + if render_merge_ref_head_diff? return CompareService.new(@project, @merge_request.merge_ref_head.sha) .execute(@project, @merge_request.target_branch) end @@ -155,6 +162,10 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes), @merge_request) end + def render_merge_ref_head_diff? + Gitlab::Utils.to_boolean(params[:diff_head]) && @merge_request.diffable_merge_ref? + end + def note_positions @note_positions ||= Gitlab::Diff::PositionCollection.new(renderable_notes.map(&:position)) end @@ -173,7 +184,6 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic end def update_diff_discussion_positions! - return unless Feature.enabled?(:merge_red_head_comments_position_on_demand, @merge_request.target_project, default_enabled: true) return if @merge_request.has_any_diff_note_positions? Discussions::CaptureDiffNotePositionsService.new(@merge_request).execute diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 91a041bb35b..f2b41294a85 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -12,7 +12,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo include SourcegraphDecorator include DiffHelper - skip_before_action :merge_request, only: [:index, :bulk_update] + skip_before_action :merge_request, only: [:index, :bulk_update, :export_csv] before_action :apply_diff_view_cookie!, only: [:show] before_action :whitelist_query_limiting, only: [:assign_related_issues, :update] before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort] @@ -27,7 +27,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo before_action :authenticate_user!, only: [:assign_related_issues] before_action :check_user_can_push_to_source_branch!, only: [:rebase] before_action only: [:show] do - push_frontend_experiment(:suggest_pipeline) + push_frontend_feature_flag(:suggest_pipeline, default_enabled: true) push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true) push_frontend_feature_flag(:mr_commit_neighbor_nav, @project, default_enabled: true) push_frontend_feature_flag(:multiline_comments, @project, default_enabled: true) @@ -37,9 +37,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:hide_jump_to_next_unresolved_in_threads, default_enabled: true) push_frontend_feature_flag(:merge_request_widget_graphql, @project) push_frontend_feature_flag(:unified_diff_lines, @project, default_enabled: true) + push_frontend_feature_flag(:unified_diff_components, @project) push_frontend_feature_flag(:highlight_current_diff_row, @project) push_frontend_feature_flag(:default_merge_ref_for_diffs, @project) push_frontend_feature_flag(:core_security_mr_widget, @project, default_enabled: true) + push_frontend_feature_flag(:remove_resolve_note, @project, default_enabled: true) + push_frontend_feature_flag(:test_failure_history, @project) record_experiment_user(:invite_members_version_a) record_experiment_user(:invite_members_version_b) @@ -47,7 +50,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo before_action do push_frontend_feature_flag(:vue_issuable_sidebar, @project.group) - push_frontend_feature_flag(:deployment_filters) end around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions] @@ -317,6 +319,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo super end + def export_csv + IssuableExportCsvWorker.perform_async(:merge_request, current_user.id, project.id, finder_options.to_h) # rubocop:disable CodeReuse/Worker + + index_path = project_merge_requests_path(project) + message = _('Your CSV export has started. It will be emailed to %{email} when complete.') % { email: current_user.notification_email } + redirect_to(index_path, notice: message) + end + protected alias_method :subscribable_resource, :merge_request @@ -471,7 +481,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo def endpoint_metadata_url(project, merge_request) params = request.query_parameters - params[:view] = cookies[:diff_view] if params[:view].blank? && cookies[:diff_view].present? + params[:view] = unified_diff_lines_view_type(project) if Feature.enabled?(:default_merge_ref_for_diffs, project) params = params.merge(diff_head: true) diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index e6c4af00b29..31189c888b7 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -7,7 +7,7 @@ class Projects::MilestonesController < Projects::ApplicationController before_action :check_issuables_available! before_action :milestone, only: [:edit, :update, :destroy, :show, :issues, :merge_requests, :participants, :labels, :promote] before_action do - push_frontend_feature_flag(:burnup_charts, @project) + push_frontend_feature_flag(:burnup_charts, @project, default_enabled: true) end # Allow read any milestone diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index e50e293a103..77fd7688caf 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -60,7 +60,7 @@ class Projects::NotesController < Projects::ApplicationController def render_json_with_notes_serializer prepare_notes_for_rendering([note]) - render json: note_serializer.represent(note) + render json: note_serializer.represent(note, render_truncated_diff_lines: true) end def note diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 953dce4d63c..f71a92ee874 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -12,11 +12,11 @@ class Projects::PipelinesController < Projects::ApplicationController before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables] before_action :authorize_update_pipeline!, only: [:retry, :cancel] before_action do - push_frontend_feature_flag(:filter_pipelines_search, project, default_enabled: true) push_frontend_feature_flag(:dag_pipeline_tab, project, default_enabled: true) push_frontend_feature_flag(:pipelines_security_report_summary, project) - push_frontend_feature_flag(:new_pipeline_form, project) + push_frontend_feature_flag(:new_pipeline_form, project, default_enabled: true) push_frontend_feature_flag(:graphql_pipeline_header, project, type: :development, default_enabled: false) + push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: false) push_frontend_feature_flag(:new_pipeline_form_prefilled_vars, project, type: :development) end before_action :ensure_pipeline, only: [:show] @@ -194,6 +194,7 @@ class Projects::PipelinesController < Projects::ApplicationController @counts[:total] = @project.all_pipelines.count(:all) @counts[:success] = @project.all_pipelines.success.count(:all) @counts[:failed] = @project.all_pipelines.failed.count(:all) + @counts[:total_duration] = @project.all_pipelines.total_duration end def test_report @@ -213,7 +214,7 @@ class Projects::PipelinesController < Projects::ApplicationController def config_variables respond_to do |format| format.json do - render json: Ci::ListConfigVariablesService.new(@project).execute(params[:sha]) + render json: Ci::ListConfigVariablesService.new(@project, current_user).execute(params[:sha]) end end end diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index a9490c106d4..d8ba7e4f235 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -6,13 +6,14 @@ class Projects::RawController < Projects::ApplicationController include SendsBlob include StaticObjectExternalStorage + skip_before_action :default_cache_headers, only: :show + prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:blob) } before_action :require_non_empty_project before_action :authorize_download_code! before_action :show_rate_limit, only: [:show], unless: :external_storage_request? before_action :assign_ref_vars - before_action :no_cache_headers, only: [:show] before_action :redirect_to_external_storage, only: :show, if: :static_objects_external_storage_enabled? feature_category :source_code_management diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index 4e8260d9e53..a6e795a2b91 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -54,7 +54,7 @@ class Projects::ReleasesController < Projects::ApplicationController end def sanitized_filepath - CGI.unescape(params[:filepath]) + "/#{CGI.unescape(params[:filepath])}" end def sanitized_tag_name diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index ba3ab52e3af..fb6a09cff65 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -8,6 +8,8 @@ class Projects::RepositoriesController < Projects::ApplicationController prepend_before_action(only: [:archive]) { authenticate_sessionless_user!(:archive) } + skip_before_action :default_cache_headers, only: :archive + # Authorize before_action :require_non_empty_project, except: :create before_action :archive_rate_limit!, only: :archive diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb index 544074f9840..24fa0894a9c 100644 --- a/app/controllers/projects/runners_controller.rb +++ b/app/controllers/projects/runners_controller.rb @@ -52,7 +52,7 @@ class Projects::RunnersController < Projects::ApplicationController end def toggle_shared_runners - if Feature.enabled?(:disable_shared_runners_on_group, default_enabled: true) && !project.shared_runners_enabled && project.group && project.group.shared_runners_setting == 'disabled_and_unoverridable' + if !project.shared_runners_enabled && project.group && project.group.shared_runners_setting == 'disabled_and_unoverridable' return redirect_to project_runners_path(@project), alert: _("Cannot enable shared runners because parent group does not allow it") end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 93ad549bc50..6ed9f74297d 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -13,6 +13,8 @@ class Projects::ServicesController < Projects::ApplicationController before_action :redirect_deprecated_prometheus_service, only: [:update] before_action only: :edit do push_frontend_feature_flag(:jira_issues_integration, @project, type: :licensed, default_enabled: true) + push_frontend_feature_flag(:jira_vulnerabilities_integration, @project, type: :licensed, default_enabled: true) + push_frontend_feature_flag(:jira_for_vulnerabilities, @project, type: :development, default_enabled: false) end respond_to :html @@ -70,7 +72,7 @@ class Projects::ServicesController < Projects::ApplicationController return { error: true, message: s_('Integrations|Connection failed. Please check your settings.'), service_response: result[:message].to_s, test_failed: true } end - {} + result[:data].presence || {} rescue Gitlab::HTTP::BlockedUrlError => e { error: true, message: s_('Integrations|Connection failed. Please check your settings.'), service_response: e.message, test_failed: true } end diff --git a/app/controllers/projects/settings/access_tokens_controller.rb b/app/controllers/projects/settings/access_tokens_controller.rb index cbd6716fdf7..74350147825 100644 --- a/app/controllers/projects/settings/access_tokens_controller.rb +++ b/app/controllers/projects/settings/access_tokens_controller.rb @@ -23,7 +23,7 @@ module Projects redirect_to namespace_project_settings_access_tokens_path, notice: _("Your new project access token has been created.") else - render :index + redirect_to namespace_project_settings_access_tokens_path, alert: _("Failed to create new project access token: %{token_response_message}") % { token_response_message: token_response.message } end end diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index 2963321f803..f76278a12a4 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -5,10 +5,11 @@ module Projects class CiCdController < Projects::ApplicationController include RunnerSetupScripts + NUMBER_OF_RUNNERS_PER_PAGE = 20 + before_action :authorize_admin_pipeline! before_action :define_variables before_action do - push_frontend_feature_flag(:new_variables_ui, @project, default_enabled: true) push_frontend_feature_flag(:ajax_new_deploy_token, @project) end @@ -76,7 +77,7 @@ module Projects [ :runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_human_readable, :build_coverage_regex, :public_builds, - :auto_cancel_pending_pipelines, :ci_config_path, + :auto_cancel_pending_pipelines, :ci_config_path, :auto_rollback_enabled, auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy], ci_cd_settings_attributes: [:default_git_depth, :forward_deployment_enabled] ].tap do |list| @@ -109,13 +110,13 @@ module Projects end def define_runners_variables - @project_runners = @project.runners.ordered + @project_runners = @project.runners.ordered.page(params[:project_page]).per(NUMBER_OF_RUNNERS_PER_PAGE).with_tags @assignable_runners = current_user .ci_owned_runners .assignable_for(project) .ordered - .page(params[:page]).per(20) + .page(params[:specific_page]).per(NUMBER_OF_RUNNERS_PER_PAGE) @shared_runners = ::Ci::Runner.instance_type.active diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb index c407b15e29f..c9386a2edec 100644 --- a/app/controllers/projects/settings/operations_controller.rb +++ b/app/controllers/projects/settings/operations_controller.rb @@ -6,6 +6,11 @@ module Projects before_action :authorize_admin_operations! before_action :authorize_read_prometheus_alerts!, only: [:reset_alerting_token] + before_action do + push_frontend_feature_flag(:http_integrations_list, @project) + push_frontend_feature_flag(:multiple_http_integrations_custom_mapping, @project) + end + respond_to :json, only: [:reset_alerting_token, :reset_pagerduty_token] helper_method :error_tracking_setting diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb index 0994bebb1d0..dd50ab1bc7a 100644 --- a/app/controllers/projects/settings/repository_controller.rb +++ b/app/controllers/projects/settings/repository_controller.rb @@ -18,14 +18,13 @@ module Projects end def cleanup - cleanup_params = params.require(:project).permit(:bfg_object_map) - result = Projects::UpdateService.new(project, current_user, cleanup_params).execute + bfg_object_map = params.require(:project).require(:bfg_object_map) + result = Projects::CleanupService.enqueue(project, current_user, bfg_object_map) if result[:status] == :success - RepositoryCleanupWorker.perform_async(project.id, current_user.id) # rubocop:disable CodeReuse/Worker flash[:notice] = _('Repository cleanup has started. You will receive an email once the cleanup operation is complete.') else - flash[:alert] = _('Failed to upload object map file') + flash[:alert] = status.fetch(:message, _('Failed to upload object map file')) end redirect_to project_settings_repository_path(project) diff --git a/app/controllers/projects/static_site_editor_controller.rb b/app/controllers/projects/static_site_editor_controller.rb index 7e2e32a843f..5c3d9b60877 100644 --- a/app/controllers/projects/static_site_editor_controller.rb +++ b/app/controllers/projects/static_site_editor_controller.rb @@ -6,12 +6,16 @@ class Projects::StaticSiteEditorController < Projects::ApplicationController layout 'fullscreen' + content_security_policy do |policy| + next if policy.directives.blank? + + frame_src_values = Array.wrap(policy.directives['frame-src']) | ['https://www.youtube.com'] + policy.frame_src(*frame_src_values) + end + prepend_before_action :authenticate_user!, only: [:show] before_action :assign_ref_and_path, only: [:show] before_action :authorize_edit_tree!, only: [:show] - before_action do - push_frontend_feature_flag(:sse_image_uploads) - end feature_category :static_site_editor @@ -47,6 +51,8 @@ class Projects::StaticSiteEditorController < Projects::ApplicationController payload.transform_values do |value| if value.is_a?(String) || value.is_a?(Integer) value + elsif value.nil? + '' else value.to_json end diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 1d783241196..94b0473e1f3 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -24,6 +24,7 @@ class Projects::TagsController < Projects::ApplicationController tag_names = @tags.map(&:name) @tags_pipelines = @project.ci_pipelines.latest_successful_for_refs(tag_names) @releases = project.releases.where(tag: tag_names) + @tag_pipeline_statuses = Ci::CommitStatusesFinder.new(@project, @repository, current_user, @tags).execute respond_to do |format| format.html diff --git a/app/controllers/projects/templates_controller.rb b/app/controllers/projects/templates_controller.rb index 7ab23e39cf0..f4726638777 100644 --- a/app/controllers/projects/templates_controller.rb +++ b/app/controllers/projects/templates_controller.rb @@ -7,6 +7,14 @@ class Projects::TemplatesController < Projects::ApplicationController feature_category :templates + def index + templates = @template_type.template_subsets(project) + + respond_to do |format| + format.json { render json: templates.to_json } + end + end + def show template = @template_type.find(params[:key], project) diff --git a/app/controllers/projects/terraform_controller.rb b/app/controllers/projects/terraform_controller.rb new file mode 100644 index 00000000000..aef163c98c5 --- /dev/null +++ b/app/controllers/projects/terraform_controller.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class Projects::TerraformController < Projects::ApplicationController + before_action :authorize_can_read_terraform_state! + + feature_category :infrastructure_as_code + + def index + end + + private + + def authorize_can_read_terraform_state! + access_denied! unless can?(current_user, :read_terraform_state, project) + end +end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 09e7563cefd..c03a820b384 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -15,7 +15,7 @@ class ProjectsController < Projects::ApplicationController around_action :allow_gitaly_ref_name_caching, only: [:index, :show] before_action :whitelist_query_limiting, only: [:create] - before_action :authenticate_user!, except: [:index, :show, :activity, :refs, :resolve] + before_action :authenticate_user!, except: [:index, :show, :activity, :refs, :resolve, :unfoldered_environment_names] before_action :redirect_git_extension, only: [:show] before_action :project, except: [:index, :new, :create, :resolve] before_action :repository, except: [:index, :new, :create, :resolve] @@ -317,8 +317,6 @@ class ProjectsController < Projects::ApplicationController end def unfoldered_environment_names - return render_404 unless Feature.enabled?(:deployment_filters) - respond_to do |format| format.json do render json: EnvironmentNamesFinder.new(@project, current_user).execute @@ -383,6 +381,20 @@ class ProjectsController < Projects::ApplicationController .merge(import_url_params) end + def project_feature_attributes + %i[ + builds_access_level + issues_access_level + forking_access_level + merge_requests_access_level + repository_access_level + snippets_access_level + wiki_access_level + pages_access_level + metrics_dashboard_access_level + ] + end + def project_params_attributes [ :allow_merge_on_skipped_pipeline, @@ -420,23 +432,11 @@ class ProjectsController < Projects::ApplicationController :suggestion_commit_message, :packages_enabled, :service_desk_enabled, - - project_feature_attributes: %i[ - builds_access_level - issues_access_level - forking_access_level - merge_requests_access_level - repository_access_level - snippets_access_level - wiki_access_level - pages_access_level - metrics_dashboard_access_level - ], project_setting_attributes: %i[ show_default_award_emojis squash_option ] - ] + ] + [project_feature_attributes: project_feature_attributes] end def project_params_create_attributes diff --git a/app/controllers/registrations/welcome_controller.rb b/app/controllers/registrations/welcome_controller.rb new file mode 100644 index 00000000000..5b3f78a92ad --- /dev/null +++ b/app/controllers/registrations/welcome_controller.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module Registrations + class WelcomeController < ApplicationController + layout 'welcome' + skip_before_action :authenticate_user!, :required_signup_info, :check_two_factor_requirement, only: [:show, :update] + before_action :require_current_user + + feature_category :authentication_and_authorization + + def show + return redirect_to path_for_signed_in_user(current_user) if completed_welcome_step? + end + + def update + result = ::Users::SignupService.new(current_user, update_params).execute + + if result[:status] == :success + process_gitlab_com_tracking + + return redirect_to new_users_sign_up_group_path if experiment_enabled?(:onboarding_issues) && show_onboarding_issues_experiment? + + redirect_to path_for_signed_in_user(current_user) + else + render :show + end + end + + private + + def require_current_user + return redirect_to new_user_registration_path unless current_user + end + + def completed_welcome_step? + current_user.role.present? && !current_user.setup_for_company.nil? + end + + def process_gitlab_com_tracking + return false unless ::Gitlab.com? + return false unless show_onboarding_issues_experiment? + + track_experiment_event(:onboarding_issues, 'signed_up') + record_experiment_user(:onboarding_issues) + end + + def update_params + params.require(:user).permit(:role, :setup_for_company) + end + + def requires_confirmation?(user) + return false if user.confirmed? + return false if Feature.enabled?(:soft_email_confirmation) + + true + end + + def path_for_signed_in_user(user) + return users_almost_there_path if requires_confirmation?(user) + + stored_location_for(user) || dashboard_projects_path + end + + def show_onboarding_issues_experiment? + !helpers.in_subscription_flow? && + !helpers.in_invitation_flow? && + !helpers.in_oauth_flow? && + !helpers.in_trial_flow? + end + end +end + +Registrations::WelcomeController.prepend_if_ee('EE::Registrations::WelcomeController') diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index b3dc0e986f4..04cb9616cf6 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -3,26 +3,22 @@ class RegistrationsController < Devise::RegistrationsController include Recaptcha::Verify include AcceptsPendingInvitations - include RecaptchaExperimentHelper + include RecaptchaHelper include InvisibleCaptchaOnSignup BLOCKED_PENDING_APPROVAL_STATE = 'blocked_pending_approval'.freeze - layout :choose_layout + layout 'devise' - skip_before_action :required_signup_info, :check_two_factor_requirement, only: [:welcome, :update_registration] prepend_before_action :check_captcha, only: :create before_action :whitelist_query_limiting, :ensure_destroy_prerequisites_met, only: [:destroy] before_action :load_recaptcha, only: :new + before_action :set_invite_params, only: :new feature_category :authentication_and_authorization def new - if experiment_enabled?(:signup_flow) - @resource = build_resource - else - redirect_to new_user_session_path(anchor: 'register-pane') - end + @resource = build_resource end def create @@ -32,6 +28,11 @@ class RegistrationsController < Devise::RegistrationsController super do |new_user| persist_accepted_terms_if_required(new_user) set_role_required(new_user) + + if pending_approval? + NotificationService.new.new_instance_access_request(new_user) + end + yield new_user if block_given? end @@ -52,31 +53,6 @@ class RegistrationsController < Devise::RegistrationsController end end - def welcome - return redirect_to new_user_registration_path unless current_user - return redirect_to path_for_signed_in_user(current_user) if current_user.role.present? && !current_user.setup_for_company.nil? - end - - def update_registration - return redirect_to new_user_registration_path unless current_user - - user_params = params.require(:user).permit(:role, :setup_for_company) - result = ::Users::SignupService.new(current_user, user_params).execute - - if result[:status] == :success - if ::Gitlab.com? && show_onboarding_issues_experiment? - track_experiment_event(:onboarding_issues, 'signed_up') - record_experiment_user(:onboarding_issues) - end - - return redirect_to new_users_sign_up_group_path if experiment_enabled?(:onboarding_issues) && show_onboarding_issues_experiment? - - redirect_to path_for_signed_in_user(current_user) - else - render :welcome - end - end - protected def persist_accepted_terms_if_required(new_user) @@ -160,6 +136,12 @@ class RegistrationsController < Devise::RegistrationsController render action: 'new' end + def pending_approval? + return false unless Gitlab::CurrentSettings.require_admin_approval_after_user_signup + + resource.persisted? && resource.blocked_pending_approval? + end + def sign_up_params params.require(:user).permit(:username, :email, :name, :first_name, :last_name, :password) end @@ -180,49 +162,17 @@ class RegistrationsController < Devise::RegistrationsController Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42380') end - def path_for_signed_in_user(user) - if requires_confirmation?(user) - users_almost_there_path - else - stored_location_for(user) || dashboard_projects_path - end - end - - def requires_confirmation?(user) - return false if user.confirmed? - return false if Feature.enabled?(:soft_email_confirmation) - return false if experiment_enabled?(:signup_flow) - - true - end - def load_recaptcha Gitlab::Recaptcha.load_configurations! end - # Part of an experiment to build a new sign up flow. Will be resolved - # with https://gitlab.com/gitlab-org/growth/engineering/issues/64 - def choose_layout - if %w(welcome update_registration).include?(action_name) || experiment_enabled?(:signup_flow) - 'devise_experimental_separate_sign_up_flow' - else - 'devise' - end - end - - def show_onboarding_issues_experiment? - !helpers.in_subscription_flow? && - !helpers.in_invitation_flow? && - !helpers.in_oauth_flow? && - !helpers.in_trial_flow? - end - def set_user_state - return unless Feature.enabled?(:admin_approval_for_new_user_signups, default_enabled: true) return unless Gitlab::CurrentSettings.require_admin_approval_after_user_signup resource.state = BLOCKED_PENDING_APPROVAL_STATE end -end -RegistrationsController.prepend_if_ee('EE::RegistrationsController') + def set_invite_params + @invite_email = ActionController::Base.helpers.sanitize(params[:invite_email]) + end +end diff --git a/app/controllers/repositories/git_http_client_controller.rb b/app/controllers/repositories/git_http_client_controller.rb index de452aa69b7..ec854bd0dde 100644 --- a/app/controllers/repositories/git_http_client_controller.rb +++ b/app/controllers/repositories/git_http_client_controller.rb @@ -6,7 +6,7 @@ module Repositories include KerberosSpnegoHelper include Gitlab::Utils::StrongMemoize - attr_reader :authentication_result, :redirected_path, :container + attr_reader :authentication_result, :redirected_path delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true delegate :type, to: :authentication_result, allow_nil: true, prefix: :auth_result @@ -75,6 +75,12 @@ module Repositories headers['Www-Authenticate'] = challenges.join("\n") if challenges.any? end + def container + parse_repo_path unless defined?(@container) + + @container + end + def project parse_repo_path unless defined?(@project) diff --git a/app/controllers/repositories/lfs_api_controller.rb b/app/controllers/repositories/lfs_api_controller.rb index 35751a2578f..96185608c09 100644 --- a/app/controllers/repositories/lfs_api_controller.rb +++ b/app/controllers/repositories/lfs_api_controller.rb @@ -17,9 +17,9 @@ module Repositories end if download_request? - render json: { objects: download_objects! } + render json: { objects: download_objects! }, content_type: LfsRequest::CONTENT_TYPE elsif upload_request? - render json: { objects: upload_objects! } + render json: { objects: upload_objects! }, content_type: LfsRequest::CONTENT_TYPE else raise "Never reached" end @@ -31,6 +31,7 @@ module Repositories message: _('Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.'), documentation_url: "#{Gitlab.config.gitlab.url}/help" }, + content_type: LfsRequest::CONTENT_TYPE, status: :not_implemented ) end diff --git a/app/controllers/repositories/lfs_storage_controller.rb b/app/controllers/repositories/lfs_storage_controller.rb index 0436b740979..48784842d48 100644 --- a/app/controllers/repositories/lfs_storage_controller.rb +++ b/app/controllers/repositories/lfs_storage_controller.rb @@ -29,7 +29,7 @@ module Repositories def upload_finalize if store_file!(oid, size) - head 200 + head 200, content_type: LfsRequest::CONTENT_TYPE else render plain: 'Unprocessable entity', status: :unprocessable_entity end @@ -59,10 +59,17 @@ module Repositories params[:size].to_i end + def uploaded_file + params[:file] + end + # rubocop: disable CodeReuse/ActiveRecord def store_file!(oid, size) object = LfsObject.find_by(oid: oid, size: size) - unless object&.file&.exists? + + if object + replace_file!(object) unless object.file&.exists? + else object = create_file!(oid, size) end @@ -73,12 +80,19 @@ module Repositories # rubocop: enable CodeReuse/ActiveRecord def create_file!(oid, size) - uploaded_file = params[:file] return unless uploaded_file.is_a?(UploadedFile) LfsObject.create!(oid: oid, size: size, file: uploaded_file) end + def replace_file!(lfs_object) + raise UploadedFile::InvalidPathError unless uploaded_file.is_a?(UploadedFile) + + Gitlab::AppJsonLogger.info(message: "LFS file replaced because it did not exist", oid: oid, size: size) + lfs_object.file = uploaded_file + lfs_object.save! + end + def link_to_project!(object) return unless object diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 0380bc1c548..4b21edc98d5 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -9,6 +9,7 @@ class SearchController < ApplicationController SCOPE_PRELOAD_METHOD = { projects: :with_web_entity_associations, issues: :with_web_entity_associations, + merge_requests: :with_web_entity_associations, epics: :with_web_entity_associations }.freeze @@ -35,7 +36,10 @@ class SearchController < ApplicationController return unless search_term_valid? + return if check_single_commit_result? + @search_term = params[:search] + @sort = params[:sort] || default_sort @scope = search_service.scope @show_snippets = search_service.show_snippets? @@ -47,8 +51,6 @@ class SearchController < ApplicationController eager_load_user_status if @scope == 'users' increment_search_counters - - check_single_commit_result end def count @@ -81,6 +83,11 @@ class SearchController < ApplicationController SCOPE_PRELOAD_METHOD[@scope.to_sym] end + # overridden in EE + def default_sort + 'created_desc' + end + def search_term_valid? unless search_service.valid_query_length? flash[:alert] = t('errors.messages.search_chars_too_long', count: SearchService::SEARCH_CHAR_LIMIT) @@ -103,14 +110,23 @@ class SearchController < ApplicationController @search_objects = @search_objects.eager_load(:status) # rubocop:disable CodeReuse/ActiveRecord end - def check_single_commit_result - if @search_results.single_commit_result? - only_commit = @search_results.objects('commits').first - query = params[:search].strip.downcase - found_by_commit_sha = Commit.valid_hash?(query) && only_commit.sha.start_with?(query) + def check_single_commit_result? + return false if params[:force_search_results] + return false unless @project.present? + # download_code project policy grants user the read_commit ability + return false unless Ability.allowed?(current_user, :download_code, @project) - redirect_to project_commit_path(@project, only_commit) if found_by_commit_sha - end + query = params[:search].strip.downcase + return false unless Commit.valid_hash?(query) + + commit = @project.commit_by(oid: query) + return false unless commit.present? + + link = search_path(safe_params.merge(force_search_results: true)) + flash[:notice] = html_escape(_("You have been redirected to the only result; see the %{a_start}search results%{a_end} instead.")) % { a_start: "<a href=\"#{link}\"><u>".html_safe, a_end: '</u></a>'.html_safe } + redirect_to project_commit_path(@project, commit) + + true end def increment_search_counters @@ -130,6 +146,9 @@ class SearchController < ApplicationController payload[:metadata]['meta.search.project_id'] = params[:project_id] payload[:metadata]['meta.search.search'] = params[:search] payload[:metadata]['meta.search.scope'] = params[:scope] + payload[:metadata]['meta.search.filters.confidential'] = params[:confidential] + payload[:metadata]['meta.search.filters.state'] = params[:state] + payload[:metadata]['meta.search.force_search_results'] = params[:force_search_results] end def block_anonymous_global_searches diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 61120c5b7d1..b8842b2efdb 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -300,13 +300,13 @@ class SessionsController < Devise::SessionsController def authentication_method if user_params[:otp_attempt] - "two-factor" + AuthenticationEvent::TWO_FACTOR elsif user_params[:device_response] && Feature.enabled?(:webauthn) - "two-factor-via-webauthn-device" + AuthenticationEvent::TWO_FACTOR_WEBAUTHN elsif user_params[:device_response] && !Feature.enabled?(:webauthn) - "two-factor-via-u2f-device" + AuthenticationEvent::TWO_FACTOR_U2F else - "standard" + AuthenticationEvent::STANDARD end end diff --git a/app/controllers/whats_new_controller.rb b/app/controllers/whats_new_controller.rb index 7156faa4e49..384c984089a 100644 --- a/app/controllers/whats_new_controller.rb +++ b/app/controllers/whats_new_controller.rb @@ -5,14 +5,14 @@ class WhatsNewController < ApplicationController skip_before_action :authenticate_user! - before_action :check_feature_flag + before_action :check_feature_flag, :check_valid_page_param, :set_pagination_headers feature_category :navigation def index respond_to do |format| format.js do - render json: whats_new_most_recent_release_items + render json: whats_new_release_items(page: current_page) end end end @@ -22,4 +22,23 @@ class WhatsNewController < ApplicationController def check_feature_flag render_404 unless Feature.enabled?(:whats_new_drawer, current_user) end + + def check_valid_page_param + render_404 if current_page < 1 + end + + def set_pagination_headers + response.set_header('X-Next-Page', next_page) + end + + def current_page + params[:page]&.to_i || 1 + end + + def next_page + next_page = current_page + 1 + next_index = next_page - 1 + + next_page if whats_new_file_paths[next_index] + end end |