Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-19 04:45:44 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-19 04:45:44 +0300
commit85dc423f7090da0a52c73eb66faf22ddb20efff9 (patch)
tree9160f299afd8c80c038f08e1545be119f5e3f1e1 /app/controllers
parent15c2c8c66dbe422588e5411eee7e68f1fa440bb8 (diff)
Add latest changes from gitlab-org/gitlab@13-4-stable-ee
Diffstat (limited to 'app/controllers')
-rw-r--r--app/controllers/admin/application_settings_controller.rb3
-rw-r--r--app/controllers/admin/cohorts_controller.rb (renamed from app/controllers/instance_statistics/cohorts_controller.rb)8
-rw-r--r--app/controllers/admin/concerns/authenticates_2fa_for_admin_mode.rb50
-rw-r--r--app/controllers/admin/dev_ops_report_controller.rb13
-rw-r--r--app/controllers/admin/groups_controller.rb6
-rw-r--r--app/controllers/admin/instance_statistics_controller.rb16
-rw-r--r--app/controllers/admin/integrations_controller.rb4
-rw-r--r--app/controllers/admin/plan_limits_controller.rb39
-rw-r--r--app/controllers/admin/runners_controller.rb1
-rw-r--r--app/controllers/admin/services_controller.rb2
-rw-r--r--app/controllers/admin/users_controller.rb17
-rw-r--r--app/controllers/application_controller.rb4
-rw-r--r--app/controllers/clusters/clusters_controller.rb1
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb79
-rw-r--r--app/controllers/concerns/integrations_actions.rb11
-rw-r--r--app/controllers/concerns/issuable_actions.rb2
-rw-r--r--app/controllers/concerns/issuable_links.rb41
-rw-r--r--app/controllers/concerns/notes_actions.rb2
-rw-r--r--app/controllers/concerns/redis_tracking.rb47
-rw-r--r--app/controllers/concerns/renders_notes.rb10
-rw-r--r--app/controllers/concerns/send_file_upload.rb38
-rw-r--r--app/controllers/concerns/snippets_actions.rb8
-rw-r--r--app/controllers/concerns/wiki_actions.rb30
-rw-r--r--app/controllers/dashboard/projects_controller.rb7
-rw-r--r--app/controllers/graphql_controller.rb10
-rw-r--r--app/controllers/groups/boards_controller.rb1
-rw-r--r--app/controllers/groups/settings/integrations_controller.rb12
-rw-r--r--app/controllers/instance_statistics/application_controller.rb10
-rw-r--r--app/controllers/instance_statistics/dev_ops_score_controller.rb13
-rw-r--r--app/controllers/invites_controller.rb23
-rw-r--r--app/controllers/jira_connect/app_descriptor_controller.rb64
-rw-r--r--app/controllers/jira_connect/application_controller.rb57
-rw-r--r--app/controllers/jira_connect/events_controller.rb30
-rw-r--r--app/controllers/jira_connect/subscriptions_controller.rb56
-rw-r--r--app/controllers/oauth/jira/authorizations_controller.rb49
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb4
-rw-r--r--app/controllers/passwords_controller.rb6
-rw-r--r--app/controllers/profiles/accounts_controller.rb10
-rw-r--r--app/controllers/profiles/keys_controller.rb21
-rw-r--r--app/controllers/profiles/notifications_controller.rb15
-rw-r--r--app/controllers/profiles/preferences_controller.rb1
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb98
-rw-r--r--app/controllers/profiles/webauthn_registrations_controller.rb10
-rw-r--r--app/controllers/profiles_controller.rb4
-rw-r--r--app/controllers/projects/application_controller.rb4
-rw-r--r--app/controllers/projects/badges_controller.rb1
-rw-r--r--app/controllers/projects/blob_controller.rb12
-rw-r--r--app/controllers/projects/boards_controller.rb1
-rw-r--r--app/controllers/projects/ci/lints_controller.rb42
-rw-r--r--app/controllers/projects/forks_controller.rb13
-rw-r--r--app/controllers/projects/imports_controller.rb6
-rw-r--r--app/controllers/projects/issue_links_controller.rb48
-rw-r--r--app/controllers/projects/issues_controller.rb43
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests/content_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb7
-rw-r--r--app/controllers/projects/merge_requests_controller.rb28
-rw-r--r--app/controllers/projects/metrics_dashboard_controller.rb31
-rw-r--r--app/controllers/projects/pages_controller.rb2
-rw-r--r--app/controllers/projects/pipelines_controller.rb27
-rw-r--r--app/controllers/projects/product_analytics_controller.rb4
-rw-r--r--app/controllers/projects/releases_controller.rb3
-rw-r--r--app/controllers/projects/services_controller.rb14
-rw-r--r--app/controllers/projects/static_site_editor_controller.rb18
-rw-r--r--app/controllers/projects/todos_controller.rb3
-rw-r--r--app/controllers/projects/web_ide_schemas_controller.rb25
-rw-r--r--app/controllers/projects/web_ide_terminals_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb6
-rw-r--r--app/controllers/registrations/experience_levels_controller.rb1
-rw-r--r--app/controllers/registrations_controller.rb31
-rw-r--r--app/controllers/repositories/lfs_api_controller.rb2
-rw-r--r--app/controllers/search_controller.rb18
-rw-r--r--app/controllers/sessions_controller.rb15
-rw-r--r--app/controllers/users_controller.rb6
74 files changed, 1081 insertions, 272 deletions
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 3a5b8b2862e..73f71f7ad55 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -32,7 +32,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
def integrations
- @integrations = Service.find_or_initialize_instances.sort_by(&:title)
+ @integrations = Service.find_or_initialize_all(Service.for_instance).sort_by(&:title)
end
def update
@@ -170,6 +170,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
def set_application_setting
@application_setting = ApplicationSetting.current_without_cache
+ @plans = Plan.all
end
def whitelist_query_limiting
diff --git a/app/controllers/instance_statistics/cohorts_controller.rb b/app/controllers/admin/cohorts_controller.rb
index 0de62a56b01..e3df98b7917 100644
--- a/app/controllers/instance_statistics/cohorts_controller.rb
+++ b/app/controllers/admin/cohorts_controller.rb
@@ -1,10 +1,8 @@
# frozen_string_literal: true
-class InstanceStatistics::CohortsController < InstanceStatistics::ApplicationController
+class Admin::CohortsController < Admin::ApplicationController
include Analytics::UniqueVisitsHelper
- before_action :authenticate_usage_ping_enabled_or_admin!
-
track_unique_visits :index, target_id: 'i_analytics_cohorts'
def index
@@ -16,8 +14,4 @@ class InstanceStatistics::CohortsController < InstanceStatistics::ApplicationCon
@cohorts = CohortsSerializer.new.represent(cohorts_results)
end
end
-
- def authenticate_usage_ping_enabled_or_admin!
- render_404 unless Gitlab::CurrentSettings.usage_ping_enabled || current_user.admin?
- end
end
diff --git a/app/controllers/admin/concerns/authenticates_2fa_for_admin_mode.rb b/app/controllers/admin/concerns/authenticates_2fa_for_admin_mode.rb
index 6014ed0dd13..03783cd75a3 100644
--- a/app/controllers/admin/concerns/authenticates_2fa_for_admin_mode.rb
+++ b/app/controllers/admin/concerns/authenticates_2fa_for_admin_mode.rb
@@ -11,7 +11,13 @@ module Authenticates2FAForAdminMode
return handle_locked_user(user) unless user.can?(:log_in)
session[:otp_user_id] = user.id
- setup_u2f_authentication(user)
+ push_frontend_feature_flag(:webauthn)
+
+ if user.two_factor_webauthn_enabled?
+ setup_webauthn_authentication(user)
+ else
+ setup_u2f_authentication(user)
+ end
render 'admin/sessions/two_factor', layout: 'application'
end
@@ -24,7 +30,11 @@ module Authenticates2FAForAdminMode
if user_params[:otp_attempt].present? && session[:otp_user_id]
admin_mode_authenticate_with_two_factor_via_otp(user)
elsif user_params[:device_response].present? && session[:otp_user_id]
- admin_mode_authenticate_with_two_factor_via_u2f(user)
+ if user.two_factor_webauthn_enabled?
+ admin_mode_authenticate_with_two_factor_via_webauthn(user)
+ else
+ admin_mode_authenticate_with_two_factor_via_u2f(user)
+ end
elsif user && user.valid_password?(user_params[:password])
admin_mode_prompt_for_two_factor(user)
else
@@ -52,18 +62,17 @@ module Authenticates2FAForAdminMode
def admin_mode_authenticate_with_two_factor_via_u2f(user)
if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge])
- # Remove any lingering user data from login
- session.delete(:otp_user_id)
- session.delete(:challenge)
-
- # The admin user has successfully passed 2fa, enable admin mode ignoring password
- enable_admin_mode
+ admin_handle_two_factor_success
else
- user.increment_failed_attempts!
- Gitlab::AppLogger.info("Failed Admin Mode Login: user=#{user.username} ip=#{request.remote_ip} method=U2F")
- flash.now[:alert] = _('Authentication via U2F device failed.')
+ admin_handle_two_factor_failure(user, 'U2F')
+ end
+ end
- admin_mode_prompt_for_two_factor(user)
+ def admin_mode_authenticate_with_two_factor_via_webauthn(user)
+ if Webauthn::AuthenticateService.new(user, user_params[:device_response], session[:challenge]).execute
+ admin_handle_two_factor_success
+ else
+ admin_handle_two_factor_failure(user, 'WebAuthn')
end
end
@@ -81,4 +90,21 @@ module Authenticates2FAForAdminMode
flash.now[:alert] = _('Invalid login or password')
render :new
end
+
+ def admin_handle_two_factor_success
+ # Remove any lingering user data from login
+ session.delete(:otp_user_id)
+ session.delete(:challenge)
+
+ # The admin user has successfully passed 2fa, enable admin mode ignoring password
+ enable_admin_mode
+ end
+
+ def admin_handle_two_factor_failure(user, method)
+ user.increment_failed_attempts!
+ Gitlab::AppLogger.info("Failed Admin Mode Login: user=#{user.username} ip=#{request.remote_ip} method=#{method}")
+ flash.now[:alert] = _('Authentication via %{method} device failed.') % { method: method }
+
+ admin_mode_prompt_for_two_factor(user)
+ end
end
diff --git a/app/controllers/admin/dev_ops_report_controller.rb b/app/controllers/admin/dev_ops_report_controller.rb
new file mode 100644
index 00000000000..bed0d51c331
--- /dev/null
+++ b/app/controllers/admin/dev_ops_report_controller.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class Admin::DevOpsReportController < Admin::ApplicationController
+ include Analytics::UniqueVisitsHelper
+
+ track_unique_visits :show, target_id: 'i_analytics_dev_ops_score'
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def show
+ @metric = DevOpsReport::Metric.order(:created_at).last&.present
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+end
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 0245c00aacb..6414792dd43 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -19,7 +19,7 @@ class Admin::GroupsController < Admin::ApplicationController
# the Group with statistics).
@group = Group.with_statistics.find(group&.id)
@members = present_members(
- @group.members.order("access_level DESC").page(params[:members_page]))
+ group_members.order("access_level DESC").page(params[:members_page]))
@requesters = present_members(
AccessRequestsFinder.new(@group).execute(current_user))
@projects = @group.projects.with_statistics.page(params[:projects_page])
@@ -82,6 +82,10 @@ class Admin::GroupsController < Admin::ApplicationController
@group ||= Group.find_by_full_path(params[:id])
end
+ def group_members
+ @group.members
+ end
+
def group_params
params.require(:group).permit(allowed_group_params)
end
diff --git a/app/controllers/admin/instance_statistics_controller.rb b/app/controllers/admin/instance_statistics_controller.rb
new file mode 100644
index 00000000000..3aee26b97a2
--- /dev/null
+++ b/app/controllers/admin/instance_statistics_controller.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class Admin::InstanceStatisticsController < Admin::ApplicationController
+ include Analytics::UniqueVisitsHelper
+
+ before_action :check_feature_flag
+
+ track_unique_visits :index, target_id: 'i_analytics_instance_statistics'
+
+ def index
+ end
+
+ def check_feature_flag
+ render_404 unless Feature.enabled?(:instance_statistics)
+ end
+end
diff --git a/app/controllers/admin/integrations_controller.rb b/app/controllers/admin/integrations_controller.rb
index b2d5a2d130c..1e2a99f7078 100644
--- a/app/controllers/admin/integrations_controller.rb
+++ b/app/controllers/admin/integrations_controller.rb
@@ -6,9 +6,7 @@ class Admin::IntegrationsController < Admin::ApplicationController
private
def find_or_initialize_integration(name)
- if name.in?(Service.available_services_names)
- "#{name}_service".camelize.constantize.find_or_initialize_by(instance: true) # rubocop:disable CodeReuse/ActiveRecord
- end
+ Service.find_or_initialize_integration(name, instance: true)
end
def integrations_enabled?
diff --git a/app/controllers/admin/plan_limits_controller.rb b/app/controllers/admin/plan_limits_controller.rb
new file mode 100644
index 00000000000..2620db8aec5
--- /dev/null
+++ b/app/controllers/admin/plan_limits_controller.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+class Admin::PlanLimitsController < Admin::ApplicationController
+ include InternalRedirect
+
+ before_action :set_plan_limits
+
+ def create
+ redirect_path = referer_path(request) || general_admin_application_settings_path
+
+ respond_to do |format|
+ if @plan_limits.update(plan_limits_params)
+ format.json { head :ok }
+ format.html { redirect_to redirect_path, notice: _('Application limits saved successfully') }
+ else
+ format.json { head :bad_request }
+ format.html { render_update_error }
+ end
+ end
+ end
+
+ private
+
+ def set_plan_limits
+ @plan_limits = Plan.find(plan_limits_params[:plan_id]).actual_limits
+ end
+
+ def plan_limits_params
+ params.require(:plan_limits).permit(%i[
+ plan_id
+ conan_max_file_size
+ maven_max_file_size
+ npm_max_file_size
+ nuget_max_file_size
+ pypi_max_file_size
+ generic_packages_max_file_size
+ ])
+ end
+end
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index 2449fa3128c..7a377a33d41 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -17,7 +17,6 @@ class Admin::RunnersController < Admin::ApplicationController
def update
if Ci::UpdateRunnerService.new(@runner).update(runner_params)
respond_to do |format|
- format.js
format.html { redirect_to admin_runner_path(@runner) }
end
else
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index 1bc82e98ab8..1f4250639c4 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -8,7 +8,7 @@ class Admin::ServicesController < Admin::ApplicationController
def index
@services = Service.find_or_create_templates.sort_by(&:title)
- @existing_instance_types = Service.instances.pluck(:type) # rubocop: disable CodeReuse/ActiveRecord
+ @existing_instance_types = Service.for_instance.pluck(:type) # rubocop: disable CodeReuse/ActiveRecord
end
def edit
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index fc0acd8f99a..050f83edacb 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -111,10 +111,14 @@ class Admin::UsersController < Admin::ApplicationController
end
def disable_two_factor
- update_user { |user| user.disable_two_factor! }
+ result = TwoFactor::DestroyService.new(current_user, user: user).execute
- redirect_to admin_user_path(user),
- notice: _('Two-factor Authentication has been disabled for this user')
+ if result[:status] == :success
+ redirect_to admin_user_path(user),
+ notice: _('Two-factor authentication has been disabled for this user')
+ else
+ redirect_to admin_user_path(user), alert: result[:message]
+ end
end
def create
@@ -145,7 +149,7 @@ class Admin::UsersController < Admin::ApplicationController
password_confirmation: params[:user][:password_confirmation]
}
- password_params[:password_expires_at] = Time.current unless changing_own_password?
+ password_params[:password_expires_at] = Time.current if admin_making_changes_for_another_user?
user_params_with_pass.merge!(password_params)
end
@@ -153,6 +157,7 @@ class Admin::UsersController < Admin::ApplicationController
respond_to do |format|
result = Users::UpdateService.new(current_user, user_params_with_pass.merge(user: user)).execute do |user|
user.skip_reconfirmation!
+ user.send_only_admin_changed_your_password_notification! if admin_making_changes_for_another_user?
end
if result[:status] == :success
@@ -193,8 +198,8 @@ class Admin::UsersController < Admin::ApplicationController
protected
- def changing_own_password?
- user == current_user
+ def admin_making_changes_for_another_user?
+ user != current_user
end
def user
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 2595b646964..5f05337e59e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -551,13 +551,9 @@ class ApplicationController < ActionController::Base
"#{self.class.name}##{action_name}"
end
- # A user requires a role and have the setup_for_company attribute set when they are part of the experimental signup
- # flow (executed by the Growth team). Users are redirected to the welcome page when their role is required and the
- # experiment is enabled for the current user.
def required_signup_info
return unless current_user
return unless current_user.role_required?
- return unless experiment_enabled?(:signup_flow)
store_location_for :user, request.fullpath
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index cae9d098799..7006c23321c 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -237,6 +237,7 @@ class Clusters::ClustersController < Clusters::BaseController
:environment_scope,
:managed,
provider_aws_attributes: [
+ :kubernetes_version,
:key_name,
:role_arn,
:region,
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index b93c98a4790..9ff97f398f5 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -22,9 +22,15 @@ module AuthenticatesWithTwoFactor
return handle_locked_user(user) unless user.can?(:log_in)
session[:otp_user_id] = user.id
- session[:user_updated_at] = user.updated_at
+ session[:user_password_hash] = Digest::SHA256.hexdigest(user.encrypted_password)
+ push_frontend_feature_flag(:webauthn)
+
+ if user.two_factor_webauthn_enabled?
+ setup_webauthn_authentication(user)
+ else
+ setup_u2f_authentication(user)
+ end
- setup_u2f_authentication(user)
render 'devise/sessions/two_factor'
end
@@ -41,12 +47,16 @@ module AuthenticatesWithTwoFactor
def authenticate_with_two_factor
user = self.resource = find_user
return handle_locked_user(user) unless user.can?(:log_in)
- return handle_changed_user(user) if user_changed?(user)
+ return handle_changed_user(user) if user_password_changed?(user)
if user_params[:otp_attempt].present? && session[:otp_user_id]
authenticate_with_two_factor_via_otp(user)
elsif user_params[:device_response].present? && session[:otp_user_id]
- authenticate_with_two_factor_via_u2f(user)
+ if user.two_factor_webauthn_enabled?
+ authenticate_with_two_factor_via_webauthn(user)
+ else
+ authenticate_with_two_factor_via_u2f(user)
+ end
elsif user && user.valid_password?(user_params[:password])
prompt_for_two_factor(user)
end
@@ -66,7 +76,7 @@ module AuthenticatesWithTwoFactor
def clear_two_factor_attempt!
session.delete(:otp_user_id)
- session.delete(:user_updated_at)
+ session.delete(:user_password_hash)
session.delete(:challenge)
end
@@ -89,16 +99,17 @@ module AuthenticatesWithTwoFactor
# Authenticate using the response from a U2F (universal 2nd factor) device
def authenticate_with_two_factor_via_u2f(user)
if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge])
- # Remove any lingering user data from login
- clear_two_factor_attempt!
+ handle_two_factor_success(user)
+ else
+ handle_two_factor_failure(user, 'U2F')
+ end
+ end
- remember_me(user) if user_params[:remember_me] == '1'
- sign_in(user, message: :two_factor_authenticated, event: :authentication)
+ def authenticate_with_two_factor_via_webauthn(user)
+ if Webauthn::AuthenticateService.new(user, user_params[:device_response], session[:challenge]).execute
+ handle_two_factor_success(user)
else
- user.increment_failed_attempts!
- Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F")
- flash.now[:alert] = _('Authentication via U2F device failed.')
- prompt_for_two_factor(user)
+ handle_two_factor_failure(user, 'WebAuthn')
end
end
@@ -116,8 +127,38 @@ module AuthenticatesWithTwoFactor
sign_requests: sign_requests })
end
end
+
+ def setup_webauthn_authentication(user)
+ if user.webauthn_registrations.present?
+
+ webauthn_registration_ids = user.webauthn_registrations.pluck(:credential_xid)
+
+ get_options = WebAuthn::Credential.options_for_get(allow: webauthn_registration_ids,
+ user_verification: 'discouraged',
+ extensions: { appid: WebAuthn.configuration.origin })
+
+ session[:credentialRequestOptions] = get_options
+ session[:challenge] = get_options.challenge
+ gon.push(webauthn: { options: get_options.to_json })
+ end
+ end
# rubocop: enable CodeReuse/ActiveRecord
+ def handle_two_factor_success(user)
+ # Remove any lingering user data from login
+ clear_two_factor_attempt!
+
+ remember_me(user) if user_params[:remember_me] == '1'
+ sign_in(user, message: :two_factor_authenticated, event: :authentication)
+ end
+
+ def handle_two_factor_failure(user, method)
+ user.increment_failed_attempts!
+ Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=#{method}")
+ flash.now[:alert] = _('Authentication via %{method} device failed.') % { method: method }
+ prompt_for_two_factor(user)
+ end
+
def handle_changed_user(user)
clear_two_factor_attempt!
@@ -126,13 +167,9 @@ module AuthenticatesWithTwoFactor
# If user has been updated since we validated the password,
# the password might have changed.
- def user_changed?(user)
- return false unless session[:user_updated_at]
-
- # See: https://gitlab.com/gitlab-org/gitlab/-/issues/244638
- # Rounding errors happen when the user is updated, as the Rails ActiveRecord
- # object has higher precision than what is stored in the database, therefore
- # using .to_i to force truncation to the timestamp
- user.updated_at.to_i != session[:user_updated_at].to_i
+ def user_password_changed?(user)
+ return false unless session[:user_password_hash]
+
+ Digest::SHA256.hexdigest(user.encrypted_password) != session[:user_password_hash]
end
end
diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb
index 9a8e5d14123..6060dc729af 100644
--- a/app/controllers/concerns/integrations_actions.rb
+++ b/app/controllers/concerns/integrations_actions.rb
@@ -16,12 +16,11 @@ module IntegrationsActions
def update
saved = integration.update(service_params[:service])
- overwrite = Gitlab::Utils.to_boolean(params[:overwrite])
respond_to do |format|
format.html do
if saved
- PropagateIntegrationWorker.perform_async(integration.id, overwrite)
+ PropagateIntegrationWorker.perform_async(integration.id, false)
redirect_to scoped_edit_integration_path(integration), notice: success_message
else
render 'shared/integrations/edit'
@@ -57,9 +56,11 @@ module IntegrationsActions
end
def success_message
- message = integration.active? ? _('activated') : _('settings saved, but not activated')
-
- _('%{service_title} %{message}.') % { service_title: integration.title, message: message }
+ if integration.active?
+ s_('Integrations|%{integration} settings saved and active.') % { integration: integration.title }
+ else
+ s_('Integrations|%{integration} settings saved, but not active.') % { integration: integration.title }
+ end
end
def serialize_as_json
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index c4dbce00593..a1a2740cde2 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -9,7 +9,7 @@ module IssuableActions
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, default_enabled: true)
+ 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)
diff --git a/app/controllers/concerns/issuable_links.rb b/app/controllers/concerns/issuable_links.rb
new file mode 100644
index 00000000000..2bdb190f1d5
--- /dev/null
+++ b/app/controllers/concerns/issuable_links.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module IssuableLinks
+ def index
+ render json: issuables
+ end
+
+ def create
+ result = create_service.execute
+
+ render json: { message: result[:message], issuables: issuables }, status: result[:http_status]
+ end
+
+ def destroy
+ result = destroy_service.execute
+
+ render json: { issuables: issuables }, status: result[:http_status]
+ end
+
+ private
+
+ def issuables
+ list_service.execute
+ end
+
+ def list_service
+ raise NotImplementedError
+ end
+
+ def create_params
+ params.permit(issuable_references: [])
+ end
+
+ def create_service
+ raise NotImplementedError
+ end
+
+ def destroy_service
+ raise NotImplementedError
+ end
+end
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index f4fc7decb60..7a5b470f366 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -100,7 +100,7 @@ module NotesActions
# the finder. Here, we select between returning all notes since then, or a
# page's worth of notes.
def gather_notes
- if Feature.enabled?(:paginated_notes, project)
+ if Feature.enabled?(:paginated_notes, noteable.try(:resource_parent))
gather_some_notes
else
gather_all_notes
diff --git a/app/controllers/concerns/redis_tracking.rb b/app/controllers/concerns/redis_tracking.rb
new file mode 100644
index 00000000000..fa5eef981d1
--- /dev/null
+++ b/app/controllers/concerns/redis_tracking.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+# Example:
+#
+# # In controller include module
+# # Track event for index action
+#
+# include RedisTracking
+#
+# track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score', feature: :my_feature
+#
+# if the feature flag is enabled by default you should use
+# track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score', feature: :my_feature, feature_default_enabled: true
+module RedisTracking
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def track_redis_hll_event(*controller_actions, name:, feature:, feature_default_enabled: false)
+ after_action only: controller_actions, if: -> { request.format.html? && request.headers['DNT'] != '1' } do
+ track_unique_redis_hll_event(name, feature, feature_default_enabled)
+ end
+ end
+ end
+
+ private
+
+ def track_unique_redis_hll_event(event_name, feature, feature_default_enabled)
+ return unless metric_feature_enabled?(feature, feature_default_enabled)
+ return unless Gitlab::CurrentSettings.usage_ping_enabled?
+ return unless visitor_id
+
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event(visitor_id, event_name)
+ end
+
+ def metric_feature_enabled?(feature, default_enabled)
+ Feature.enabled?(feature, default_enabled: default_enabled)
+ end
+
+ def visitor_id
+ return cookies[:visitor_id] if cookies[:visitor_id].present?
+ return unless current_user
+
+ uuid = SecureRandom.uuid
+ cookies[:visitor_id] = { value: uuid, expires: 24.months }
+ uuid
+ end
+end
diff --git a/app/controllers/concerns/renders_notes.rb b/app/controllers/concerns/renders_notes.rb
index 18015b1de88..f8e3717acee 100644
--- a/app/controllers/concerns/renders_notes.rb
+++ b/app/controllers/concerns/renders_notes.rb
@@ -5,7 +5,6 @@ module RendersNotes
def prepare_notes_for_rendering(notes, noteable = nil)
preload_noteable_for_regular_notes(notes)
preload_max_access_for_authors(notes, @project)
- preload_first_time_contribution_for_authors(noteable, notes)
preload_author_status(notes)
Notes::RenderService.new(current_user).execute(notes)
@@ -19,7 +18,8 @@ module RendersNotes
return unless project
user_ids = notes.map(&:author_id)
- project.team.max_member_access_for_user_ids(user_ids)
+ access = project.team.max_member_access_for_user_ids(user_ids).select { |k, v| v == Gitlab::Access::NO_ACCESS }.keys
+ project.team.contribution_check_for_user_ids(access)
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -28,12 +28,6 @@ module RendersNotes
end
# rubocop: enable CodeReuse/ActiveRecord
- def preload_first_time_contribution_for_authors(noteable, notes)
- return unless noteable.is_a?(Issuable) && noteable.first_contribution?
-
- notes.each {|n| n.specialize_for_first_contribution!(noteable)}
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def preload_author_status(notes)
ActiveRecord::Associations::Preloader.new.preload(notes, { author: :status })
diff --git a/app/controllers/concerns/send_file_upload.rb b/app/controllers/concerns/send_file_upload.rb
index 7cb19fc7e58..2f06cd84ee5 100644
--- a/app/controllers/concerns/send_file_upload.rb
+++ b/app/controllers/concerns/send_file_upload.rb
@@ -2,6 +2,8 @@
module SendFileUpload
def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, proxy: false, disposition: 'attachment')
+ content_type = content_type_for(attachment)
+
if attachment
response_disposition = ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: attachment)
@@ -9,7 +11,7 @@ module SendFileUpload
# Google Cloud Storage, so the metadata needs to be cleared on GCS for
# this to work. However, this override works with AWS.
redirect_params[:query] = { "response-content-disposition" => response_disposition,
- "response-content-type" => guess_content_type(attachment) }
+ "response-content-type" => content_type }
# By default, Rails will send uploads with an extension of .js with a
# content-type of text/javascript, which will trigger Rails'
# cross-origin JavaScript protection.
@@ -20,7 +22,7 @@ module SendFileUpload
if image_scaling_request?(file_upload)
location = file_upload.file_storage? ? file_upload.path : file_upload.url
- headers.store(*Gitlab::Workhorse.send_scaled_image(location, params[:width].to_i))
+ headers.store(*Gitlab::Workhorse.send_scaled_image(location, params[:width].to_i, content_type))
head :ok
elsif file_upload.file_storage?
send_file file_upload.path, send_params
@@ -32,6 +34,12 @@ module SendFileUpload
end
end
+ def content_type_for(attachment)
+ return '' unless attachment
+
+ guess_content_type(attachment)
+ end
+
def guess_content_type(filename)
types = MIME::Types.type_for(filename)
@@ -45,15 +53,33 @@ module SendFileUpload
private
def image_scaling_request?(file_upload)
- avatar_image_upload?(file_upload) && valid_image_scaling_width? && current_user &&
- Feature.enabled?(:dynamic_image_resizing, current_user)
+ avatar_safe_for_scaling?(file_upload) &&
+ scaling_allowed_by_feature_flags?(file_upload) &&
+ valid_image_scaling_width?
end
- def avatar_image_upload?(file_upload)
- file_upload.try(:image?) && file_upload.try(:mounted_as)&.to_sym == :avatar
+ def avatar_safe_for_scaling?(file_upload)
+ file_upload.try(:image_safe_for_scaling?) && mounted_as_avatar?(file_upload)
+ end
+
+ def mounted_as_avatar?(file_upload)
+ file_upload.try(:mounted_as)&.to_sym == :avatar
end
def valid_image_scaling_width?
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)
+ end
end
diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
index 5552fd663f7..4548595d968 100644
--- a/app/controllers/concerns/snippets_actions.rb
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -14,8 +14,6 @@ module SnippetsActions
skip_before_action :verify_authenticity_token,
if: -> { action_name == 'show' && js_request? }
- before_action :redirect_if_binary, only: [:edit, :update]
-
respond_to :html
end
@@ -134,10 +132,4 @@ module SnippetsActions
recaptcha_check_with_fallback(errors.empty?) { render action }
end
-
- def redirect_if_binary
- return if Feature.enabled?(:snippets_binary_blob)
-
- redirect_to gitlab_snippet_path(snippet) if blob&.binary?
- end
end
diff --git a/app/controllers/concerns/wiki_actions.rb b/app/controllers/concerns/wiki_actions.rb
index 5b953fe37d6..5a5b634da40 100644
--- a/app/controllers/concerns/wiki_actions.rb
+++ b/app/controllers/concerns/wiki_actions.rb
@@ -93,9 +93,10 @@ module WikiActions
def update
return render('shared/wikis/empty') unless can?(current_user, :create_wiki, container)
- @page = WikiPages::UpdateService.new(container: container, current_user: current_user, params: wiki_params).execute(page)
+ response = WikiPages::UpdateService.new(container: container, current_user: current_user, params: wiki_params).execute(page)
+ @page = response.payload[:page]
- if page.valid?
+ if response.success?
redirect_to(
wiki_page_path(wiki, page),
notice: _('Wiki was successfully updated.')
@@ -103,7 +104,7 @@ module WikiActions
else
render 'shared/wikis/edit'
end
- rescue WikiPage::PageChangedError, WikiPage::PageRenameError, Gitlab::Git::Wiki::OperationError => e
+ rescue WikiPage::PageChangedError, WikiPage::PageRenameError => e
@error = e
render 'shared/wikis/edit'
end
@@ -120,13 +121,8 @@ module WikiActions
notice: _('Wiki was successfully updated.')
)
else
- flash[:alert] = response.message
render 'shared/wikis/edit'
end
- rescue Gitlab::Git::Wiki::OperationError => e
- @page = build_page(wiki_params)
- @error = e
- render 'shared/wikis/edit'
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
@@ -162,14 +158,18 @@ module WikiActions
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def destroy
- WikiPages::DestroyService.new(container: container, current_user: current_user).execute(page)
+ return render_404 unless page
- redirect_to wiki_path(wiki),
- status: :found,
- notice: _("Page was successfully deleted")
- rescue Gitlab::Git::Wiki::OperationError => e
- @error = e
- render 'shared/wikis/edit'
+ response = WikiPages::DestroyService.new(container: container, current_user: current_user).execute(page)
+
+ if response.success?
+ redirect_to wiki_path(wiki),
+ status: :found,
+ notice: _("Page was successfully deleted")
+ else
+ @error = response
+ render 'shared/wikis/edit'
+ end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 91704f030cd..2bd6fd85381 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -63,10 +63,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
def load_projects(finder_params)
- @total_user_projects_count = ProjectsFinder.new(params: { non_public: true }, current_user: current_user).execute
- @total_starred_projects_count = ProjectsFinder.new(params: { starred: true }, current_user: current_user).execute
+ @total_user_projects_count = ProjectsFinder.new(params: { non_public: true, without_deleted: true }, current_user: current_user).execute
+ @total_starred_projects_count = ProjectsFinder.new(params: { starred: true, without_deleted: true }, current_user: current_user).execute
finder_params[:use_cte] = true if use_cte_for_finder?
+ finder_params[:without_deleted] = true
projects = ProjectsFinder.new(params: finder_params, current_user: current_user).execute
@@ -89,7 +90,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
def load_events
projects = ProjectsFinder
- .new(params: params.merge(non_public: true), current_user: current_user)
+ .new(params: params.merge(non_public: true, without_deleted: true), current_user: current_user)
.execute
@events = EventCollection
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index a1348e4d858..123102bf793 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -81,7 +81,7 @@ class GraphqlController < ApplicationController
end
def context
- @context ||= { current_user: current_user }
+ @context ||= { current_user: current_user, is_sessionless_user: !!sessionless_user? }
end
def build_variables(variable_info)
@@ -107,4 +107,12 @@ class GraphqlController < ApplicationController
render json: error, status: status
end
+
+ def append_info_to_payload(payload)
+ super
+
+ # Merging to :metadata will ensure these are logged as top level keys
+ payload[:metadata] ||= {}
+ payload[:metadata].merge!(graphql: { operation_name: params[:operationName] })
+ end
end
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index 23d4f0d24e9..ea7e83a2caf 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 :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)
end
diff --git a/app/controllers/groups/settings/integrations_controller.rb b/app/controllers/groups/settings/integrations_controller.rb
index adfbe9bfa17..e8551a7f270 100644
--- a/app/controllers/groups/settings/integrations_controller.rb
+++ b/app/controllers/groups/settings/integrations_controller.rb
@@ -8,15 +8,19 @@ module Groups
before_action :authorize_admin_group!
def index
- @integrations = []
+ @integrations = Service.find_or_initialize_all(Service.for_group(group)).sort_by(&:title)
+ end
+
+ def edit
+ @default_integration = Service.default_integration(integration.type, group)
+
+ super
end
private
- # TODO: Make this compatible with group-level integration
- # https://gitlab.com/groups/gitlab-org/-/epics/2543
def find_or_initialize_integration(name)
- Project.first.find_or_initialize_service(name)
+ Service.find_or_initialize_integration(name, group_id: group.id)
end
def integrations_enabled?
diff --git a/app/controllers/instance_statistics/application_controller.rb b/app/controllers/instance_statistics/application_controller.rb
deleted file mode 100644
index a273dde105c..00000000000
--- a/app/controllers/instance_statistics/application_controller.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-class InstanceStatistics::ApplicationController < ApplicationController
- before_action :authorize_read_instance_statistics!
- layout 'instance_statistics'
-
- def authorize_read_instance_statistics!
- render_404 unless can?(current_user, :read_instance_statistics)
- end
-end
diff --git a/app/controllers/instance_statistics/dev_ops_score_controller.rb b/app/controllers/instance_statistics/dev_ops_score_controller.rb
deleted file mode 100644
index b98a1bf7f99..00000000000
--- a/app/controllers/instance_statistics/dev_ops_score_controller.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-class InstanceStatistics::DevOpsScoreController < InstanceStatistics::ApplicationController
- include Analytics::UniqueVisitsHelper
-
- track_unique_visits :index, target_id: 'i_analytics_dev_ops_score'
-
- # rubocop: disable CodeReuse/ActiveRecord
- def index
- @metric = DevOpsScore::Metric.order(:created_at).last&.present
- end
- # rubocop: enable CodeReuse/ActiveRecord
-end
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 29cafbbbdb6..aa9c7d01ba3 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -12,11 +12,13 @@ class InvitesController < ApplicationController
respond_to :html
def show
+ track_experiment('opened')
accept if skip_invitation_prompt?
end
def accept
if member.accept_invite!(current_user)
+ track_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] }
else
@@ -74,8 +76,14 @@ class InvitesController < ApplicationController
notice << "or create an account" if Gitlab::CurrentSettings.allow_signup?
notice = notice.join(' ') + "."
+ # this is temporary finder instead of using member method due to render_404 possibility
+ # will be resolved via https://gitlab.com/gitlab-org/gitlab/-/issues/245325
+ initial_member = Member.find_by_invite_token(params[:id])
+ redirect_params = initial_member ? { invite_email: initial_member.invite_email } : {}
+
store_location_for :user, request.fullpath
- redirect_to new_user_session_path(invite_email: member.invite_email), notice: notice
+
+ redirect_to new_user_session_path(redirect_params), notice: notice
end
def invite_details
@@ -96,4 +104,17 @@ class InvitesController < ApplicationController
}
end
end
+
+ def track_experiment(action)
+ return unless params[:new_user_invite]
+
+ property = params[:new_user_invite] == 'experiment' ? 'experiment_group' : 'control_group'
+
+ Gitlab::Tracking.event(
+ Gitlab::Experimentation::EXPERIMENTS[:invite_email][:tracking_category],
+ action,
+ property: property,
+ label: Digest::MD5.hexdigest(member.to_global_id.to_s)
+ )
+ end
end
diff --git a/app/controllers/jira_connect/app_descriptor_controller.rb b/app/controllers/jira_connect/app_descriptor_controller.rb
new file mode 100644
index 00000000000..bf53c61601b
--- /dev/null
+++ b/app/controllers/jira_connect/app_descriptor_controller.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+# This returns an app descriptor for use with Jira in development mode
+# For the Atlassian Marketplace, a static copy of this JSON is uploaded to the marketplace
+# https://developer.atlassian.com/cloud/jira/platform/app-descriptor/
+
+class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController
+ skip_before_action :verify_atlassian_jwt!
+
+ def show
+ render json: {
+ name: Atlassian::JiraConnect.app_name,
+ description: 'Integrate commits, branches and merge requests from GitLab into Jira',
+ key: Atlassian::JiraConnect.app_key,
+ baseUrl: jira_connect_base_url(protocol: 'https'),
+ lifecycle: {
+ installed: relative_to_base_path(jira_connect_events_installed_path),
+ uninstalled: relative_to_base_path(jira_connect_events_uninstalled_path)
+ },
+ vendor: {
+ name: 'GitLab',
+ url: 'https://gitlab.com'
+ },
+ links: {
+ documentation: help_page_url('integration/jira_development_panel', anchor: 'gitlabcom-1')
+ },
+ authentication: {
+ type: 'jwt'
+ },
+ scopes: %w(READ WRITE DELETE),
+ apiVersion: 1,
+ modules: {
+ jiraDevelopmentTool: {
+ key: 'gitlab-development-tool',
+ application: {
+ value: 'GitLab'
+ },
+ name: {
+ value: 'GitLab'
+ },
+ url: 'https://gitlab.com',
+ logoUrl: view_context.image_url('gitlab_logo.png'),
+ capabilities: %w(branch commit pull_request)
+ },
+ postInstallPage: {
+ key: 'gitlab-configuration',
+ name: {
+ value: 'GitLab Configuration'
+ },
+ url: relative_to_base_path(jira_connect_subscriptions_path)
+ }
+ },
+ apiMigrations: {
+ gdpr: true
+ }
+ }
+ end
+
+ private
+
+ def relative_to_base_path(full_path)
+ full_path.sub(/^#{jira_connect_base_path}/, '')
+ end
+end
diff --git a/app/controllers/jira_connect/application_controller.rb b/app/controllers/jira_connect/application_controller.rb
new file mode 100644
index 00000000000..a84f25998a6
--- /dev/null
+++ b/app/controllers/jira_connect/application_controller.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+class JiraConnect::ApplicationController < ApplicationController
+ include Gitlab::Utils::StrongMemoize
+
+ skip_before_action :authenticate_user!
+ skip_before_action :verify_authenticity_token
+ before_action :verify_atlassian_jwt!
+
+ attr_reader :current_jira_installation
+
+ private
+
+ def verify_atlassian_jwt!
+ return render_403 unless atlassian_jwt_valid?
+
+ @current_jira_installation = installation_from_jwt
+ end
+
+ def verify_qsh_claim!
+ payload, _ = decode_auth_token!
+
+ # Make sure `qsh` claim matches the current request
+ render_403 unless payload['qsh'] == Atlassian::Jwt.create_query_string_hash(request.url, request.method, jira_connect_base_url)
+ rescue
+ render_403
+ end
+
+ def atlassian_jwt_valid?
+ return false unless installation_from_jwt
+
+ # Verify JWT signature with our stored `shared_secret`
+ decode_auth_token!
+ rescue JWT::DecodeError
+ false
+ end
+
+ def installation_from_jwt
+ return unless auth_token
+
+ strong_memoize(:installation_from_jwt) do
+ # Decode without verification to get `client_key` in `iss`
+ payload, _ = Atlassian::Jwt.decode(auth_token, nil, false)
+ JiraConnectInstallation.find_by_client_key(payload['iss'])
+ end
+ end
+
+ def decode_auth_token!
+ Atlassian::Jwt.decode(auth_token, installation_from_jwt.shared_secret)
+ end
+
+ def auth_token
+ strong_memoize(:auth_token) do
+ params[:jwt] || request.headers['Authorization']&.split(' ', 2)&.last
+ end
+ end
+end
diff --git a/app/controllers/jira_connect/events_controller.rb b/app/controllers/jira_connect/events_controller.rb
new file mode 100644
index 00000000000..8f79c82d847
--- /dev/null
+++ b/app/controllers/jira_connect/events_controller.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class JiraConnect::EventsController < JiraConnect::ApplicationController
+ skip_before_action :verify_atlassian_jwt!, only: :installed
+ before_action :verify_qsh_claim!, only: :uninstalled
+
+ def installed
+ installation = JiraConnectInstallation.new(install_params)
+
+ if installation.save
+ head :ok
+ else
+ head :unprocessable_entity
+ end
+ end
+
+ def uninstalled
+ if current_jira_installation.destroy
+ head :ok
+ else
+ head :unprocessable_entity
+ end
+ end
+
+ private
+
+ def install_params
+ params.permit(:clientKey, :sharedSecret, :baseUrl).transform_keys(&:underscore)
+ end
+end
diff --git a/app/controllers/jira_connect/subscriptions_controller.rb b/app/controllers/jira_connect/subscriptions_controller.rb
new file mode 100644
index 00000000000..3ff12f29f10
--- /dev/null
+++ b/app/controllers/jira_connect/subscriptions_controller.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+class JiraConnect::SubscriptionsController < JiraConnect::ApplicationController
+ layout 'jira_connect'
+
+ content_security_policy do |p|
+ next if p.directives.blank?
+
+ # rubocop: disable Lint/PercentStringArray
+ script_src_values = Array.wrap(p.directives['script-src']) | %w('self' https://connect-cdn.atl-paas.net https://unpkg.com/jquery@3.3.1/)
+ style_src_values = Array.wrap(p.directives['style-src']) | %w('self' 'unsafe-inline' https://unpkg.com/@atlaskit/)
+ # rubocop: enable Lint/PercentStringArray
+
+ p.frame_ancestors :self, 'https://*.atlassian.net'
+ p.script_src(*script_src_values)
+ p.style_src(*style_src_values)
+ end
+
+ before_action :allow_rendering_in_iframe, only: :index
+ before_action :verify_qsh_claim!, only: :index
+ before_action :authenticate_user!, only: :create
+
+ def index
+ @subscriptions = current_jira_installation.subscriptions.preload_namespace_route
+ end
+
+ def create
+ result = create_service.execute
+
+ if result[:status] == :success
+ render json: { success: true }
+ else
+ render json: { error: result[:message] }, status: result[:http_status]
+ end
+ end
+
+ def destroy
+ subscription = current_jira_installation.subscriptions.find(params[:id])
+
+ if subscription.destroy
+ render json: { success: true }
+ else
+ render json: { error: subscription.errors.full_messages.join(', ') }, status: :unprocessable_entity
+ end
+ end
+
+ private
+
+ def create_service
+ JiraConnectSubscriptions::CreateService.new(current_jira_installation, current_user, namespace_path: params['namespace_path'])
+ end
+
+ def allow_rendering_in_iframe
+ response.headers.delete('X-Frame-Options')
+ end
+end
diff --git a/app/controllers/oauth/jira/authorizations_controller.rb b/app/controllers/oauth/jira/authorizations_controller.rb
new file mode 100644
index 00000000000..f552b0dc10c
--- /dev/null
+++ b/app/controllers/oauth/jira/authorizations_controller.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+# This controller's role is to mimic and rewire the GitLab OAuth
+# flow routes for Jira DVCS integration.
+# See https://gitlab.com/gitlab-org/gitlab/issues/2381
+#
+class Oauth::Jira::AuthorizationsController < ApplicationController
+ skip_before_action :authenticate_user!
+ skip_before_action :verify_authenticity_token
+
+ # 1. Rewire Jira OAuth initial request to our stablished OAuth authorization URL.
+ def new
+ session[:redirect_uri] = params['redirect_uri']
+
+ redirect_to oauth_authorization_path(client_id: params['client_id'],
+ response_type: 'code',
+ scope: params['scope'],
+ redirect_uri: oauth_jira_callback_url)
+ end
+
+ # 2. Handle the callback call as we were a Github Enterprise instance client.
+ def callback
+ # Handling URI query params concatenation.
+ redirect_uri = URI.parse(session['redirect_uri'])
+ new_query = URI.decode_www_form(String(redirect_uri.query)) << ['code', params[:code]]
+ redirect_uri.query = URI.encode_www_form(new_query)
+
+ redirect_to redirect_uri.to_s
+ end
+
+ # 3. Rewire and adjust access_token request accordingly.
+ def access_token
+ # We have to modify request.parameters because Doorkeeper::Server reads params from there
+ request.parameters[:redirect_uri] = oauth_jira_callback_url
+
+ strategy = Doorkeeper::Server.new(self).token_request('authorization_code')
+ response = strategy.authorize
+
+ if response.status == :ok
+ access_token, scope, token_type = response.body.values_at('access_token', 'scope', 'token_type')
+
+ render body: "access_token=#{access_token}&scope=#{scope}&token_type=#{token_type}"
+ else
+ render status: response.status, body: response.body
+ end
+ rescue Doorkeeper::Errors::DoorkeeperError => e
+ render status: :unauthorized, body: e.type
+ end
+end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index a558b01f0c6..b798d6680bc 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -83,6 +83,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
+ def atlassian_oauth2
+ omniauth_flow(Gitlab::Auth::Atlassian)
+ end
+
private
def log_failed_login(user, provider)
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index af860297358..c27226c3f3f 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -31,8 +31,10 @@ class PasswordsController < Devise::PasswordsController
def update
super do |resource|
- if resource.valid? && resource.password_automatically_set?
- resource.update_attribute(:password_automatically_set, false)
+ if resource.valid?
+ resource.password_automatically_set = false
+ resource.password_expires_at = nil
+ resource.save(validate: false) if resource.changed?
end
end
end
diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb
index 95e055a44db..b19285e98bb 100644
--- a/app/controllers/profiles/accounts_controller.rb
+++ b/app/controllers/profiles/accounts_controller.rb
@@ -7,10 +7,9 @@ class Profiles::AccountsController < Profiles::ApplicationController
render(locals: show_view_variables)
end
- # rubocop: disable CodeReuse/ActiveRecord
def unlink
provider = params[:provider]
- identity = current_user.identities.find_by(provider: provider)
+ identity = find_identity(provider)
return render_404 unless identity
@@ -22,13 +21,18 @@ class Profiles::AccountsController < Profiles::ApplicationController
redirect_to profile_account_path
end
- # rubocop: enable CodeReuse/ActiveRecord
private
def show_view_variables
{}
end
+
+ def find_identity(provider)
+ return current_user.atlassian_identity if provider == 'atlassian_oauth2'
+
+ current_user.identities.find_by(provider: provider) # rubocop: disable CodeReuse/ActiveRecord
+ end
end
Profiles::AccountsController.prepend_if_ee('EE::Profiles::AccountsController')
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index 99e1b9027fa..965493955ac 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Profiles::KeysController < Profiles::ApplicationController
+ skip_before_action :authenticate_user!, only: [:get_keys]
+
def index
@keys = current_user.keys.order_id_desc
@key = Key.new
@@ -31,6 +33,25 @@ class Profiles::KeysController < Profiles::ApplicationController
end
end
+ # Get all keys of a user(params[:username]) in a text format
+ # Helpful for sysadmins to put in respective servers
+ def get_keys
+ if params[:username].present?
+ begin
+ user = UserFinder.new(params[:username]).find_by_username
+ if user.present?
+ render plain: user.all_ssh_keys.join("\n")
+ else
+ render_404
+ end
+ rescue => e
+ render html: e.message
+ end
+ else
+ render_404
+ end
+ end
+
private
def key_params
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index 064b2a2cc12..bc51830c119 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -4,12 +4,9 @@ class Profiles::NotificationsController < Profiles::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def show
@user = current_user
- @group_notifications = current_user.notification_settings.preload_source_route.for_groups.order(:id)
- @group_notifications += GroupsFinder.new(
- current_user,
- all_available: false,
- exclude_group_ids: @group_notifications.select(:source_id)
- ).execute.map { |group| current_user.notification_settings_for(group, inherit: true) }
+ @user_groups = user_groups
+ @group_notifications = UserGroupNotificationSettingsFinder.new(current_user, user_groups).execute
+
@project_notifications = current_user.notification_settings.for_projects.order(:id)
.preload_source_route
.select { |notification| current_user.can?(:read_project, notification.source) }
@@ -32,4 +29,10 @@ class Profiles::NotificationsController < Profiles::ApplicationController
def user_params
params.require(:user).permit(:notification_email, :notified_of_own_activity)
end
+
+ private
+
+ def user_groups
+ GroupsFinder.new(current_user, all_available: false).execute.order_name_asc.page(params[:page])
+ end
end
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 8653fe3b6ed..ea4d3e861be 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -51,6 +51,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController
:view_diffs_file_by_file,
:tab_width,
:sourcegraph_enabled,
+ :gitpod_enabled,
:render_whitespace_in_code
]
end
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 50fbf8146e5..5de6d84fdd9 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -2,6 +2,9 @@
class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
skip_before_action :check_two_factor_requirement
+ before_action do
+ push_frontend_feature_flag(:webauthn)
+ end
def show
unless current_user.two_factor_enabled?
@@ -33,7 +36,12 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
@qr_code = build_qr_code
@account_string = account_string
- setup_u2f_registration
+
+ if Feature.enabled?(:webauthn)
+ setup_webauthn_registration
+ else
+ setup_u2f_registration
+ end
end
def create
@@ -48,7 +56,13 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
else
@error = _('Invalid pin code')
@qr_code = build_qr_code
- setup_u2f_registration
+
+ if Feature.enabled?(:webauthn)
+ setup_webauthn_registration
+ else
+ setup_u2f_registration
+ end
+
render 'show'
end
end
@@ -56,7 +70,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
# A U2F (universal 2nd factor) device's information is stored after successful
# registration, which is then used while 2FA authentication is taking place.
def create_u2f
- @u2f_registration = U2fRegistration.register(current_user, u2f_app_id, u2f_registration_params, session[:challenges])
+ @u2f_registration = U2fRegistration.register(current_user, u2f_app_id, device_registration_params, session[:challenges])
if @u2f_registration.persisted?
session.delete(:challenges)
@@ -68,6 +82,21 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end
end
+ def create_webauthn
+ @webauthn_registration = Webauthn::RegisterService.new(current_user, device_registration_params, session[:challenge]).execute
+ if @webauthn_registration.persisted?
+ session.delete(:challenge)
+
+ redirect_to profile_two_factor_auth_path, notice: s_("Your WebAuthn device was registered!")
+ else
+ @qr_code = build_qr_code
+
+ setup_webauthn_registration
+
+ render :show
+ end
+ end
+
def codes
Users::UpdateService.new(current_user, user: current_user).execute! do |user|
@codes = user.generate_otp_backup_codes!
@@ -75,9 +104,13 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end
def destroy
- current_user.disable_two_factor!
+ result = TwoFactor::DestroyService.new(current_user, user: current_user).execute
- redirect_to profile_account_path, status: :found
+ if result[:status] == :success
+ redirect_to profile_account_path, status: :found, notice: s_('Two-factor authentication has been disabled successfully!')
+ else
+ redirect_to profile_account_path, status: :found, alert: result[:message]
+ end
end
def skip
@@ -108,11 +141,11 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
# Actual communication is performed using a Javascript API
def setup_u2f_registration
@u2f_registration ||= U2fRegistration.new
- @u2f_registrations = current_user.u2f_registrations
+ @registrations = u2f_registrations
u2f = U2F::U2F.new(u2f_app_id)
registration_requests = u2f.registration_requests
- sign_requests = u2f.authentication_requests(@u2f_registrations.map(&:key_handle))
+ sign_requests = u2f.authentication_requests(current_user.u2f_registrations.map(&:key_handle))
session[:challenges] = registration_requests.map(&:challenge)
gon.push(u2f: { challenges: session[:challenges], app_id: u2f_app_id,
@@ -120,8 +153,53 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
sign_requests: sign_requests })
end
- def u2f_registration_params
- params.require(:u2f_registration).permit(:device_response, :name)
+ def device_registration_params
+ params.require(:device_registration).permit(:device_response, :name)
+ end
+
+ def setup_webauthn_registration
+ @registrations = webauthn_registrations
+ @webauthn_registration ||= WebauthnRegistration.new
+
+ unless current_user.webauthn_xid
+ current_user.user_detail.update!(webauthn_xid: WebAuthn.generate_user_id)
+ end
+
+ options = webauthn_options
+ session[:challenge] = options.challenge
+
+ gon.push(webauthn: { options: options, app_id: u2f_app_id })
+ end
+
+ # Adds delete path to u2f registrations
+ # to reduce logic in view template
+ def u2f_registrations
+ current_user.u2f_registrations.map do |u2f_registration|
+ {
+ name: u2f_registration.name,
+ created_at: u2f_registration.created_at,
+ delete_path: profile_u2f_registration_path(u2f_registration)
+ }
+ end
+ end
+
+ def webauthn_registrations
+ current_user.webauthn_registrations.map do |webauthn_registration|
+ {
+ name: webauthn_registration.name,
+ created_at: webauthn_registration.created_at,
+ delete_path: profile_webauthn_registration_path(webauthn_registration)
+ }
+ end
+ end
+
+ def webauthn_options
+ WebAuthn::Credential.options_for_create(
+ user: { id: current_user.webauthn_xid, name: current_user.username },
+ exclude: current_user.webauthn_registrations.map { |c| c.credential_xid },
+ authenticator_selection: { user_verification: 'discouraged' },
+ rp: { name: 'GitLab' }
+ )
end
def groups_notification(groups)
@@ -129,6 +207,6 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
leave_group_links = groups.map { |group| view_context.link_to (s_("leave %{group_name}") % { group_name: group.full_name }), leave_group_members_path(group), remote: false, method: :delete}.to_sentence
s_(%{The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}.})
- .html_safe % { group_links: group_links.html_safe, leave_group_links: leave_group_links.html_safe }
+ .html_safe % { group_links: group_links.html_safe, leave_group_links: leave_group_links.html_safe }
end
end
diff --git a/app/controllers/profiles/webauthn_registrations_controller.rb b/app/controllers/profiles/webauthn_registrations_controller.rb
new file mode 100644
index 00000000000..81b1dd6f710
--- /dev/null
+++ b/app/controllers/profiles/webauthn_registrations_controller.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class Profiles::WebauthnRegistrationsController < Profiles::ApplicationController
+ def destroy
+ webauthn_registration = current_user.webauthn_registrations.find(params[:id])
+ webauthn_registration.destroy
+
+ redirect_to profile_two_factor_auth_path, status: :found, notice: _("Successfully deleted WebAuthn device.")
+ end
+end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index c9f46eb72c5..248d5755d92 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -6,6 +6,9 @@ class ProfilesController < Profiles::ApplicationController
before_action :user
before_action :authorize_change_username!, only: :update_username
skip_before_action :require_email, only: [:show, :update]
+ before_action do
+ push_frontend_feature_flag(:webauthn)
+ end
def show
end
@@ -101,6 +104,7 @@ class ProfilesController < Profiles::ApplicationController
:bio,
:email,
:role,
+ :gitpod_enabled,
:hide_no_password,
:hide_no_ssh_key,
:hide_project_limit,
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 518d414be1b..ca2692438e8 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -58,9 +58,9 @@ class Projects::ApplicationController < ApplicationController
def method_missing(method_sym, *arguments, &block)
case method_sym.to_s
when /\Aauthorize_(.*)!\z/
- authorize_action!($1.to_sym)
+ authorize_action!(Regexp.last_match(1).to_sym)
when /\Acheck_(.*)_available!\z/
- check_project_feature_available!($1.to_sym)
+ check_project_feature_available!(Regexp.last_match(1).to_sym)
else
super
end
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index 59a7dff680c..eb47fec2b7e 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -9,6 +9,7 @@ class Projects::BadgesController < Projects::ApplicationController
def pipeline
pipeline_status = Gitlab::Badge::Pipeline::Status
.new(project, params[:ref], opts: {
+ ignore_skipped: params[:ignore_skipped],
key_text: params[:key_text],
key_width: params[:key_width]
})
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index d969e7bf771..1568d9966dd 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -10,6 +10,8 @@ class Projects::BlobController < Projects::ApplicationController
include RedirectsForMissingPathOnTree
include SourcegraphDecorator
include DiffHelper
+ include RedisTracking
+ extend ::Gitlab::Utils::Override
prepend_before_action :authenticate_user!, only: [:edit]
@@ -33,8 +35,11 @@ class Projects::BlobController < Projects::ApplicationController
before_action only: :show do
push_frontend_feature_flag(:code_navigation, @project, default_enabled: true)
push_frontend_feature_flag(:suggest_pipeline) if experiment_enabled?(:suggest_pipeline)
+ push_frontend_feature_flag(:gitlab_ci_yml_preview, @project, default_enabled: false)
end
+ track_redis_hll_event :create, :update, name: 'g_edit_by_sfe', feature: :track_editor_edit_actions, feature_default_enabled: true
+
def new
commit unless @repository.empty?
end
@@ -99,8 +104,6 @@ class Projects::BlobController < Projects::ApplicationController
end
def diff
- apply_diff_view_cookie!
-
@form = Blobs::UnfoldPresenter.new(blob, diff_params)
# keep only json rendering when
@@ -256,4 +259,9 @@ class Projects::BlobController < Projects::ApplicationController
def diff_params
params.permit(:full, :since, :to, :bottom, :unfold, :offset, :indent)
end
+
+ override :visitor_id
+ def visitor_id
+ current_user&.id
+ end
end
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index db05da0bb7f..5ed35094476 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -9,6 +9,7 @@ class Projects::BoardsController < Projects::ApplicationController
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)
end
private
diff --git a/app/controllers/projects/ci/lints_controller.rb b/app/controllers/projects/ci/lints_controller.rb
index c13baaea8c6..813a0a9ddd5 100644
--- a/app/controllers/projects/ci/lints_controller.rb
+++ b/app/controllers/projects/ci/lints_controller.rb
@@ -2,6 +2,9 @@
class Projects::Ci::LintsController < Projects::ApplicationController
before_action :authorize_create_pipeline!
+ before_action do
+ push_frontend_feature_flag(:ci_lint_vue, project)
+ end
def show
end
@@ -10,40 +13,15 @@ class Projects::Ci::LintsController < Projects::ApplicationController
@content = params[:content]
@dry_run = params[:dry_run]
- if @dry_run && Gitlab::Ci::Features.lint_creates_pipeline_with_dry_run?(@project)
- pipeline = Ci::CreatePipelineService
- .new(@project, current_user, ref: @project.default_branch)
- .execute(:push, dry_run: true, content: @content)
-
- @status = pipeline.error_messages.empty?
- @stages = pipeline.stages
- @errors = pipeline.error_messages.map(&:content)
- @warnings = pipeline.warning_messages.map(&:content)
- else
- result = Gitlab::Ci::YamlProcessor.new_with_validation_errors(@content, yaml_processor_options)
+ @result = Gitlab::Ci::Lint
+ .new(project: @project, current_user: current_user)
+ .validate(@content, dry_run: @dry_run)
- @status = result.valid?
- @errors = result.errors
- @warnings = result.warnings
-
- if result.valid?
- @config_processor = result.config
- @stages = @config_processor.stages
- @builds = @config_processor.builds
- @jobs = @config_processor.jobs
+ respond_to do |format|
+ format.html { render :show }
+ format.json do
+ render json: ::Ci::Lint::ResultSerializer.new.represent(@result)
end
end
-
- render :show
- end
-
- private
-
- def yaml_processor_options
- {
- project: @project,
- user: current_user,
- sha: project.repository.commit.sha
- }
end
end
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 41631aea620..c6b6b825bb7 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -43,9 +43,10 @@ class Projects::ForksController < Projects::ApplicationController
end
format.json do
- namespaces = fork_service.valid_fork_targets - [current_user.namespace, project.namespace]
+ namespaces = load_namespaces_with_associations - [project.namespace]
+
render json: {
- namespaces: ForkNamespaceSerializer.new.represent(namespaces, project: project, current_user: current_user)
+ namespaces: ForkNamespaceSerializer.new.represent(namespaces, project: project, current_user: current_user, memberships: memberships_hash)
}
end
end
@@ -100,6 +101,14 @@ class Projects::ForksController < Projects::ApplicationController
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42335')
end
+
+ def load_namespaces_with_associations
+ @load_namespaces_with_associations ||= fork_service.valid_fork_targets(only_groups: true).preload(:route)
+ end
+
+ def memberships_hash
+ current_user.members.where(source: load_namespaces_with_associations).index_by(&:source_id)
+ end
end
Projects::ForksController.prepend_if_ee('EE::Projects::ForksController')
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index deba71c9dd3..9bed12fd151 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -5,10 +5,10 @@ class Projects::ImportsController < Projects::ApplicationController
include ImportUrlParams
# Authorize
- before_action :authorize_admin_project!, only: [:new, :create]
+ before_action :authorize_admin_project!, except: :show
before_action :require_namespace_project_creation_permission, only: :show
- before_action :require_no_repo, only: [:new, :create]
- before_action :redirect_if_progress, only: [:new, :create]
+ before_action :require_no_repo, except: :show
+ before_action :redirect_if_progress, except: :show
before_action :redirect_if_no_import, only: :show
def new
diff --git a/app/controllers/projects/issue_links_controller.rb b/app/controllers/projects/issue_links_controller.rb
new file mode 100644
index 00000000000..2f7489373ed
--- /dev/null
+++ b/app/controllers/projects/issue_links_controller.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Projects
+ class IssueLinksController < Projects::ApplicationController
+ include IssuableLinks
+
+ before_action :authorize_admin_issue_link!, only: [:create, :destroy]
+ before_action :authorize_issue_link_association!, only: :destroy
+
+ private
+
+ def authorize_admin_issue_link!
+ render_403 unless can?(current_user, :admin_issue_link, @project)
+ end
+
+ def authorize_issue_link_association!
+ render_404 if link.target != issue && link.source != issue
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def issue
+ @issue ||=
+ IssuesFinder.new(current_user, project_id: @project.id)
+ .find_by!(iid: params[:issue_id])
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def list_service
+ IssueLinks::ListService.new(issue, current_user)
+ end
+
+ def create_service
+ IssueLinks::CreateService.new(issue, current_user, create_params)
+ end
+
+ def destroy_service
+ IssueLinks::DestroyService.new(link, current_user)
+ end
+
+ def link
+ @link ||= IssueLink.find(params[:id])
+ end
+
+ def create_params
+ params.permit(:link_type, issuable_references: [])
+ end
+ end
+end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 2200860a184..7f0d23b79ce 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -10,13 +10,8 @@ class Projects::IssuesController < Projects::ApplicationController
include SpammableActions
include RecordUserLastActivity
- def issue_except_actions
- %i[index calendar new create bulk_update import_csv export_csv service_desk]
- end
-
- def set_issuables_index_only_actions
- %i[index calendar service_desk]
- end
+ ISSUES_EXCEPT_ACTIONS = %i[index calendar new create bulk_update import_csv export_csv service_desk].freeze
+ SET_ISSUEABLES_INDEX_ONLY_ACTIONS = %i[index calendar service_desk].freeze
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
prepend_before_action(only: [:calendar]) { authenticate_sessionless_user!(:ics) }
@@ -25,9 +20,10 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available!
- before_action :issue, unless: ->(c) { c.issue_except_actions.include?(c.action_name.to_sym) }
+ before_action :issue, unless: ->(c) { ISSUES_EXCEPT_ACTIONS.include?(c.action_name.to_sym) }
+ after_action :log_issue_show, unless: ->(c) { ISSUES_EXCEPT_ACTIONS.include?(c.action_name.to_sym) }
- before_action :set_issuables_index, if: ->(c) { c.set_issuables_index_only_actions.include?(c.action_name.to_sym) }
+ before_action :set_issuables_index, if: ->(c) { SET_ISSUEABLES_INDEX_ONLY_ACTIONS.include?(c.action_name.to_sym) }
# Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create]
@@ -48,6 +44,8 @@ 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(:design_management_todo_button, project, default_enabled: true)
+ push_frontend_feature_flag(:vue_sidebar_labels, @project)
end
before_action only: :show do
@@ -95,7 +93,7 @@ class Projects::IssuesController < Projects::ApplicationController
discussion_to_resolve: params[:discussion_to_resolve],
confidential: !!Gitlab::Utils.to_boolean(params[:issue][:confidential])
)
- service = Issues::BuildService.new(project, current_user, build_params)
+ service = ::Issues::BuildService.new(project, current_user, build_params)
@issue = @noteable = service.execute
@@ -115,7 +113,7 @@ class Projects::IssuesController < Projects::ApplicationController
discussion_to_resolve: params[:discussion_to_resolve]
)
- service = Issues::CreateService.new(project, current_user, create_params)
+ service = ::Issues::CreateService.new(project, current_user, create_params)
@issue = service.execute
if service.discussions_to_resolve.count(&:resolved?) > 0
@@ -143,7 +141,7 @@ class Projects::IssuesController < Projects::ApplicationController
new_project = Project.find(params[:move_to_project_id])
return render_404 unless issue.can_move?(current_user, new_project)
- @issue = Issues::UpdateService.new(project, current_user, target_project: new_project).execute(issue)
+ @issue = ::Issues::UpdateService.new(project, current_user, target_project: new_project).execute(issue)
end
respond_to do |format|
@@ -157,7 +155,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def reorder
- service = Issues::ReorderService.new(project, current_user, reorder_params)
+ service = ::Issues::ReorderService.new(project, current_user, reorder_params)
if service.execute(issue)
head :ok
@@ -167,7 +165,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def related_branches
- @related_branches = Issues::RelatedBranchesService
+ @related_branches = ::Issues::RelatedBranchesService
.new(project, current_user)
.execute(issue)
.map { |branch| branch.merge(link: branch_link(branch)) }
@@ -249,6 +247,13 @@ class Projects::IssuesController < Projects::ApplicationController
@issue
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def log_issue_show
+ return unless current_user && @issue
+
+ ::Gitlab::Search::RecentIssues.new(user: current_user).log_view(@issue)
+ end
+
alias_method :subscribable_resource, :issue
alias_method :issuable, :issue
alias_method :awardable, :issue
@@ -319,7 +324,7 @@ class Projects::IssuesController < Projects::ApplicationController
def update_service
update_params = issue_params.merge(spammable_params)
- Issues::UpdateService.new(project, current_user, update_params)
+ ::Issues::UpdateService.new(project, current_user, update_params)
end
def finder_type
@@ -340,10 +345,12 @@ class Projects::IssuesController < Projects::ApplicationController
def finder_options
options = super
- return options unless service_desk?
+ options[:issue_types] = Issue::TYPES_FOR_LIST
- options.reject! { |key| key == 'author_username' || key == 'author_id' }
- options[:author_id] = User.support_bot
+ if service_desk?
+ options.reject! { |key| key == 'author_username' || key == 'author_id' }
+ options[:author_id] = User.support_bot
+ end
options
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index 0bb4e0fb5ee..921da788ad2 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -43,6 +43,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
:discussion_locked,
label_ids: [],
assignee_ids: [],
+ reviewer_ids: [],
update_task: [:index, :checked, :line_number, :line_source]
]
end
diff --git a/app/controllers/projects/merge_requests/content_controller.rb b/app/controllers/projects/merge_requests/content_controller.rb
index eec5c1a4355..399745151b1 100644
--- a/app/controllers/projects/merge_requests/content_controller.rb
+++ b/app/controllers/projects/merge_requests/content_controller.rb
@@ -10,6 +10,9 @@ class Projects::MergeRequests::ContentController < Projects::MergeRequests::Appl
before_action :set_polling_header
around_action :allow_gitaly_ref_name_caching
+ FAST_POLLING_INTERVAL = 10.seconds.in_milliseconds
+ SLOW_POLLING_INTERVAL = 5.minutes.in_milliseconds
+
def widget
respond_to do |format|
format.json do
@@ -29,7 +32,8 @@ class Projects::MergeRequests::ContentController < Projects::MergeRequests::Appl
private
def set_polling_header
- Gitlab::PollingInterval.set_header(response, interval: 10_000)
+ interval = merge_request.open? ? FAST_POLLING_INTERVAL : SLOW_POLLING_INTERVAL
+ Gitlab::PollingInterval.set_header(response, interval: interval)
end
def serializer(entity)
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index bceccc7063b..8aacfdce094 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -4,7 +4,6 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
include DiffHelper
include RendersNotes
- before_action :apply_diff_view_cookie!
before_action :commit
before_action :define_diff_vars
before_action :define_diff_comment_vars, except: [:diffs_batch, :diffs_metadata]
@@ -21,15 +20,15 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
end
def diffs_batch
- return render_404 unless Feature.enabled?(:diffs_batch_load, @merge_request.project, default_enabled: true)
-
diffs = @compare.diffs_in_batch(params[:page], params[:per_page], diff_options: diff_options)
positions = @merge_request.note_positions_for_paths(diffs.diff_file_paths, current_user)
+ environment = @merge_request.environments_for(current_user, latest: true).last
diffs.unfold_diff_files(positions.unfoldable)
diffs.write_cache
options = {
+ environment: environment,
merge_request: @merge_request,
diff_view: diff_view,
pagination_data: diffs.pagination_data
@@ -65,7 +64,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
render: ->(partial, locals) { view_to_html_string(partial, locals) }
}
- options = additional_attributes.merge(diff_view: diff_view)
+ options = additional_attributes.merge(diff_view: Feature.enabled?(:unified_diff_lines, @merge_request.project) ? "inline" : diff_view)
if @merge_request.project.context_commits_enabled?
options[:context_commits] = @merge_request.recent_context_commits
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index e77d2f0f5ee..92785540172 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -10,8 +10,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include IssuableCollections
include RecordUserLastActivity
include SourcegraphDecorator
+ include DiffHelper
skip_before_action :merge_request, only: [:index, :bulk_update]
+ 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]
before_action :authorize_read_actual_head_pipeline!, only: [
@@ -25,9 +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_feature_flag(:diffs_batch_load, @project, default_enabled: true)
push_frontend_feature_flag(:deploy_from_footer, @project, default_enabled: true)
- push_frontend_feature_flag(:single_mr_diff_view, @project, default_enabled: true)
push_frontend_feature_flag(:suggest_pipeline) if experiment_enabled?(:suggest_pipeline)
push_frontend_feature_flag(:code_navigation, @project, default_enabled: true)
push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true)
@@ -36,10 +36,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:multiline_comments, @project, default_enabled: true)
push_frontend_feature_flag(:file_identifier_hash)
push_frontend_feature_flag(:batch_suggestions, @project, default_enabled: true)
- push_frontend_feature_flag(:auto_expand_collapsed_diffs, @project, default_enabled: true)
push_frontend_feature_flag(:approvals_commented_by, @project, default_enabled: true)
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)
+ push_frontend_feature_flag(:highlight_current_diff_row, @project)
+ push_frontend_feature_flag(:default_merge_ref_for_diffs, @project)
end
before_action do
@@ -48,6 +50,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions]
+ after_action :log_merge_request_show, only: [:show]
+
feature_category :source_code_management,
unless: -> (action) { action.ends_with?("_reports") }
feature_category :code_testing,
@@ -427,7 +431,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42438')
end
- def reports_response(report_comparison)
+ def reports_response(report_comparison, pipeline = nil)
+ if pipeline&.active?
+ ::Gitlab::PollingInterval.set_header(response, interval: 3000)
+
+ render json: '', status: :no_content && return
+ end
+
case report_comparison[:status]
when :parsing
::Gitlab::PollingInterval.set_header(response, interval: 3000)
@@ -442,6 +452,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
end
+ def log_merge_request_show
+ return unless current_user && @merge_request
+
+ ::Gitlab::Search::RecentMergeRequests.new(user: current_user).log_view(@merge_request)
+ end
+
def authorize_read_actual_head_pipeline!
return render_404 unless can?(current_user, :read_build, merge_request.actual_head_pipeline)
end
@@ -450,6 +466,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
params = request.query_parameters
params[:view] = cookies[:diff_view] if params[:view].blank? && cookies[:diff_view].present?
+ if Feature.enabled?(:default_merge_ref_for_diffs, project)
+ params = params.merge(diff_head: true)
+ end
+
diffs_metadata_project_json_merge_request_path(project, merge_request, 'json', params)
end
end
diff --git a/app/controllers/projects/metrics_dashboard_controller.rb b/app/controllers/projects/metrics_dashboard_controller.rb
index 51307c3665c..bc0a701b9fd 100644
--- a/app/controllers/projects/metrics_dashboard_controller.rb
+++ b/app/controllers/projects/metrics_dashboard_controller.rb
@@ -6,6 +6,8 @@ module Projects
# app/controllers/projects/environments_controller.rb
# See https://gitlab.com/gitlab-org/gitlab/-/issues/226002 for more details.
+ include Gitlab::Utils::StrongMemoize
+
before_action :authorize_metrics_dashboard!
before_action do
push_frontend_feature_flag(:prometheus_computed_alerts)
@@ -15,6 +17,15 @@ module Projects
def show
if environment
render 'projects/environments/metrics'
+ elsif default_environment
+ redirect_to project_metrics_dashboard_path(
+ project,
+ # Reverse merge the query parameters so that a query parameter named dashboard_path doesn't
+ # override the dashboard_path path parameter.
+ **permitted_params.to_h.symbolize_keys
+ .merge(environment: default_environment.id)
+ .reverse_merge(request.query_parameters.symbolize_keys)
+ )
else
render 'projects/environments/empty_metrics'
end
@@ -22,13 +33,21 @@ module Projects
private
+ def permitted_params
+ @permitted_params ||= params.permit(:dashboard_path, :environment, :page)
+ end
+
def environment
- @environment ||=
- if params[:environment]
- project.environments.find(params[:environment])
- else
- project.default_environment
- end
+ strong_memoize(:environment) do
+ env = permitted_params[:environment]
+ project.environments.find(env) if env
+ end
+ end
+
+ def default_environment
+ strong_memoize(:default_environment) do
+ project.default_environment
+ end
end
end
end
diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index 2a8bc823931..9ad6bf4095a 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -21,7 +21,7 @@ class Projects::PagesController < Projects::ApplicationController
format.html do
redirect_to project_pages_path(@project),
status: :found,
- notice: 'Pages were removed'
+ notice: 'Pages were scheduled for removal'
end
end
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index bfe23eb1035..c1734d2cd8a 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -63,10 +63,27 @@ class Projects::PipelinesController < Projects::ApplicationController
.new(project, current_user, create_params)
.execute(:web, ignore_skip_ci: true, save_on_errors: false)
- if @pipeline.created_successfully?
- redirect_to project_pipeline_path(project, @pipeline)
- else
- render 'new', status: :bad_request
+ respond_to do |format|
+ format.html do
+ if @pipeline.created_successfully?
+ redirect_to project_pipeline_path(project, @pipeline)
+ else
+ render 'new', status: :bad_request
+ end
+ end
+ format.json do
+ if @pipeline.created_successfully?
+ render json: PipelineSerializer
+ .new(project: project, current_user: current_user)
+ .represent(@pipeline),
+ status: :created
+ else
+ render json: { errors: @pipeline.error_messages.map(&:content),
+ warnings: @pipeline.warning_messages(limit: ::Gitlab::Ci::Warnings::MAX_LIMIT).map(&:content),
+ total_warnings: @pipeline.warning_messages.length },
+ status: :bad_request
+ end
+ end
end
end
@@ -247,7 +264,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def latest_pipeline
- @project.latest_pipeline_for_ref(params['ref'])
+ @project.latest_pipeline(params['ref'])
&.present(current_user: current_user)
end
diff --git a/app/controllers/projects/product_analytics_controller.rb b/app/controllers/projects/product_analytics_controller.rb
index badd7671dcf..c019dc191d6 100644
--- a/app/controllers/projects/product_analytics_controller.rb
+++ b/app/controllers/projects/product_analytics_controller.rb
@@ -27,6 +27,10 @@ class Projects::ProductAnalyticsController < Projects::ApplicationController
.new(project, { graph: graph, timerange: @timerange })
.execute
end
+
+ @activity_graph = ProductAnalytics::BuildActivityGraphService
+ .new(project, { timerange: @timerange })
+ .execute
end
private
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index c48d573edbf..bd24aae980c 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -11,6 +11,9 @@ class Projects::ReleasesController < Projects::ApplicationController
push_frontend_feature_flag(:release_show_page, project, default_enabled: true)
push_frontend_feature_flag(:release_asset_link_editing, project, default_enabled: true)
push_frontend_feature_flag(:release_asset_link_type, project, default_enabled: true)
+ push_frontend_feature_flag(:graphql_release_data, project, default_enabled: true)
+ push_frontend_feature_flag(:graphql_milestone_stats, project, default_enabled: true)
+ push_frontend_feature_flag(:graphql_releases_page, project, default_enabled: false)
end
before_action :authorize_update_release!, only: %i[edit update]
before_action :authorize_create_release!, only: :new
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index ca2a19e67b0..9a69ef991dd 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -20,7 +20,7 @@ class Projects::ServicesController < Projects::ApplicationController
layout "project_settings"
def edit
- @admin_integration = Service.instance_for(service.type)
+ @default_integration = Service.default_integration(service.type, project)
end
def update
@@ -65,18 +65,20 @@ class Projects::ServicesController < Projects::ApplicationController
result = ::Integrations::Test::ProjectService.new(@service, current_user, params[:event]).execute
unless result[:success]
- return { error: true, message: _('Test failed.'), service_response: result[:message].to_s, test_failed: true }
+ return { error: true, message: s_('Integrations|Connection failed. Please check your settings.'), service_response: result[:message].to_s, test_failed: true }
end
{}
rescue Gitlab::HTTP::BlockedUrlError => e
- { error: true, message: _('Test failed.'), service_response: e.message, test_failed: true }
+ { error: true, message: s_('Integrations|Connection failed. Please check your settings.'), service_response: e.message, test_failed: true }
end
def success_message
- message = @service.active? ? _('activated') : _('settings saved, but not activated')
-
- _('%{service_title} %{message}.') % { service_title: @service.title, message: message }
+ if @service.active?
+ s_('Integrations|%{integration} settings saved and active.') % { integration: @service.title }
+ else
+ s_('Integrations|%{integration} settings saved, but not active.') % { integration: @service.title }
+ end
end
def service
diff --git a/app/controllers/projects/static_site_editor_controller.rb b/app/controllers/projects/static_site_editor_controller.rb
index 9ec50ff8196..e97a8db0b79 100644
--- a/app/controllers/projects/static_site_editor_controller.rb
+++ b/app/controllers/projects/static_site_editor_controller.rb
@@ -14,13 +14,27 @@ class Projects::StaticSiteEditorController < Projects::ApplicationController
end
def show
- @config = Gitlab::StaticSiteEditor::Config.new(@repository, @ref, @path, params[:return_url])
+ service_response = ::StaticSiteEditor::ConfigService.new(
+ container: project,
+ current_user: current_user,
+ params: {
+ ref: @ref,
+ path: @path,
+ return_url: params[:return_url]
+ }
+ ).execute
+
+ if service_response.success?
+ @data = service_response.payload
+ else
+ respond_422
+ end
end
private
def assign_ref_and_path
- @ref, @path = extract_ref(params[:id])
+ @ref, @path = extract_ref(params.fetch(:id))
render_404 if @ref.blank? || @path.blank?
end
diff --git a/app/controllers/projects/todos_controller.rb b/app/controllers/projects/todos_controller.rb
index 0b11ee9edc0..33205b93317 100644
--- a/app/controllers/projects/todos_controller.rb
+++ b/app/controllers/projects/todos_controller.rb
@@ -15,6 +15,9 @@ class Projects::TodosController < Projects::ApplicationController
IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id])
when "merge_request"
MergeRequestsFinder.new(current_user, project_id: @project.id).find(params[:issuable_id])
+ when "design"
+ issue = IssuesFinder.new(current_user, project_id: @project.id).find(params[:issue_id])
+ DesignManagement::DesignsFinder.new(issue, current_user).find(params[:issuable_id])
end
end
end
diff --git a/app/controllers/projects/web_ide_schemas_controller.rb b/app/controllers/projects/web_ide_schemas_controller.rb
new file mode 100644
index 00000000000..3d16a6fafd4
--- /dev/null
+++ b/app/controllers/projects/web_ide_schemas_controller.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class Projects::WebIdeSchemasController < Projects::ApplicationController
+ before_action :authenticate_user!
+
+ def show
+ return respond_422 unless branch_sha
+
+ result = ::Ide::SchemasConfigService.new(project, current_user, sha: branch_sha, filename: params[:filename]).execute
+
+ if result[:status] == :success
+ render json: result[:schema]
+ else
+ render json: result, status: :unprocessable_entity
+ end
+ end
+
+ private
+
+ def branch_sha
+ return unless params[:branch].present?
+
+ project.commit(params[:branch])&.id
+ end
+end
diff --git a/app/controllers/projects/web_ide_terminals_controller.rb b/app/controllers/projects/web_ide_terminals_controller.rb
index 08ea5c4bca8..76bcaa9e80c 100644
--- a/app/controllers/projects/web_ide_terminals_controller.rb
+++ b/app/controllers/projects/web_ide_terminals_controller.rb
@@ -11,7 +11,7 @@ class Projects::WebIdeTerminalsController < Projects::ApplicationController
def check_config
return respond_422 unless branch_sha
- result = ::Ci::WebIdeConfigService.new(project, current_user, sha: branch_sha).execute
+ result = ::Ide::TerminalConfigService.new(project, current_user, sha: branch_sha).execute
if result[:status] == :success
head :ok
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index ba21fbddde1..848625ff6b5 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -42,10 +42,7 @@ class ProjectsController < Projects::ApplicationController
before_action only: [:edit] do
push_frontend_feature_flag(:service_desk_custom_address, @project)
- end
-
- before_action only: [:edit] do
- push_frontend_feature_flag(:approval_suggestions, @project)
+ push_frontend_feature_flag(:approval_suggestions, @project, default_enabled: true)
end
layout :determine_layout
@@ -98,6 +95,7 @@ class ProjectsController < Projects::ApplicationController
end
else
flash.now[:alert] = result[:message]
+ @project.reset
format.html { render_edit }
end
diff --git a/app/controllers/registrations/experience_levels_controller.rb b/app/controllers/registrations/experience_levels_controller.rb
index 5bb039bd9ba..38cffff91eb 100644
--- a/app/controllers/registrations/experience_levels_controller.rb
+++ b/app/controllers/registrations/experience_levels_controller.rb
@@ -12,7 +12,6 @@ module Registrations
if current_user.save
hide_advanced_issues
- flash[:message] = I18n.t('devise.registrations.signed_up')
redirect_to group_path(params[:namespace_path])
else
render :show
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 2a865aac767..a1252c68403 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -26,18 +26,18 @@ class RegistrationsController < Devise::RegistrationsController
end
def create
- track_experiment_event(:terms_opt_in, 'end')
accept_pending_invitations
super do |new_user|
persist_accepted_terms_if_required(new_user)
set_role_required(new_user)
+ track_terms_experiment(new_user)
yield new_user if block_given?
end
- # Do not show the signed_up notice message when the signup_flow experiment is enabled.
- # Instead, show it after successfully updating the role.
- flash[:notice] = nil if experiment_enabled?(:signup_flow)
+ # Devise sets a flash message on `create` for a successful signup,
+ # which we don't want to show.
+ flash[:notice] = nil
rescue Gitlab::Access::AccessDeniedError
redirect_to(new_user_session_path)
end
@@ -69,7 +69,6 @@ class RegistrationsController < Devise::RegistrationsController
return redirect_to new_users_sign_up_group_path if experiment_enabled?(:onboarding_issues) && show_onboarding_issues_experiment?
- set_flash_message! :notice, :signed_up
redirect_to path_for_signed_in_user(current_user)
else
render :welcome
@@ -89,7 +88,7 @@ class RegistrationsController < Devise::RegistrationsController
end
def set_role_required(new_user)
- new_user.set_role_required! if new_user.persisted? && experiment_enabled?(:signup_flow)
+ new_user.set_role_required! if new_user.persisted?
end
def destroy_confirmation_valid?
@@ -115,9 +114,7 @@ class RegistrationsController < Devise::RegistrationsController
def after_sign_up_path_for(user)
Gitlab::AppLogger.info(user_created_message(confirmed: user.confirmed?))
- return users_sign_up_welcome_path if experiment_enabled?(:signup_flow)
-
- path_for_signed_in_user(user)
+ users_sign_up_welcome_path
end
def after_inactive_sign_up_path_for(resource)
@@ -154,7 +151,7 @@ class RegistrationsController < Devise::RegistrationsController
end
def sign_up_params
- params.require(:user).permit(:username, :email, :email_confirmation, :name, :first_name, :last_name, :password)
+ params.require(:user).permit(:username, :email, :name, :first_name, :last_name, :password)
end
def resource_name
@@ -201,6 +198,13 @@ class RegistrationsController < Devise::RegistrationsController
true
end
+ def track_terms_experiment(new_user)
+ return unless new_user.persisted?
+
+ track_experiment_event(:terms_opt_in, 'end')
+ record_experiment_user(:terms_opt_in)
+ end
+
def load_recaptcha
Gitlab::Recaptcha.load_configurations!
end
@@ -208,7 +212,7 @@ class RegistrationsController < Devise::RegistrationsController
# 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 experiment_enabled?(:signup_flow)
+ if %w(welcome update_registration).include?(action_name) || experiment_enabled?(:signup_flow)
'devise_experimental_separate_sign_up_flow'
else
'devise'
@@ -216,7 +220,10 @@ class RegistrationsController < Devise::RegistrationsController
end
def show_onboarding_issues_experiment?
- !helpers.in_subscription_flow? && !helpers.in_invitation_flow? && !helpers.in_oauth_flow?
+ !helpers.in_subscription_flow? &&
+ !helpers.in_invitation_flow? &&
+ !helpers.in_oauth_flow? &&
+ !helpers.in_trial_flow?
end
end
diff --git a/app/controllers/repositories/lfs_api_controller.rb b/app/controllers/repositories/lfs_api_controller.rb
index f93038f455e..35751a2578f 100644
--- a/app/controllers/repositories/lfs_api_controller.rb
+++ b/app/controllers/repositories/lfs_api_controller.rb
@@ -46,7 +46,7 @@ module Repositories
end
def download_objects!
- existing_oids = project.all_lfs_objects_oids(oids: objects_oids)
+ existing_oids = project.lfs_objects_oids(oids: objects_oids)
objects.each do |object|
if existing_oids.include?(object[:oid])
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 56b6a5201e7..dedaf0c903a 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -4,14 +4,18 @@ class SearchController < ApplicationController
include ControllerWithCrossProjectAccessCheck
include SearchHelper
include RendersCommits
+ include RedisTracking
SCOPE_PRELOAD_METHOD = {
projects: :with_web_entity_associations,
issues: :with_web_entity_associations
}.freeze
+ track_redis_hll_event :show, name: 'i_search_total', feature: :search_track_unique_users, feature_default_enabled: true
+
around_action :allow_gitaly_ref_name_caching
+ before_action :block_anonymous_global_searches
skip_before_action :authenticate_user!
requires_cross_project_access if: -> do
search_term_present = params[:search].present? || params[:term].present?
@@ -119,10 +123,22 @@ class SearchController < ApplicationController
super
# Merging to :metadata will ensure these are logged as top level keys
- payload[:metadata] || {}
+ payload[:metadata] ||= {}
payload[:metadata]['meta.search.group_id'] = params[:group_id]
payload[:metadata]['meta.search.project_id'] = params[:project_id]
payload[:metadata]['meta.search.search'] = params[:search]
payload[:metadata]['meta.search.scope'] = params[:scope]
end
+
+ def block_anonymous_global_searches
+ return if params[:project_id].present? || params[:group_id].present?
+ return if current_user
+ return unless ::Feature.enabled?(:block_anonymous_global_searches)
+
+ store_location_for(:user, request.fullpath)
+
+ redirect_to new_user_session_path, alert: _('You must be logged in to search across all of GitLab')
+ end
end
+
+SearchController.prepend_if_ee('EE::SearchController')
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 9435b9887e9..318553b5e0a 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -11,6 +11,8 @@ class SessionsController < Devise::SessionsController
include Gitlab::Utils::StrongMemoize
skip_before_action :check_two_factor_requirement, only: [:destroy]
+ skip_before_action :check_password_expiration, only: [:destroy]
+
# replaced with :require_no_authentication_without_flash
skip_before_action :require_no_authentication, only: [:new, :create]
@@ -27,6 +29,9 @@ class SessionsController < Devise::SessionsController
before_action :save_failed_login, if: :action_new_and_failed_login?
before_action :load_recaptcha
before_action :set_invite_params, only: [:new]
+ before_action do
+ push_frontend_feature_flag(:webauthn)
+ end
after_action :log_failed_login, if: :action_new_and_failed_login?
after_action :verify_known_sign_in, only: [:create]
@@ -157,13 +162,13 @@ class SessionsController < Devise::SessionsController
(options = request.env["warden.options"]) && options[:action] == "unauthenticated"
end
- # storing sessions per IP lets us check if there are associated multiple
+ # counting sessions per IP lets us check if there are associated multiple
# anonymous sessions with one IP and prevent situations when there are
# multiple attempts of logging in
def store_unauthenticated_sessions
return if current_user
- Gitlab::AnonymousSession.new(request.remote_ip, session_id: request.session.id).store_session_id_per_ip
+ Gitlab::AnonymousSession.new(request.remote_ip).count_session_ip
end
# Handle an "initial setup" state, where there's only one user, it's an admin,
@@ -285,13 +290,15 @@ class SessionsController < Devise::SessionsController
end
def exceeded_anonymous_sessions?
- Gitlab::AnonymousSession.new(request.remote_ip).stored_sessions >= MAX_FAILED_LOGIN_ATTEMPTS
+ Gitlab::AnonymousSession.new(request.remote_ip).session_count >= MAX_FAILED_LOGIN_ATTEMPTS
end
def authentication_method
if user_params[:otp_attempt]
"two-factor"
- elsif user_params[:device_response]
+ elsif user_params[:device_response] && Feature.enabled?(:webauthn)
+ "two-factor-via-webauthn-device"
+ elsif user_params[:device_response] && !Feature.enabled?(:webauthn)
"two-factor-via-u2f-device"
else
"standard"
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 95ea31fa977..75a861423ed 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -37,12 +37,6 @@ class UsersController < ApplicationController
end
end
- # Get all keys of a user(params[:username]) in a text format
- # Helpful for sysadmins to put in respective servers
- def ssh_keys
- render plain: user.all_ssh_keys.join("\n")
- end
-
def activity
respond_to do |format|
format.html { render 'show' }