diff options
Diffstat (limited to 'app/controllers')
72 files changed, 847 insertions, 396 deletions
diff --git a/app/controllers/acme_challenges_controller.rb b/app/controllers/acme_challenges_controller.rb index 4a7706db94e..a187e43b3df 100644 --- a/app/controllers/acme_challenges_controller.rb +++ b/app/controllers/acme_challenges_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -# rubocop:disable Rails/ApplicationController -class AcmeChallengesController < ActionController::Base +class AcmeChallengesController < BaseActionController def show if acme_order render plain: acme_order.challenge_file_content, content_type: 'text/plain' @@ -16,4 +15,3 @@ class AcmeChallengesController < ActionController::Base @acme_order ||= PagesDomainAcmeOrder.find_by_domain_and_token(params[:domain], params[:token]) end end -# rubocop:enable Rails/ApplicationController diff --git a/app/controllers/activity_pub/application_controller.rb b/app/controllers/activity_pub/application_controller.rb index f9c2b14fe77..70cf881c857 100644 --- a/app/controllers/activity_pub/application_controller.rb +++ b/app/controllers/activity_pub/application_controller.rb @@ -8,6 +8,8 @@ module ActivityPub skip_before_action :authenticate_user! after_action :set_content_type + protect_from_forgery with: :null_session + def can?(object, action, subject = :global) Ability.allowed?(object, action, subject) end diff --git a/app/controllers/activity_pub/projects/releases_controller.rb b/app/controllers/activity_pub/projects/releases_controller.rb index 7c4c2a0322b..eeff96a5ef7 100644 --- a/app/controllers/activity_pub/projects/releases_controller.rb +++ b/app/controllers/activity_pub/projects/releases_controller.rb @@ -5,15 +5,27 @@ module ActivityPub class ReleasesController < ApplicationController feature_category :release_orchestration + before_action :enforce_payload, only: :inbox + def index opts = { - inbox: nil, + inbox: inbox_project_releases_url(@project), outbox: outbox_project_releases_url(@project) } render json: ActivityPub::ReleasesActorSerializer.new.represent(@project, opts) end + def inbox + service = inbox_service + success = service ? service.execute : true + + response = { success: success } + response[:errors] = service.errors unless success + + render json: response + end + def outbox serializer = ActivityPub::ReleasesOutboxSerializer.new.with_pagination(request, response) render json: serializer.represent(releases) @@ -24,6 +36,39 @@ module ActivityPub def releases(params = {}) ReleasesFinder.new(@project, current_user, params).execute end + + def enforce_payload + return if payload + + head :unprocessable_entity + false + end + + def payload + @payload ||= begin + Gitlab::Json.parse(request.body.read) + rescue JSON::ParserError + nil + end + end + + def follow? + payload['type'] == 'Follow' + end + + def unfollow? + undo = payload['type'] == 'Undo' + object = payload['object'] + follow = object.present? && object.is_a?(Hash) && object['type'] == 'Follow' + undo && follow + end + + def inbox_service + return ReleasesFollowService.new(project, payload) if follow? + return ReleasesUnfollowService.new(project, payload) if unfollow? + + nil + end end end end diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 8cf0ab60fd3..cd099173718 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -164,10 +164,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController params.delete(:domain_denylist_raw) if params[:domain_denylist] params.delete(:domain_allowlist_raw) if params[:domain_allowlist] - if params[:application_setting].key?(:user_email_lookup_limit) - params[:application_setting][:search_rate_limit] ||= params[:application_setting][:user_email_lookup_limit] - end - params[:application_setting].permit(visible_application_setting_attributes) end @@ -183,6 +179,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController *::ApplicationSettingsHelper.visible_attributes, *::ApplicationSettingsHelper.external_authorization_service_attributes, *ApplicationSetting.kroki_formats_attributes.keys.map { |key| "kroki_formats_#{key}".to_sym }, + :can_create_organization, :lets_encrypt_notification_email, :lets_encrypt_terms_of_service_accepted, :domain_denylist_file, diff --git a/app/controllers/admin/clusters_controller.rb b/app/controllers/admin/clusters_controller.rb index 052c8821588..ead02a7aa8c 100644 --- a/app/controllers/admin/clusters_controller.rb +++ b/app/controllers/admin/clusters_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Admin::ClustersController < Clusters::ClustersController +class Admin::ClustersController < ::Clusters::ClustersController include EnforcesAdminAuthentication before_action :ensure_feature_enabled! diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb index b24b25446b0..a9b34bf56f6 100644 --- a/app/controllers/admin/deploy_keys_controller.rb +++ b/app/controllers/admin/deploy_keys_controller.rb @@ -55,7 +55,7 @@ class Admin::DeployKeysController < Admin::ApplicationController end def create_params - params.require(:deploy_key).permit(:key, :title) + params.require(:deploy_key).permit(:key, :title, :expires_at) end def update_params diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6739fc57a1f..fca3bb3460f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,7 +3,7 @@ require 'gon' require 'fogbugz' -class ApplicationController < ActionController::Base +class ApplicationController < BaseActionController include Gitlab::GonHelper include Gitlab::NoCacheHeaders include GitlabRoutingHelper @@ -25,7 +25,6 @@ class ApplicationController < ActionController::Base include FlocOptOut include CheckRateLimit include RequestPayloadLogger - extend ContentSecurityPolicyPatch before_action :limit_session_time, if: -> { !current_user } before_action :authenticate_user!, except: [:route_not_found] @@ -113,33 +112,6 @@ class ApplicationController < ActionController::Base render plain: e.message, status: :service_unavailable end - content_security_policy do |p| - next if p.directives.blank? - - if Rails.env.development? && Feature.enabled?(:vite) - vite_host = ViteRuby.instance.config.host - vite_port = ViteRuby.instance.config.port - vite_origin = "#{vite_host}:#{vite_port}" - http_origin = "http://#{vite_origin}" - ws_origin = "ws://#{vite_origin}" - wss_origin = "wss://#{vite_origin}" - gitlab_ws_origin = Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'vite-dev/') - http_path = Gitlab::Utils.append_path(http_origin, 'vite-dev/') - - connect_sources = p.directives['connect-src'] - p.connect_src(*(Array.wrap(connect_sources) | [ws_origin, wss_origin, http_path])) - - worker_sources = p.directives['worker-src'] - p.worker_src(*(Array.wrap(worker_sources) | [gitlab_ws_origin, http_path])) - end - - next unless Gitlab::CurrentSettings.snowplow_enabled? && !Gitlab::CurrentSettings.snowplow_collector_hostname.blank? - - default_connect_src = p.directives['connect-src'] || p.directives['default-src'] - connect_src_values = Array.wrap(default_connect_src) | [Gitlab::CurrentSettings.snowplow_collector_hostname] - p.connect_src(*connect_src_values) - end - def redirect_back_or_default(default: root_path, options: {}) redirect_back(fallback_location: default, **options) end @@ -325,7 +297,7 @@ class ApplicationController < ActionController::Base return if current_user.nil? if current_user.password_expired? && current_user.allow_password_authentication? - redirect_to new_profile_password_path + redirect_to new_user_settings_password_path end end diff --git a/app/controllers/base_action_controller.rb b/app/controllers/base_action_controller.rb new file mode 100644 index 00000000000..e251c44f63d --- /dev/null +++ b/app/controllers/base_action_controller.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# GitLab lightweight base action controller +# +# This class should be limited to content that +# is desired/required for *all* controllers in +# GitLab. +# +# Most controllers inherit from `ApplicationController`. +# Some controllers don't want or need all of that +# logic and instead inherit from `ActionController::Base`. +# This makes it difficult to set security headers and +# handle other critical logic across *all* controllers. +# +# Between this controller and `ApplicationController` +# no controller should ever inherit directly from +# `ActionController::Base` +# +# rubocop:disable Rails/ApplicationController -- This class is specifically meant as a base class for controllers that +# don't inherit from ApplicationController +# rubocop:disable Gitlab/NamespacedClass -- Base controllers live in the global namespace +class BaseActionController < ActionController::Base + extend ContentSecurityPolicyPatch + + content_security_policy do |p| + next if p.directives.blank? + + if helpers.vite_enabled? + vite_port = ViteRuby.instance.config.port + vite_origin = "#{Gitlab.config.gitlab.host}:#{vite_port}" + http_origin = "http://#{vite_origin}" + ws_origin = "ws://#{vite_origin}" + wss_origin = "wss://#{vite_origin}" + gitlab_ws_origin = Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'vite-dev/') + http_path = Gitlab::Utils.append_path(http_origin, 'vite-dev/') + + connect_sources = p.directives['connect-src'] + p.connect_src(*(Array.wrap(connect_sources) | [ws_origin, wss_origin, http_path])) + + worker_sources = p.directives['worker-src'] + p.worker_src(*(Array.wrap(worker_sources) | [gitlab_ws_origin, http_path])) + end + + next unless Gitlab::CurrentSettings.snowplow_enabled? && !Gitlab::CurrentSettings.snowplow_collector_hostname.blank? + + default_connect_src = p.directives['connect-src'] || p.directives['default-src'] + connect_src_values = Array.wrap(default_connect_src) | [Gitlab::CurrentSettings.snowplow_collector_hostname] + p.connect_src(*connect_src_values) + end +end +# rubocop:enable Gitlab/NamespacedClass +# rubocop:enable Rails/ApplicationController diff --git a/app/controllers/chaos_controller.rb b/app/controllers/chaos_controller.rb index 7328b793b09..b61a8c5ff12 100644 --- a/app/controllers/chaos_controller.rb +++ b/app/controllers/chaos_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -# rubocop:disable Rails/ApplicationController -class ChaosController < ActionController::Base +class ChaosController < BaseActionController before_action :validate_chaos_secret, unless: :development_or_test? def leakmem @@ -95,4 +94,3 @@ class ChaosController < ActionController::Base Rails.env.development? || Rails.env.test? end end -# rubocop:enable Rails/ApplicationController diff --git a/app/controllers/clusters/agents/dashboard_controller.rb b/app/controllers/clusters/agents/dashboard_controller.rb index 1f72aaa4775..7016ebacfba 100644 --- a/app/controllers/clusters/agents/dashboard_controller.rb +++ b/app/controllers/clusters/agents/dashboard_controller.rb @@ -6,15 +6,15 @@ module Clusters include KasCookie before_action :check_feature_flag! - before_action :find_agent - before_action :authorize_read_cluster_agent! + before_action :find_agent, only: [:show], if: -> { current_user } + before_action :authorize_read_cluster_agent!, only: [:show], if: -> { current_user } before_action :set_kas_cookie, only: [:show], if: -> { current_user } feature_category :deployment_management - def show - head :ok - end + def index; end + + def show; end private diff --git a/app/controllers/clusters/base_controller.rb b/app/controllers/clusters/base_controller.rb index e7b76b87ad9..495b6463383 100644 --- a/app/controllers/clusters/base_controller.rb +++ b/app/controllers/clusters/base_controller.rb @@ -4,6 +4,7 @@ class Clusters::BaseController < ApplicationController include RoutableActions skip_before_action :authenticate_user! + before_action :clusterable before_action :authorize_admin_cluster!, except: [:show, :index, :new, :authorize_aws_role, :update] helper_method :clusterable @@ -41,6 +42,11 @@ class Clusters::BaseController < ApplicationController access_denied! unless can?(current_user, :read_prometheus, clusterable) end + # For Group/Clusters and Project/Clusters, the clusterable object (group or project) + # is fetched through `find_routable!`, which calls a `render_404` if the user does not have access to the object + # The `clusterable` method will need to be in its own before_action call before the `authorize_*` calls + # so that the call stack will not proceed to the `authorize_*` calls + # and instead just render a not found page after the `clusterable` call def clusterable raise NotImplementedError end diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index b012a4e003e..917007144fa 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Clusters::ClustersController < Clusters::BaseController +class Clusters::ClustersController < ::Clusters::BaseController include RoutableActions before_action :cluster, only: [:cluster_status, :show, :update, :destroy, :clear_cache] diff --git a/app/controllers/concerns/autocomplete_sources/expires_in.rb b/app/controllers/concerns/autocomplete_sources/expires_in.rb new file mode 100644 index 00000000000..d61cd88bd91 --- /dev/null +++ b/app/controllers/concerns/autocomplete_sources/expires_in.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module AutocompleteSources + module ExpiresIn + extend ActiveSupport::Concern + + AUTOCOMPLETE_EXPIRES_IN = 3.minutes + AUTOCOMPLETE_CACHED_ACTIONS = [:members, :commands, :labels].freeze + + included do + before_action :set_expires_in, only: AUTOCOMPLETE_CACHED_ACTIONS + end + + private + + def set_expires_in + case action_name.to_sym + when :members + expires_in AUTOCOMPLETE_EXPIRES_IN if Feature.enabled?(:cache_autocomplete_sources_members, current_user) + when :commands + expires_in AUTOCOMPLETE_EXPIRES_IN if Feature.enabled?(:cache_autocomplete_sources_commands, current_user) + when :labels + expires_in AUTOCOMPLETE_EXPIRES_IN if Feature.enabled?(:cache_autocomplete_sources_labels, current_user) + end + end + end +end diff --git a/app/controllers/concerns/import/github_oauth.rb b/app/controllers/concerns/import/github_oauth.rb index ae5a0401155..fa6162254dc 100644 --- a/app/controllers/concerns/import/github_oauth.rb +++ b/app/controllers/concerns/import/github_oauth.rb @@ -56,7 +56,11 @@ module Import session[:auth_on_failure_path] = "#{new_project_path}#import_project" oauth_client.auth_code.authorize_url( redirect_uri: callback_import_url, - scope: 'repo, user, user:email', + # read:org only required for collaborator import, which is optional, + # but at the time of this OAuth request we do not know which optional + # configuration the user will select because the options are only shown + # after authenticating + scope: 'repo, read:org', state: state ) end diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index b4f5589a059..042adc8479e 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -4,6 +4,11 @@ module MembershipActions include MembersPresentation extend ActiveSupport::Concern + included do + before_action :authenticate_user!, only: :request_access + before_action :already_a_member!, only: :request_access + end + def update member = members_and_requesters.find(params[:id]) result = Members::UpdateService @@ -166,6 +171,20 @@ module MembershipActions end end end + + def authenticate_user! + return if current_user + + redirect_to new_user_session_path + end + + def already_a_member! + member = members_and_requesters.find_by(user_id: current_user.id) # rubocop: disable CodeReuse/ActiveRecord + return if member.nil? + + message = member.request? ? _('You have already requested access.') : _('You already have access.') + redirect_to polymorphic_path(membershipable), notice: message + end end MembershipActions.prepend_mod_with('MembershipActions') diff --git a/app/controllers/concerns/onboarding/redirectable.rb b/app/controllers/concerns/onboarding/redirectable.rb index 7e669db9199..15c1847ebe4 100644 --- a/app/controllers/concerns/onboarding/redirectable.rb +++ b/app/controllers/concerns/onboarding/redirectable.rb @@ -9,7 +9,7 @@ module Onboarding def after_sign_up_path if onboarding_status.single_invite? flash[:notice] = helpers.invite_accepted_notice(onboarding_status.last_invited_member) - onboarding_status.last_invited_member_source.activity_path + polymorphic_path(onboarding_status.last_invited_member_source) else # Invites will come here if there is more than 1. path_for_signed_in_user @@ -17,13 +17,13 @@ module Onboarding end def path_for_signed_in_user - stored_location_for(:user) || last_member_activity_path + stored_location_for(:user) || last_member_source_path end - def last_member_activity_path + def last_member_source_path return dashboard_projects_path unless onboarding_status.last_invited_member_source.present? - onboarding_status.last_invited_member_source.activity_path + polymorphic_path(onboarding_status.last_invited_member_source) end end end diff --git a/app/controllers/concerns/product_analytics_tracking.rb b/app/controllers/concerns/product_analytics_tracking.rb index d4610267897..e148f5d063a 100644 --- a/app/controllers/concerns/product_analytics_tracking.rb +++ b/app/controllers/concerns/product_analytics_tracking.rb @@ -14,7 +14,7 @@ module ProductAnalyticsTracking end def track_internal_event(*controller_actions, name:, conditions: nil) - custom_conditions = [:trackable_html_request?, :authenticated?, *conditions] + custom_conditions = [:trackable_html_request?, *conditions] after_action only: controller_actions, if: custom_conditions do Gitlab::InternalEvents.track_event( @@ -70,8 +70,4 @@ module ProductAnalyticsTracking cookies[:visitor_id] = { value: uuid, expires: 24.months } uuid end - - def authenticated? - current_user.present? - end end diff --git a/app/controllers/explore/catalog_controller.rb b/app/controllers/explore/catalog_controller.rb index 3cd3771129e..d384ad10c86 100644 --- a/app/controllers/explore/catalog_controller.rb +++ b/app/controllers/explore/catalog_controller.rb @@ -2,8 +2,14 @@ module Explore class CatalogController < Explore::ApplicationController + include ProductAnalyticsTracking + feature_category :pipeline_composition - before_action :check_feature_flag + before_action :check_resource_access, only: :show + track_internal_event :index, name: 'unique_users_visiting_ci_catalog' + before_action do + push_frontend_feature_flag(:ci_catalog_components_tab, current_user) + end def show; end @@ -13,8 +19,20 @@ module Explore private - def check_feature_flag - render_404 unless Feature.enabled?(:global_ci_catalog, current_user) + def check_resource_access + render_404 unless catalog_resource.present? + end + + def catalog_resource + ::Ci::Catalog::Listing.new(current_user).find_resource(full_path: params[:full_path]) + end + + def tracking_namespace_source + current_user.namespace + end + + def tracking_project_source + nil end end end diff --git a/app/controllers/external_redirect/external_redirect_controller.rb b/app/controllers/external_redirect/external_redirect_controller.rb index 532196157b7..8d5f07ad3eb 100644 --- a/app/controllers/external_redirect/external_redirect_controller.rb +++ b/app/controllers/external_redirect/external_redirect_controller.rb @@ -11,7 +11,6 @@ module ExternalRedirect redirect_to url_param else render layout: 'fullscreen', locals: { - minimal: true, url: url_param } end @@ -29,8 +28,13 @@ module ExternalRedirect uri_data.site == Gitlab.config.gitlab.url end + def should_handle_url?(url) + # note: To avoid lots of redirects, don't allow url to point to self. + ::Gitlab::UrlSanitizer.valid_web?(url) && !url.starts_with?(request.base_url + request.path) + end + def check_url_param - render_404 unless ::Gitlab::UrlSanitizer.valid_web?(url_param) + render_404 unless should_handle_url?(url_param) end end end diff --git a/app/controllers/groups/autocomplete_sources_controller.rb b/app/controllers/groups/autocomplete_sources_controller.rb index 86bf65f4723..7a490b34511 100644 --- a/app/controllers/groups/autocomplete_sources_controller.rb +++ b/app/controllers/groups/autocomplete_sources_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Groups::AutocompleteSourcesController < Groups::ApplicationController + include AutocompleteSources::ExpiresIn + feature_category :groups_and_projects, [:members] feature_category :team_planning, [:issues, :labels, :milestones, :commands] feature_category :code_review_workflow, [:merge_requests] @@ -8,11 +10,6 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController urgency :low, [:issues, :labels, :milestones, :commands, :merge_requests, :members] def members - if Feature.enabled?(:cache_autocomplete_sources_members, current_user) - # Cache the response on the frontend - expires_in 3.minutes - end - render json: ::Groups::ParticipantsService.new(@group, current_user).execute(target) end diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb index 6bb807be1c4..7cc0e6a8558 100644 --- a/app/controllers/groups/boards_controller.rb +++ b/app/controllers/groups/boards_controller.rb @@ -8,6 +8,7 @@ class Groups::BoardsController < Groups::ApplicationController before_action do push_frontend_feature_flag(:board_multi_select, group) push_frontend_feature_flag(:apollo_boards, group) + push_frontend_feature_flag(:display_work_item_epic_issue_sidebar, group) experiment(:prominent_create_board_btn, subject: current_user) do |e| e.control {} e.candidate {} diff --git a/app/controllers/groups/clusters_controller.rb b/app/controllers/groups/clusters_controller.rb index 2fe9faa252f..5b1508f44a2 100644 --- a/app/controllers/groups/clusters_controller.rb +++ b/app/controllers/groups/clusters_controller.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true -class Groups::ClustersController < Clusters::ClustersController +class Groups::ClustersController < ::Clusters::ClustersController include ControllerWithCrossProjectAccessCheck before_action :ensure_feature_enabled! - prepend_before_action :group requires_cross_project_access layout 'group' @@ -12,7 +11,7 @@ class Groups::ClustersController < Clusters::ClustersController private def clusterable - @clusterable ||= ClusterablePresenter.fabricate(group, current_user: current_user) + @clusterable ||= group && ClusterablePresenter.fabricate(group, current_user: current_user) end def group diff --git a/app/controllers/groups/work_items_controller.rb b/app/controllers/groups/work_items_controller.rb index ece279da778..bfb5b74d2a5 100644 --- a/app/controllers/groups/work_items_controller.rb +++ b/app/controllers/groups/work_items_controller.rb @@ -20,3 +20,5 @@ module Groups end end end + +Groups::WorkItemsController.prepend_mod diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb index 1381999ab4c..2b2db2f950c 100644 --- a/app/controllers/health_controller.rb +++ b/app/controllers/health_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -# rubocop:disable Rails/ApplicationController -class HealthController < ActionController::Base +class HealthController < BaseActionController protect_from_forgery with: :exception, prepend: true include RequiresAllowlistedMonitoringClient @@ -40,4 +39,3 @@ class HealthController < ActionController::Base render json: result.json, status: result.http_status end end -# rubocop:enable Rails/ApplicationController diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb index 4cc943ac252..4a4d41f3e6f 100644 --- a/app/controllers/ide_controller.rb +++ b/app/controllers/ide_controller.rb @@ -5,7 +5,8 @@ class IdeController < ApplicationController include StaticObjectExternalStorageCSP include Gitlab::Utils::StrongMemoize - before_action :authorize_read_project! + before_action :authorize_read_project!, only: [:index] + before_action :ensure_web_ide_oauth_application!, only: [:index] before_action do push_frontend_feature_flag(:build_service_proxy) @@ -24,7 +25,17 @@ class IdeController < ApplicationController @fork_info = fork_info(project, params[:branch]) end - render layout: 'fullscreen', locals: { minimal: helpers.use_new_web_ide? } + render layout: helpers.use_new_web_ide? ? 'fullscreen' : 'application' + end + + def oauth_redirect + return render_404 unless ::Gitlab::WebIde::DefaultOauthApplication.feature_enabled?(current_user) + # TODO - It's **possible** we end up here and no oauth application has been set up. + # We need to have better handling of these edge cases. Here's a follow-up issue: + # https://gitlab.com/gitlab-org/gitlab/-/issues/433322 + return render_404 unless ::Gitlab::WebIde::DefaultOauthApplication.oauth_application + + render layout: 'fullscreen', locals: { minimal: true } end private @@ -33,6 +44,12 @@ class IdeController < ApplicationController render_404 unless can?(current_user, :read_project, project) end + def ensure_web_ide_oauth_application! + return unless ::Gitlab::WebIde::DefaultOauthApplication.feature_enabled?(current_user) + + ::Gitlab::WebIde::DefaultOauthApplication.ensure_oauth_application! + end + def fork_info(project, branch) return if can?(current_user, :push_code, project) diff --git a/app/controllers/import/bulk_imports_controller.rb b/app/controllers/import/bulk_imports_controller.rb index bc425323d6f..e211ea70a56 100644 --- a/app/controllers/import/bulk_imports_controller.rb +++ b/app/controllers/import/bulk_imports_controller.rb @@ -76,7 +76,7 @@ class Import::BulkImportsController < ApplicationController def realtime_changes Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL) - render json: current_user_bulk_imports.to_json(only: [:id], methods: [:status_name]) + render json: current_user_bulk_imports.to_json(only: [:id], methods: [:status_name, :has_failures]) end private diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index c058329680a..fcd87f46f67 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -125,14 +125,14 @@ class InvitesController < ApplicationController name: member.source.full_name, url: project_url(member.source), title: _("project"), - path: member.source.activity_path + path: project_path(member.source) } when Group { name: member.source.name, url: group_url(member.source), title: _("group"), - path: member.source.activity_path + path: group_path(member.source) } end end diff --git a/app/controllers/jwks_controller.rb b/app/controllers/jwks_controller.rb index d3a8d3dafea..2e030cf46c4 100644 --- a/app/controllers/jwks_controller.rb +++ b/app/controllers/jwks_controller.rb @@ -2,6 +2,10 @@ class JwksController < Doorkeeper::OpenidConnect::DiscoveryController def index + if ::Feature.enabled?(:cache_control_headers_for_openid_jwks) + expires_in 24.hours, public: true, must_revalidate: true, 'no-transform': true + end + render json: { keys: payload } end diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 83409c7e096..e6e232cfbc3 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -33,6 +33,7 @@ class JwtController < ApplicationController @authentication_result = Gitlab::Auth::Result.new(nil, nil, :none, Gitlab::Auth.read_only_authentication_abilities) authenticate_with_http_basic do |login, password| + @raw_token = password @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, request: request) if @authentication_result.failed? @@ -80,6 +81,7 @@ class JwtController < ApplicationController def additional_params { scopes: scopes_param, + raw_token: @raw_token, deploy_token: @authentication_result.deploy_token, auth_type: @authentication_result.type }.compact diff --git a/app/controllers/metrics_controller.rb b/app/controllers/metrics_controller.rb index 9f41c092fa0..61851fd1c60 100644 --- a/app/controllers/metrics_controller.rb +++ b/app/controllers/metrics_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -# rubocop:disable Rails/ApplicationController -class MetricsController < ActionController::Base +class MetricsController < BaseActionController include RequiresAllowlistedMonitoringClient protect_from_forgery with: :exception, prepend: true @@ -36,4 +35,3 @@ class MetricsController < ActionController::Base ) end end -# rubocop:enable Rails/ApplicationController diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index 6fc2eb6bc45..42c65a845c6 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -19,7 +19,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio Doorkeeper::Application.revoke_tokens_and_grants_for(params[:id], current_resource_owner) end - redirect_to applications_profile_url, + redirect_to user_settings_applications_url, status: :found, notice: I18n.t(:notice, scope: [:doorkeeper, :flash, :authorized_applications, :destroy]) end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index a97516fddff..907ece1a06e 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -8,6 +8,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController include KnownSignIn include AcceptsPendingInvitations include Onboarding::Redirectable + include InternalRedirect + + ACTIVE_SINCE_KEY = 'active_since' after_action :verify_known_sign_in @@ -113,14 +116,21 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController super end + def store_redirect_to + # overridden in EE + end + def omniauth_flow(auth_module, identity_linker: nil) if fragment = request.env.dig('omniauth.params', 'redirect_fragment').presence store_redirect_fragment(fragment) end + store_redirect_to + if current_user return render_403 unless link_provider_allowed?(oauth['provider']) + set_session_active_since(oauth['provider']) if ::AuthHelper.saml_providers.include?(oauth['provider'].to_sym) track_event(current_user, oauth['provider'], 'succeeded') if Gitlab::CurrentSettings.admin_mode @@ -167,6 +177,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController end end + # Overrided in EE + def set_session_active_since(id); end + def sign_in_user_flow(auth_user_class) auth_user = build_auth_user(auth_user_class) new_user = auth_user.new? @@ -181,6 +194,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController Gitlab::Tracking.event(self.class.name, "#{oauth['provider']}_sso", user: @user) if new_user set_remember_me(@user) + set_session_active_since(oauth['provider']) if ::AuthHelper.saml_providers.include?(oauth['provider'].to_sym) if @user.two_factor_enabled? && !auth_user.bypass_two_factor? prompt_for_two_factor(@user) diff --git a/app/controllers/organizations/application_controller.rb b/app/controllers/organizations/application_controller.rb index 8a99b6804ae..9cc33ec0447 100644 --- a/app/controllers/organizations/application_controller.rb +++ b/app/controllers/organizations/application_controller.rb @@ -28,6 +28,10 @@ module Organizations access_denied! unless can?(current_user, :read_organization, organization) end + def authorize_read_organization_user! + access_denied! unless can?(current_user, :read_organization_user, organization) + end + def authorize_admin_organization! access_denied! unless can?(current_user, :admin_organization, organization) end diff --git a/app/controllers/organizations/organizations_controller.rb b/app/controllers/organizations/organizations_controller.rb index 3085f0c07d1..9f09627b1e4 100644 --- a/app/controllers/organizations/organizations_controller.rb +++ b/app/controllers/organizations/organizations_controller.rb @@ -4,7 +4,7 @@ module Organizations class OrganizationsController < ApplicationController feature_category :cell - skip_before_action :authenticate_user!, except: [:index, :new] + skip_before_action :authenticate_user!, except: [:index, :new, :users] def index; end @@ -21,7 +21,7 @@ module Organizations end def users - authorize_read_organization! + authorize_read_organization_user! end end end diff --git a/app/controllers/profiles/active_sessions_controller.rb b/app/controllers/profiles/active_sessions_controller.rb deleted file mode 100644 index 5a86179b89f..00000000000 --- a/app/controllers/profiles/active_sessions_controller.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -class Profiles::ActiveSessionsController < Profiles::ApplicationController - feature_category :system_access - - def index - @sessions = ActiveSession.list(current_user).reject(&:is_impersonated) - end - - def destroy - # params[:id] can be an Rack::Session::SessionId#private_id - ActiveSession.destroy_session(current_user, params[:id]) - current_user.forget_me! - - respond_to do |format| - format.html { redirect_to profile_active_sessions_url, status: :found } - format.js { head :ok } - end - end -end diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb deleted file mode 100644 index 7a0dfbbba0d..00000000000 --- a/app/controllers/profiles/passwords_controller.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -class Profiles::PasswordsController < Profiles::ApplicationController - include Gitlab::Tracking::Helpers::WeakPasswordErrorEvent - - skip_before_action :check_password_expiration, only: [:new, :create] - skip_before_action :check_two_factor_requirement, only: [:new, :create] - - before_action :set_user - before_action :authorize_change_password! - - layout :determine_layout - - feature_category :system_access - - def new - end - - def create - unless @user.password_automatically_set || @user.valid_password?(user_params[:password]) - redirect_to new_profile_password_path, alert: _('You must provide a valid current password') - return - end - - result = Users::UpdateService.new(current_user, password_attributes.merge(user: @user)).execute - - if result[:status] == :success - Users::UpdateService.new(current_user, user: @user, password_expires_at: nil).execute - - redirect_to root_path, notice: _('Password successfully changed') - else - track_weak_password_error(@user, self.class.name, 'create') - render :new - end - end - - def edit - end - - def update - unless @user.password_automatically_set || @user.valid_password?(user_params[:password]) - handle_invalid_current_password_attempt! - - redirect_to edit_profile_password_path, alert: _('You must provide a valid current password') - return - end - - result = Users::UpdateService.new(current_user, password_attributes.merge(user: @user)).execute - - if result[:status] == :success - flash[:notice] = _('Password was successfully updated. Please sign in again.') - redirect_to new_user_session_path - else - track_weak_password_error(@user, self.class.name, 'update') - @user.reset - render 'edit' - end - end - - def reset - current_user.send_reset_password_instructions - redirect_to edit_profile_password_path, notice: _('We sent you an email with reset password instructions') - end - - private - - def set_user - @user = current_user - end - - def determine_layout - if [:new, :create].include?(action_name.to_sym) - 'application' - else - 'profile' - end - end - - def authorize_change_password! - render_404 unless @user.allow_password_authentication? - end - - def handle_invalid_current_password_attempt! - Gitlab::AppLogger.info(message: 'Invalid current password when attempting to update user password', username: @user.username, ip: request.remote_ip) - - @user.increment_failed_attempts! - end - - def user_params - params.require(:user).permit(:password, :new_password, :password_confirmation) - end - - def password_attributes - { - password: user_params[:new_password], - password_confirmation: user_params[:password_confirmation], - password_automatically_set: false - } - end -end - -Profiles::PasswordsController.prepend_mod diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb deleted file mode 100644 index 4b6e2f768fa..00000000000 --- a/app/controllers/profiles/personal_access_tokens_controller.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true - -class Profiles::PersonalAccessTokensController < Profiles::ApplicationController - include RenderAccessTokens - - feature_category :system_access - - before_action :check_personal_access_tokens_enabled - - def index - set_index_vars - scopes = params[:scopes].split(',').map(&:squish).select(&:present?).map(&:to_sym) unless params[:scopes].nil? - @personal_access_token = finder.build( - name: params[:name], - scopes: scopes - ) - - respond_to do |format| - format.html - format.json do - render json: @active_access_tokens - end - end - end - - def create - result = ::PersonalAccessTokens::CreateService.new( - current_user: current_user, - target_user: current_user, - params: personal_access_token_params, - concatenate_errors: false - ).execute - - @personal_access_token = result.payload[:personal_access_token] - - if result.success? - render json: { new_token: @personal_access_token.token, - active_access_tokens: active_access_tokens }, status: :ok - else - render json: { errors: result.errors }, status: :unprocessable_entity - end - end - - def revoke - @personal_access_token = finder.find(params[:id]) - service = PersonalAccessTokens::RevokeService.new(current_user, token: @personal_access_token).execute - service.success? ? flash[:notice] = service.message : flash[:alert] = service.message - - redirect_to profile_personal_access_tokens_path - end - - private - - def finder(options = {}) - PersonalAccessTokensFinder.new({ user: current_user, impersonation: false }.merge(options)) - end - - def personal_access_token_params - params.require(:personal_access_token).permit(:name, :expires_at, scopes: []) - end - - def set_index_vars - @scopes = Gitlab::Auth.available_scopes_for(current_user) - @active_access_tokens = active_access_tokens - end - - def represent(tokens) - ::PersonalAccessTokenSerializer.new.represent(tokens) - end - - def check_personal_access_tokens_enabled - render_404 if Gitlab::CurrentSettings.personal_access_tokens_disabled? - end -end diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 7059e2a0371..4c93c738484 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -41,6 +41,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController :color_scheme_id, :diffs_deletion_color, :diffs_addition_color, + :home_organization_id, :layout, :dashboard, :project_view, diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index cb29f0f3539..39a070b6405 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -14,7 +14,6 @@ class ProfilesController < Profiles::ApplicationController feature_category :user_profile, [:show, :update, :reset_incoming_email_token, :reset_feed_token, :reset_static_object_token, :update_username] - feature_category :system_access, [:audit_log] urgency :low, [:show, :update] def show @@ -43,7 +42,7 @@ class ProfilesController < Profiles::ApplicationController flash[:notice] = s_("Profiles|Incoming email token was successfully reset") - redirect_to profile_personal_access_tokens_path + redirect_to user_settings_personal_access_tokens_path end def reset_feed_token @@ -53,7 +52,7 @@ class ProfilesController < Profiles::ApplicationController flash[:notice] = s_('Profiles|Feed token was successfully reset') - redirect_to profile_personal_access_tokens_path + redirect_to user_settings_personal_access_tokens_path end def reset_static_object_token @@ -61,20 +60,10 @@ class ProfilesController < Profiles::ApplicationController user.reset_static_object_token! end - redirect_to profile_personal_access_tokens_path, + redirect_to user_settings_personal_access_tokens_path, notice: s_('Profiles|Static object token was successfully reset') end - # rubocop: disable CodeReuse/ActiveRecord - def audit_log - @events = AuthenticationEvent.where(user: current_user) - .order("created_at DESC") - .page(params[:page]) - - Gitlab::Tracking.event(self.class.name, 'search_audit_event', user: current_user) - end - # rubocop: enable CodeReuse/ActiveRecord - def update_username result = Users::UpdateService.new(current_user, user: @user, username: username_param).execute diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb index 60c8fe97e81..ff3484d3020 100644 --- a/app/controllers/projects/autocomplete_sources_controller.rb +++ b/app/controllers/projects/autocomplete_sources_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Projects::AutocompleteSourcesController < Projects::ApplicationController + include AutocompleteSources::ExpiresIn + before_action :authorize_read_milestone!, only: :milestones before_action :authorize_read_crm_contact!, only: :contacts @@ -13,11 +15,6 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController urgency :low, [:issues, :labels, :milestones, :commands, :contacts] def members - if Feature.enabled?(:cache_autocomplete_sources_members, current_user) - # Cache the response on the frontend - expires_in 3.minutes - end - render json: ::Projects::ParticipantsService.new(@project, current_user).execute(target) end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 7371902a6bd..7851e2ac80b 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -52,6 +52,7 @@ class Projects::BlobController < Projects::ApplicationController push_frontend_feature_flag(:blob_blame_info, @project) push_frontend_feature_flag(:highlight_js_worker, @project) push_frontend_feature_flag(:explain_code_chat, current_user) + push_frontend_feature_flag(:encoding_logs_tree) push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks) end diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 84872d1e978..fd853b5aaed 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -8,6 +8,7 @@ class Projects::BoardsController < Projects::ApplicationController before_action do push_frontend_feature_flag(:board_multi_select, project) push_frontend_feature_flag(:apollo_boards, project) + push_frontend_feature_flag(:display_work_item_epic_issue_sidebar, project) experiment(:prominent_create_board_btn, subject: current_user) do |e| e.control {} e.candidate {} diff --git a/app/controllers/projects/ci/pipeline_editor_controller.rb b/app/controllers/projects/ci/pipeline_editor_controller.rb index 6e7f764c5c1..62a5baccc62 100644 --- a/app/controllers/projects/ci/pipeline_editor_controller.rb +++ b/app/controllers/projects/ci/pipeline_editor_controller.rb @@ -4,7 +4,6 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController before_action :check_can_collaborate! before_action do push_frontend_feature_flag(:ci_job_assistant_drawer, @project) - push_frontend_feature_flag(:ai_ci_config_generator, @user) push_frontend_feature_flag(:ci_graphql_pipeline_mini_graph, @project) end diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb index b781365b3c3..dd969efa49b 100644 --- a/app/controllers/projects/clusters_controller.rb +++ b/app/controllers/projects/clusters_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -class Projects::ClustersController < Clusters::ClustersController - prepend_before_action :project +class Projects::ClustersController < ::Clusters::ClustersController before_action :repository before_action do @@ -13,7 +12,7 @@ class Projects::ClustersController < Clusters::ClustersController private def clusterable - @clusterable ||= ClusterablePresenter.fabricate(project, current_user: current_user) + @clusterable ||= project && ClusterablePresenter.fabricate(project, current_user: current_user) end def project diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 9cdbd2a30f6..66ce501f9f0 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -22,6 +22,34 @@ class Projects::DeployKeysController < Projects::ApplicationController end end + def enabled_keys + respond_to do |format| + format.json do + enabled_keys = find_keys(filter: :enabled_keys) + render json: { keys: serialize(enabled_keys) } + end + end + end + + def available_project_keys + respond_to do |format| + format.json do + available_project_keys = find_keys(filter: :available_project_keys) + render json: { keys: serialize(available_project_keys) } + end + end + end + + def available_public_keys + respond_to do |format| + format.json do + available_public_keys = find_keys(filter: :available_public_keys) + + render json: { keys: serialize(available_public_keys) } + end + end + end + def new redirect_to_repository end @@ -108,4 +136,17 @@ class Projects::DeployKeysController < Projects::ApplicationController def redirect_to_repository redirect_to_repository_settings(@project, anchor: 'js-deploy-keys-settings') end + + def find_keys(params) + DeployKeys::DeployKeysFinder.new(@project, current_user, params) + .execute + end + + def serialize(keys) + opts = { user: current_user, project: project } + + DeployKeys::DeployKeySerializer.new + .with_pagination(request, response) + .represent(keys, opts) + end end diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 4b2749dc716..8cdd6efa7c5 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -14,6 +14,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController push_frontend_feature_flag(:k8s_watch_api, project) end + before_action only: [:folder] do + push_frontend_feature_flag(:environments_folder_new_look, project) + end + before_action :authorize_read_environment! before_action :authorize_create_environment!, only: [:new, :create] before_action :authorize_stop_environment!, only: [:stop] diff --git a/app/controllers/projects/gcp/artifact_registry/base_controller.rb b/app/controllers/projects/gcp/artifact_registry/base_controller.rb new file mode 100644 index 00000000000..4084427f3e5 --- /dev/null +++ b/app/controllers/projects/gcp/artifact_registry/base_controller.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Projects + module Gcp + module ArtifactRegistry + class BaseController < ::Projects::ApplicationController + before_action :ensure_feature_flag + before_action :ensure_saas + before_action :authorize_read_container_image! + before_action :ensure_private_project + + feature_category :container_registry + urgency :low + + private + + def ensure_feature_flag + return if Feature.enabled?(:gcp_technical_demo, project) + + @error = 'Feature flag disabled' + + render + end + + def ensure_saas + return if Gitlab.com_except_jh? # rubocop: disable Gitlab/AvoidGitlabInstanceChecks -- demo requirement + + @error = "Can't run here" + + render + end + + def ensure_private_project + return if project.private? + + @error = 'Can only run on private projects' + + render + end + end + end + end +end diff --git a/app/controllers/projects/gcp/artifact_registry/docker_images_controller.rb b/app/controllers/projects/gcp/artifact_registry/docker_images_controller.rb new file mode 100644 index 00000000000..b88b86975a4 --- /dev/null +++ b/app/controllers/projects/gcp/artifact_registry/docker_images_controller.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +module Projects + module Gcp + module ArtifactRegistry + class DockerImagesController < Projects::Gcp::ArtifactRegistry::BaseController + before_action :require_gcp_params + before_action :handle_pagination + + REPO_NAME_REGEX = %r{/repositories/(.*)/dockerImages/} + + def index + result = service.execute(page_token: params[:page_token]) + + if result.success? + @docker_images = process_docker_images(result.payload[:images] || []) + @next_page_token = result.payload[:next_page_token] + @artifact_repository_name = artifact_repository_name + @error = @docker_images.blank? ? 'No docker images' : false + else + @error = result.message + end + end + + private + + def service + ::Integrations::GoogleCloudPlatform::ArtifactRegistry::ListDockerImagesService.new( + project: @project, + current_user: current_user, + params: { + gcp_project_id: gcp_project_id, + gcp_location: gcp_location, + gcp_repository: gcp_ar_repository, + gcp_wlif: gcp_wlif_url + } + ) + end + + def process_docker_images(raw_images) + raw_images.map { |r| process_docker_image(r) } + end + + def process_docker_image(raw_image) + DockerImage.new( + name: raw_image[:name], + uri: raw_image[:uri], + tags: raw_image[:tags], + image_size_bytes: raw_image[:size_bytes], + media_type: raw_image[:media_type], + upload_time: raw_image[:uploaded_at], + build_time: raw_image[:built_at], + update_time: raw_image[:updated_at] + ) + end + + def artifact_repository_name + return unless @docker_images.present? + + (@docker_images.first.name || '')[REPO_NAME_REGEX, 1] + end + + def handle_pagination + @page = Integer(params[:page] || 1) + @page_tokens = {} + @previous_page_token = nil + + if params[:page_tokens] + @page_tokens = ::Gitlab::Json.parse(Base64.decode64(params[:page_tokens])) + @previous_page_token = @page_tokens[(@page - 1).to_s] + end + + @page_tokens[@page.to_s] = params[:page_token] + @page_tokens = Base64.encode64(::Gitlab::Json.dump(@page_tokens.compact)) + end + + def require_gcp_params + return unless gcp_project_id.blank? || gcp_location.blank? || gcp_ar_repository.blank? || gcp_wlif_url.blank? + + redirect_to new_namespace_project_gcp_artifact_registry_setup_path + end + + def gcp_project_id + params[:gcp_project_id] + end + + def gcp_location + params[:gcp_location] + end + + def gcp_ar_repository + params[:gcp_ar_repository] + end + + def gcp_wlif_url + params[:gcp_wlif_url] + end + + class DockerImage + include ActiveModel::API + + attr_accessor :name, :uri, :tags, :image_size_bytes, :upload_time, :media_type, :build_time, :update_time + + SHORT_NAME_REGEX = %r{dockerImages/(.*)$} + + def short_name + (name || '')[SHORT_NAME_REGEX, 1] + end + + def updated_at + return unless update_time + + Time.zone.parse(update_time) + end + + def built_at + return unless build_time + + Time.zone.parse(build_time) + end + + def uploaded_at + return unless upload_time + + Time.zone.parse(upload_time) + end + end + end + end + end +end diff --git a/app/controllers/projects/gcp/artifact_registry/setup_controller.rb b/app/controllers/projects/gcp/artifact_registry/setup_controller.rb new file mode 100644 index 00000000000..e90304ce593 --- /dev/null +++ b/app/controllers/projects/gcp/artifact_registry/setup_controller.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Projects + module Gcp + module ArtifactRegistry + class SetupController < ::Projects::Gcp::ArtifactRegistry::BaseController + def new; end + end + end + end +end diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 5f8bf423219..855b9824cf2 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -3,7 +3,7 @@ class Projects::GroupLinksController < Projects::ApplicationController layout 'project_settings' before_action :authorize_admin_project!, except: [:destroy] - before_action :authorize_admin_project_group_link!, only: [:destroy] + before_action :authorize_manage_destroy!, only: [:destroy] before_action :authorize_admin_project_member!, only: [:update] feature_category :groups_and_projects @@ -20,8 +20,8 @@ class Projects::GroupLinksController < Projects::ApplicationController else render json: {} end - elsif result.reason == :not_found - render json: { message: result.message }, status: :not_found + else + render json: { message: result.message }, status: result.reason end end @@ -47,7 +47,7 @@ class Projects::GroupLinksController < Projects::ApplicationController end format.js do - render json: { message: result.message }, status: :not_found if result.reason == :not_found + render json: { message: result.message }, status: result.reason end end end @@ -55,8 +55,8 @@ class Projects::GroupLinksController < Projects::ApplicationController protected - def authorize_admin_project_group_link! - render_404 unless can?(current_user, :admin_project_group_link, group_link) + def authorize_manage_destroy! + render_404 unless can?(current_user, :manage_destroy, group_link) end def group_link diff --git a/app/controllers/projects/integrations/shimos_controller.rb b/app/controllers/projects/integrations/shimos_controller.rb deleted file mode 100644 index 6c8313d0805..00000000000 --- a/app/controllers/projects/integrations/shimos_controller.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module Projects - module Integrations - class ShimosController < Projects::ApplicationController - feature_category :integrations - - before_action :ensure_renderable - - def show; end - - private - - def ensure_renderable - render_404 unless project.has_shimo? && project.shimo_integration&.render? - end - end - end -end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index a6444dc038c..d0eabf8d837 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -49,6 +49,7 @@ class Projects::IssuesController < Projects::ApplicationController push_frontend_feature_flag(:service_desk_ticket) push_frontend_feature_flag(:issues_list_drawer, project) push_frontend_feature_flag(:linked_work_items, project) + push_frontend_feature_flag(:display_work_item_epic_issue_sidebar, project) end before_action only: [:index, :show] do @@ -67,6 +68,7 @@ class Projects::IssuesController < Projects::ApplicationController push_force_frontend_feature_flag(:work_items_mvc_2, project&.work_items_mvc_2_feature_flag_enabled?) push_frontend_feature_flag(:epic_widget_edit_confirmation, project) push_frontend_feature_flag(:moved_mr_sidebar, project) + push_frontend_feature_flag(:display_work_item_epic_issue_sidebar, project) push_force_frontend_feature_flag(:linked_work_items, project.linked_work_items_feature_flag_enabled?) push_frontend_feature_flag(:notifications_todos_buttons, current_user) end diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index d5a7f25d4ce..4062e625e07 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -21,7 +21,6 @@ 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 :push_job_log_jump_to_failures, only: [:show] before_action :reject_if_build_artifacts_size_refreshing!, only: [:erase] before_action :push_ai_build_failure_cause, only: [:show] layout 'project' @@ -277,10 +276,6 @@ class Projects::JobsController < Projects::ApplicationController ::Gitlab::Workhorse.channel_websocket(service) end - def push_job_log_jump_to_failures - push_frontend_feature_flag(:job_log_jump_to_failures, @project) - end - def push_ai_build_failure_cause push_frontend_feature_flag(:ai_build_failure_cause, @project) end diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index 5bd0063ab95..b269d41fa77 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -32,8 +32,11 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic # rubocop: disable Metrics/AbcSize def diffs_batch + collapse_generated = Feature.enabled?(:collapse_generated_diff_files, project) + diff_options_hash = diff_options diff_options_hash[:paths] = params[:paths] if params[:paths] + diff_options_hash[:collapse_generated] = collapse_generated diffs = @compare.diffs_in_batch(params[:page], params[:per_page], diff_options: diff_options_hash) @@ -59,7 +62,8 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic params[:expanded], params[:page], params[:per_page], - options[:merge_ref_head_diff] + options[:merge_ref_head_diff], + collapse_generated ] expires_in(1.day) if cache_with_max_age? diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index eb7505bd81f..0899e303305 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -41,18 +41,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:moved_mr_sidebar, project) push_frontend_feature_flag(:sast_reports_in_inline_diff, project) push_frontend_feature_flag(:mr_experience_survey, project) - push_force_frontend_feature_flag(:summarize_my_code_review, summarize_my_code_review_enabled?) push_frontend_feature_flag(:ci_job_failures_in_mr, project) push_frontend_feature_flag(:mr_pipelines_graphql, project) push_frontend_feature_flag(:notifications_todos_buttons, current_user) - push_frontend_feature_flag(:widget_pipeline_pass_subscription_update, project) push_frontend_feature_flag(:mr_request_changes, current_user) - end - - before_action only: [:edit] do - if can?(current_user, :fill_in_merge_request_template, project) - push_frontend_feature_flag(:fill_in_mr_template, project) - end + push_frontend_feature_flag(:merge_blocked_component, current_user) end around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :diffs, :discussions] @@ -345,15 +338,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end def discussions - if Feature.enabled?(:only_highlight_discussions_requested, project) - super do |discussion_notes| - note_ids = discussion_notes.flat_map { |x| x.notes.collect(&:id) } - merge_request.discussions_diffs.load_highlight(diff_note_ids: note_ids) - end - else - merge_request.discussions_diffs.load_highlight - - super + super do |discussion_notes| + note_ids = discussion_notes.flat_map { |x| x.notes.collect(&:id) } + merge_request.discussions_diffs.load_highlight(diff_note_ids: note_ids) end end @@ -461,7 +448,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo @update_current_user_path = expose_path(api_v4_user_preferences_path) @endpoint_metadata_url = endpoint_metadata_url(@project, @merge_request) @endpoint_diff_batch_url = endpoint_diff_batch_url(@project, @merge_request) - @diffs_batch_cache_key = @merge_request.merge_head_diff&.id if merge_request.diffs_batch_cache_with_max_age? + + if merge_request.diffs_batch_cache_with_max_age? + @diffs_batch_cache_key = @merge_request.merge_head_diff&.patch_id_sha + end set_pipeline_variables @@ -471,11 +461,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end def get_diffs_count - if show_only_context_commits? - @merge_request.context_commits_diff.raw_diffs.size - else - @merge_request.diff_size - end + return @merge_request.context_commits_diff.raw_diffs.size if show_only_context_commits? + return @merge_request.merge_request_diffs.find_by_id(params[:diff_id])&.size if params[:diff_id] + + @merge_request.diff_size end def merge_request_update_params @@ -598,7 +587,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo when :parsed render json: Gitlab::Json.dump(report_comparison[:data]), status: :ok when :error - render json: { status_reason: report_comparison[:status_reason] }, status: :bad_request + render json: { + errors: [report_comparison[:status_reason]], + status_reason: report_comparison[:status_reason] + }, + status: :bad_request else raise "Failed to build comparison response as comparison yielded unknown status '#{report_comparison[:status]}'" end @@ -629,7 +622,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo params = request .query_parameters .merge(view: 'inline', diff_head: true, w: show_whitespace, page: '0', per_page: per_page) - params[:ck] = merge_request.merge_head_diff&.id if merge_request.diffs_batch_cache_with_max_age? + params[:ck] = merge_request.merge_head_diff&.patch_id_sha if merge_request.diffs_batch_cache_with_max_age? diffs_batch_project_json_merge_request_path(project, merge_request, 'json', params) end @@ -638,16 +631,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo Date.strptime(date, "%Y-%m-%d")&.to_time&.to_i if date rescue Date::Error, TypeError end - - def summarize_my_code_review_enabled? - namespace = project&.group&.root_ancestor - return false if namespace.nil? - - Feature.enabled?(:summarize_my_code_review, current_user) && - namespace.group_namespace? && - namespace.licensed_feature_available?(:summarize_my_mr_code_review) && - Gitlab::Llm::StageCheck.available?(namespace, :summarize_my_mr_code_review) - end end Projects::MergeRequestsController.prepend_mod_with('Projects::MergeRequestsController') diff --git a/app/controllers/projects/ml/candidates_controller.rb b/app/controllers/projects/ml/candidates_controller.rb index 12111c45fde..9905e454acb 100644 --- a/app/controllers/projects/ml/candidates_controller.rb +++ b/app/controllers/projects/ml/candidates_controller.rb @@ -9,9 +9,7 @@ module Projects feature_category :mlops - def show - @include_ci_info = @candidate.from_ci? && can?(current_user, :read_build, @candidate.ci_build) - end + def show; end def destroy @experiment = @candidate.experiment diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index cd2db2dad2c..516aa70cf89 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -166,6 +166,8 @@ class Projects::PipelinesController < Projects::ApplicationController @stage = pipeline.stage(params[:stage]) return not_found unless @stage + return unless stage_stale? + render json: StageSerializer .new(project: @project, current_user: @current_user) .represent(@stage, details: true, retried: params[:retried]) @@ -263,6 +265,14 @@ class Projects::PipelinesController < Projects::ApplicationController redirect_to url_for(safe_params.except(:scope).merge(status: safe_params[:scope])), status: :moved_permanently end + def stage_stale? + return true if Feature.disabled?(:pipeline_stage_set_last_modified, @current_user) + + last_modified = [@stage.updated_at.utc, @stage.statuses.maximum(:updated_at)].max + + stale?(last_modified: last_modified, etag: @stage) + end + # rubocop: disable CodeReuse/ActiveRecord def pipeline return @pipeline if defined?(@pipeline) diff --git a/app/controllers/projects/service_desk/custom_email_controller.rb b/app/controllers/projects/service_desk/custom_email_controller.rb index fb5e87f9a97..7c1623cfcd1 100644 --- a/app/controllers/projects/service_desk/custom_email_controller.rb +++ b/app/controllers/projects/service_desk/custom_email_controller.rb @@ -3,7 +3,6 @@ module Projects module ServiceDesk class CustomEmailController < Projects::ApplicationController - before_action :check_feature_flag_enabled before_action :authorize_admin_project! feature_category :service_desk @@ -75,10 +74,6 @@ module Projects error_message: error_message } end - - def check_feature_flag_enabled - render_404 unless Feature.enabled?(:service_desk_custom_email, @project) - end end end end diff --git a/app/controllers/projects/service_desk_controller.rb b/app/controllers/projects/service_desk_controller.rb index 70cb439c4f3..a53e8859ee6 100644 --- a/app/controllers/projects/service_desk_controller.rb +++ b/app/controllers/projects/service_desk_controller.rb @@ -29,7 +29,13 @@ class Projects::ServiceDeskController < Projects::ApplicationController end def allowed_update_attributes - %i[issue_template_key outgoing_name project_key add_external_participants_from_cc] + %i[ + issue_template_key + outgoing_name + project_key + reopen_issue_on_external_participant_note + add_external_participants_from_cc + ] end def service_desk_attributes @@ -42,6 +48,7 @@ class Projects::ServiceDeskController < Projects::ApplicationController template_file_missing: service_desk_settings&.issue_template_missing?, outgoing_name: service_desk_settings&.outgoing_name, project_key: service_desk_settings&.project_key, + reopen_issue_on_external_participant_note: service_desk_settings&.reopen_issue_on_external_participant_note, add_external_participants_from_cc: service_desk_settings&.add_external_participants_from_cc } end diff --git a/app/controllers/projects/settings/merge_requests_controller.rb b/app/controllers/projects/settings/merge_requests_controller.rb index f09e324f574..2724e2d9eec 100644 --- a/app/controllers/projects/settings/merge_requests_controller.rb +++ b/app/controllers/projects/settings/merge_requests_controller.rb @@ -52,6 +52,7 @@ module Projects :resolve_outdated_diff_discussions, :only_allow_merge_if_all_discussions_are_resolved, :only_allow_merge_if_pipeline_succeeds, + :allow_merge_without_pipeline, :printing_merge_request_link_enabled, :remove_source_branch_after_merge, :merge_method, diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index cfcc27edf3e..1bbf272e8f9 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -22,6 +22,7 @@ class Projects::TreeController < Projects::ApplicationController push_frontend_feature_flag(:blob_blame_info, @project) push_frontend_feature_flag(:highlight_js_worker, @project) push_frontend_feature_flag(:explain_code_chat, current_user) + push_frontend_feature_flag(:encoding_logs_tree) push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks) end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index cee56dca538..1152bdcf058 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -42,8 +42,8 @@ class ProjectsController < Projects::ApplicationController push_frontend_feature_flag(:highlight_js_worker, @project) push_frontend_feature_flag(:remove_monitor_metrics, @project) push_frontend_feature_flag(:explain_code_chat, current_user) - push_frontend_feature_flag(:service_desk_custom_email, @project) push_frontend_feature_flag(:issue_email_participants, @project) + push_frontend_feature_flag(:encoding_logs_tree) # TODO: We need to remove the FF eventually when we rollout page_specific_styles push_frontend_feature_flag(:page_specific_styles, current_user) push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks) @@ -398,6 +398,7 @@ class ProjectsController < Projects::ApplicationController if can?(current_user, :read_code, @project) return render 'projects/no_repo' unless @project.repository_exists? + return render 'projects/missing_default_branch', status: :service_unavailable if @ref == '' render 'projects/empty' if @project.empty_repo? else @@ -442,6 +443,7 @@ class ProjectsController < Projects::ApplicationController params.require(:project) .permit(project_params_attributes + attributes) .merge(import_url_params) + .merge(object_format_params) end def project_feature_attributes @@ -465,6 +467,7 @@ class ProjectsController < Projects::ApplicationController monitor_access_level infrastructure_access_level model_experiments_access_level + model_registry_access_level ] end @@ -497,6 +500,7 @@ class ProjectsController < Projects::ApplicationController :name, :only_allow_merge_if_all_discussions_are_resolved, :only_allow_merge_if_pipeline_succeeds, + :allow_merge_without_pipeline, :path, :printing_merge_request_link_enabled, :public_builds, @@ -529,6 +533,12 @@ class ProjectsController < Projects::ApplicationController {} end + def object_format_params + return {} unless Gitlab::Utils.to_boolean(params.dig(:project, :use_sha256_repository)) + + { repository_object_format: Repository::FORMAT_SHA256 } + end + def active_new_project_tab project_params[:import_url].present? ? 'import' : 'blank' end @@ -552,6 +562,9 @@ class ProjectsController < Projects::ApplicationController # Override get_id from ExtractsPath in this case is just the root of the default branch. def get_id project.repository.root_ref + rescue Gitlab::Git::CommandError + # Empty string is intentional and prevent the @ref reload + '' end def build_canonical_path(project) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 72636a89433..ed0f1687420 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -23,6 +23,7 @@ class RegistrationsController < Devise::RegistrationsController before_action :load_recaptcha, only: :new before_action only: [:create] do check_rate_limit!(:user_sign_up, scope: request.ip) + invite_email # set for failure path so we still remember we are invite in form end feature_category :instance_resiliency @@ -271,10 +272,15 @@ class RegistrationsController < Devise::RegistrationsController def set_invite_params if resource.email.blank? && params[:invite_email].present? - resource.email = @invite_email = ActionController::Base.helpers.sanitize(params[:invite_email]) + resource.email = invite_email end end + def invite_email + ActionController::Base.helpers.sanitize(params[:invite_email]) + end + strong_memoize_attr :invite_email + def user_invited? !!member_id end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index b639a9dda3f..64d9db41a1b 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -27,7 +27,10 @@ class SearchController < ApplicationController around_action :allow_gitaly_ref_name_caching - before_action :block_anonymous_global_searches, :check_scope_global_search_enabled, except: :opensearch + before_action :block_all_anonymous_searches, + :block_anonymous_global_searches, + :check_scope_global_search_enabled, + except: :opensearch skip_before_action :authenticate_user! requires_cross_project_access if: -> do @@ -191,17 +194,7 @@ class SearchController < ApplicationController # Merging to :metadata will ensure these are logged as top level keys payload[:metadata] ||= {} - payload[:metadata]['meta.search.group_id'] = params[:group_id] - payload[:metadata]['meta.search.project_id'] = params[:project_id] - payload[:metadata]['meta.search.scope'] = params[:scope] || @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] - payload[:metadata]['meta.search.project_ids'] = params[:project_ids] - payload[:metadata]['meta.search.filters.language'] = params[:language] - payload[:metadata]['meta.search.type'] = @search_type if @search_type.present? - payload[:metadata]['meta.search.level'] = @search_level if @search_level.present? - payload[:metadata][:global_search_duration_s] = @global_search_duration_s if @global_search_duration_s.present? + payload[:metadata].merge!(payload_metadata) if search_service.abuse_detected? payload[:metadata]['abuse.confidence'] = Gitlab::Abuse.confidence(:certain) @@ -209,6 +202,23 @@ class SearchController < ApplicationController end end + def payload_metadata + {}.tap do |metadata| + metadata['meta.search.group_id'] = params[:group_id] + metadata['meta.search.project_id'] = params[:project_id] + metadata['meta.search.scope'] = params[:scope] || @scope + metadata['meta.search.page'] = params[:page] || '1' + metadata['meta.search.filters.confidential'] = params[:confidential] + metadata['meta.search.filters.state'] = params[:state] + metadata['meta.search.force_search_results'] = params[:force_search_results] + metadata['meta.search.project_ids'] = params[:project_ids] + metadata['meta.search.filters.language'] = params[:language] + metadata['meta.search.type'] = @search_type if @search_type.present? + metadata['meta.search.level'] = @search_level if @search_level.present? + metadata[:global_search_duration_s] = @global_search_duration_s if @global_search_duration_s.present? + end + end + def block_anonymous_global_searches return unless search_service.global_search? return if current_user @@ -219,6 +229,14 @@ class SearchController < ApplicationController redirect_to new_user_session_path, alert: _('You must be logged in to search across all of GitLab') end + def block_all_anonymous_searches + return if current_user || ::Feature.enabled?(:allow_anonymous_searches, type: :ops) + + store_location_for(:user, request.fullpath) + + redirect_to new_user_session_path, alert: _('You must be logged in to search') + end + def check_scope_global_search_enabled return unless search_service.global_search? diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 595d79abcf2..a8a09bd6ac6 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -100,7 +100,7 @@ class SessionsController < Devise::SessionsController def after_pending_invitations_hook member = resource.members.last - store_location_for(:user, member.source.activity_path) if member + store_location_for(:user, polymorphic_path(member.source)) if member end def captcha_enabled? diff --git a/app/controllers/user_settings/active_sessions_controller.rb b/app/controllers/user_settings/active_sessions_controller.rb new file mode 100644 index 00000000000..bfc969d0ff8 --- /dev/null +++ b/app/controllers/user_settings/active_sessions_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module UserSettings + class ActiveSessionsController < ApplicationController + feature_category :system_access + + def index + @sessions = ActiveSession.list(current_user).reject(&:is_impersonated) + end + + def destroy + # params[:id] can be an Rack::Session::SessionId#private_id + ActiveSession.destroy_session(current_user, params[:id]) + current_user.forget_me! + + respond_to do |format| + format.html { redirect_to user_settings_active_sessions_url, status: :found } + format.js { head :ok } + end + end + end +end diff --git a/app/controllers/user_settings/application_controller.rb b/app/controllers/user_settings/application_controller.rb new file mode 100644 index 00000000000..d495c604bf3 --- /dev/null +++ b/app/controllers/user_settings/application_controller.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module UserSettings + class ApplicationController < ::ApplicationController + layout 'profile' + end +end diff --git a/app/controllers/user_settings/passwords_controller.rb b/app/controllers/user_settings/passwords_controller.rb new file mode 100644 index 00000000000..d68ddf90d49 --- /dev/null +++ b/app/controllers/user_settings/passwords_controller.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +module UserSettings + class PasswordsController < ApplicationController + include Gitlab::Tracking::Helpers::WeakPasswordErrorEvent + + skip_before_action :check_password_expiration, only: [:new, :create] + skip_before_action :check_two_factor_requirement, only: [:new, :create] + + before_action :set_user + before_action :authorize_change_password! + + layout :determine_layout + + feature_category :system_access + + def new; end + + def create + unless @user.password_automatically_set || @user.valid_password?(user_params[:password]) + redirect_to new_user_settings_password_path, alert: _('You must provide a valid current password') + return + end + + result = Users::UpdateService.new(current_user, password_attributes.merge(user: @user)).execute + + if result[:status] == :success + Users::UpdateService.new(current_user, user: @user, password_expires_at: nil).execute + + redirect_to root_path, notice: _('Password successfully changed') + else + track_weak_password_error(@user, self.class.name, 'create') + render :new + end + end + + def edit; end + + def update + unless @user.password_automatically_set || @user.valid_password?(user_params[:password]) + handle_invalid_current_password_attempt! + + redirect_to edit_user_settings_password_path, alert: _('You must provide a valid current password') + return + end + + result = Users::UpdateService.new(current_user, password_attributes.merge(user: @user)).execute + + if result[:status] == :success + flash[:notice] = _('Password was successfully updated. Please sign in again.') + redirect_to new_user_session_path + else + track_weak_password_error(@user, self.class.name, 'update') + @user.reset + render 'edit' + end + end + + def reset + current_user.send_reset_password_instructions + redirect_to edit_user_settings_password_path, notice: _('We sent you an email with reset password instructions') + end + + private + + def set_user + @user = current_user + end + + def determine_layout + if [:new, :create].include?(action_name.to_sym) + 'application' + else + 'profile' + end + end + + def authorize_change_password! + render_404 unless @user.allow_password_authentication? + end + + def handle_invalid_current_password_attempt! + Gitlab::AppLogger.info(message: 'Invalid current password when attempting to update user password', + username: @user.username, ip: request.remote_ip) + + @user.increment_failed_attempts! + end + + def user_params + params.require(:user).permit(:password, :new_password, :password_confirmation) + end + + def password_attributes + { + password: user_params[:new_password], + password_confirmation: user_params[:password_confirmation], + password_automatically_set: false + } + end + end +end + +UserSettings::PasswordsController.prepend_mod diff --git a/app/controllers/user_settings/personal_access_tokens_controller.rb b/app/controllers/user_settings/personal_access_tokens_controller.rb new file mode 100644 index 00000000000..a8e9a328c26 --- /dev/null +++ b/app/controllers/user_settings/personal_access_tokens_controller.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module UserSettings + class PersonalAccessTokensController < ApplicationController + include RenderAccessTokens + + feature_category :system_access + + before_action :check_personal_access_tokens_enabled + + def index + set_index_vars + scopes = params[:scopes].split(',').map(&:squish).select(&:present?).map(&:to_sym) unless params[:scopes].nil? + @personal_access_token = finder.build( + name: params[:name], + scopes: scopes + ) + + respond_to do |format| + format.html + format.json do + render json: @active_access_tokens + end + end + end + + def create + result = ::PersonalAccessTokens::CreateService.new( + current_user: current_user, + target_user: current_user, + params: personal_access_token_params, + concatenate_errors: false + ).execute + + @personal_access_token = result.payload[:personal_access_token] + + if result.success? + render json: { new_token: @personal_access_token.token, + active_access_tokens: active_access_tokens }, status: :ok + else + render json: { errors: result.errors }, status: :unprocessable_entity + end + end + + def revoke + @personal_access_token = finder.find(params[:id]) + service = PersonalAccessTokens::RevokeService.new(current_user, token: @personal_access_token).execute + service.success? ? flash[:notice] = service.message : flash[:alert] = service.message + + redirect_to user_settings_personal_access_tokens_path + end + + private + + def finder(options = {}) + PersonalAccessTokensFinder.new({ user: current_user, impersonation: false }.merge(options)) + end + + def personal_access_token_params + params.require(:personal_access_token).permit(:name, :expires_at, scopes: []) + end + + def set_index_vars + @scopes = Gitlab::Auth.available_scopes_for(current_user) + @active_access_tokens = active_access_tokens + end + + def represent(tokens) + ::PersonalAccessTokenSerializer.new.represent(tokens) + end + + def check_personal_access_tokens_enabled + render_404 if Gitlab::CurrentSettings.personal_access_tokens_disabled? + end + end +end diff --git a/app/controllers/user_settings/user_settings_controller.rb b/app/controllers/user_settings/user_settings_controller.rb new file mode 100644 index 00000000000..3535a30095a --- /dev/null +++ b/app/controllers/user_settings/user_settings_controller.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module UserSettings + class UserSettingsController < ApplicationController + feature_category :system_access + + def authentication_log + @events = AuthenticationEvent.for_user(current_user) + .order_by_created_at_desc + .page(params[:page]) + + Gitlab::Tracking.event(self.class.name, 'search_audit_event', user: current_user) + end + end +end diff --git a/app/controllers/web_ide/remote_ide_controller.rb b/app/controllers/web_ide/remote_ide_controller.rb index 90652a1b6e2..8392e7a190c 100644 --- a/app/controllers/web_ide/remote_ide_controller.rb +++ b/app/controllers/web_ide/remote_ide_controller.rb @@ -17,7 +17,7 @@ module WebIde def index return render_404 unless Feature.enabled?(:vscode_web_ide, current_user) - render layout: 'fullscreen', locals: { minimal: true, data: root_element_data } + render layout: 'fullscreen', locals: { data: root_element_data } end private diff --git a/app/controllers/well_known_controller.rb b/app/controllers/well_known_controller.rb new file mode 100644 index 00000000000..9ca38a81b11 --- /dev/null +++ b/app/controllers/well_known_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# This controller implements /.well-known paths that have no better home. +# +# Other controllers also implement /.well-known/* paths. They can be +# discovered by running `rails routes | grep "well-known"`. +class WellKnownController < ApplicationController # rubocop:disable Gitlab/NamespacedClass -- No relevant product domain exists + skip_before_action :authenticate_user!, :check_two_factor_requirement + feature_category :compliance_management, [:security_txt] + + def security_txt + content = Gitlab::CurrentSettings.current_application_settings.security_txt_content + if content.present? + render plain: content + else + route_not_found + end + end +end |