diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-20 18:40:28 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-20 18:40:28 +0300 |
commit | b595cb0c1dec83de5bdee18284abe86614bed33b (patch) | |
tree | 8c3d4540f193c5ff98019352f554e921b3a41a72 /app/controllers/concerns | |
parent | 2f9104a328fc8a4bddeaa4627b595166d24671d0 (diff) |
Add latest changes from gitlab-org/gitlab@15-2-stable-eev15.2.0-rc42
Diffstat (limited to 'app/controllers/concerns')
-rw-r--r-- | app/controllers/concerns/google_analytics_csp.rb | 24 | ||||
-rw-r--r-- | app/controllers/concerns/harbor/access.rb | 27 | ||||
-rw-r--r-- | app/controllers/concerns/harbor/artifact.rb | 41 | ||||
-rw-r--r-- | app/controllers/concerns/harbor/repository.rb | 51 | ||||
-rw-r--r-- | app/controllers/concerns/harbor/tag.rb | 41 | ||||
-rw-r--r-- | app/controllers/concerns/integrations/hooks_execution.rb | 60 | ||||
-rw-r--r-- | app/controllers/concerns/integrations/params.rb | 1 | ||||
-rw-r--r-- | app/controllers/concerns/issuable_actions.rb | 1 | ||||
-rw-r--r-- | app/controllers/concerns/issuable_collections.rb | 11 | ||||
-rw-r--r-- | app/controllers/concerns/product_analytics_tracking.rb | 5 | ||||
-rw-r--r-- | app/controllers/concerns/verifies_with_email.rb | 194 |
11 files changed, 444 insertions, 12 deletions
diff --git a/app/controllers/concerns/google_analytics_csp.rb b/app/controllers/concerns/google_analytics_csp.rb new file mode 100644 index 00000000000..1a8e405928d --- /dev/null +++ b/app/controllers/concerns/google_analytics_csp.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module GoogleAnalyticsCSP + extend ActiveSupport::Concern + + included do + content_security_policy do |policy| + next unless helpers.google_tag_manager_enabled? || policy.directives.present? + + default_script_src = policy.directives['script-src'] || policy.directives['default-src'] + script_src_values = Array.wrap(default_script_src) | ['*.googletagmanager.com'] + policy.script_src(*script_src_values) + + default_img_src = policy.directives['img-src'] || policy.directives['default-src'] + img_src_values = Array.wrap(default_img_src) | ['*.google-analytics.com', '*.googletagmanager.com'] + policy.img_src(*img_src_values) + + default_connect_src = policy.directives['connect-src'] || policy.directives['default-src'] + connect_src_values = + Array.wrap(default_connect_src) | ['*.google-analytics.com', '*.analytics.google.com', '*.googletagmanager.com'] + policy.connect_src(*connect_src_values) + end + end +end diff --git a/app/controllers/concerns/harbor/access.rb b/app/controllers/concerns/harbor/access.rb new file mode 100644 index 00000000000..70de72f15fc --- /dev/null +++ b/app/controllers/concerns/harbor/access.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Harbor + module Access + extend ActiveSupport::Concern + + included do + before_action :harbor_registry_enabled! + before_action :authorize_read_harbor_registry! + before_action do + push_frontend_feature_flag(:harbor_registry_integration) + end + + feature_category :integrations + end + + private + + def harbor_registry_enabled! + render_404 unless Feature.enabled?(:harbor_registry_integration) + end + + def authorize_read_harbor_registry! + raise NotImplementedError + end + end +end diff --git a/app/controllers/concerns/harbor/artifact.rb b/app/controllers/concerns/harbor/artifact.rb new file mode 100644 index 00000000000..c9d7d26fbb9 --- /dev/null +++ b/app/controllers/concerns/harbor/artifact.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Harbor + module Artifact + def index + respond_to do |format| + format.json do + artifacts + end + end + end + + private + + def query_params + params.permit(:repository_id, :search, :sort, :page, :limit) + end + + def query + Gitlab::Harbor::Query.new(container.harbor_integration, query_params) + end + + def artifacts + unless query.valid? + return render( + json: { message: 'Invalid parameters', errors: query.errors }, + status: :unprocessable_entity + ) + end + + artifacts_json = ::Integrations::HarborSerializers::ArtifactSerializer.new + .with_pagination(request, response) + .represent(query.artifacts) + render json: artifacts_json + end + + def container + raise NotImplementedError + end + end +end diff --git a/app/controllers/concerns/harbor/repository.rb b/app/controllers/concerns/harbor/repository.rb new file mode 100644 index 00000000000..0e541e2172e --- /dev/null +++ b/app/controllers/concerns/harbor/repository.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Harbor + module Repository + def index + respond_to do |format| + format.html + format.json do + repositories + end + end + end + + # The show action renders index to allow frontend routing to work on page refresh + def show + render :index + end + + private + + def query_params + params.permit(:search, :sort, :page, :limit) + end + + def query + Gitlab::Harbor::Query.new(container.harbor_integration, query_params) + end + + def repositories + unless query.valid? + return render( + json: { message: 'Invalid parameters', errors: query.errors }, + status: :unprocessable_entity + ) + end + + repositories_json = ::Integrations::HarborSerializers::RepositorySerializer.new + .with_pagination(request, response) + .represent( + query.repositories, + url: container.harbor_integration.url, + project_name: container.harbor_integration.project_name + ) + render json: repositories_json + end + + def container + raise NotImplementedError + end + end +end diff --git a/app/controllers/concerns/harbor/tag.rb b/app/controllers/concerns/harbor/tag.rb new file mode 100644 index 00000000000..e0c00d1155a --- /dev/null +++ b/app/controllers/concerns/harbor/tag.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Harbor + module Tag + def index + respond_to do |format| + format.json do + tags + end + end + end + + private + + def query_params + params.permit(:repository_id, :artifact_id, :sort, :page, :limit) + end + + def query + Gitlab::Harbor::Query.new(container.harbor_integration, query_params) + end + + def tags + unless query.valid? + return render( + json: { message: 'Invalid parameters', errors: query.errors }, + status: :unprocessable_entity + ) + end + + tags_json = ::Integrations::HarborSerializers::TagSerializer.new + .with_pagination(request, response) + .represent(query.tags) + render json: tags_json + end + + def container + raise NotImplementedError + end + end +end diff --git a/app/controllers/concerns/integrations/hooks_execution.rb b/app/controllers/concerns/integrations/hooks_execution.rb index 6a9d3d51f9b..fb26840168f 100644 --- a/app/controllers/concerns/integrations/hooks_execution.rb +++ b/app/controllers/concerns/integrations/hooks_execution.rb @@ -3,8 +3,68 @@ module Integrations::HooksExecution extend ActiveSupport::Concern + included do + attr_writer :hooks, :hook + end + + def index + self.hooks = relation.select(&:persisted?) + self.hook = relation.new + end + + def create + self.hook = relation.new(hook_params) + hook.save + + unless hook.valid? + self.hooks = relation.select(&:persisted?) + flash[:alert] = hook.errors.full_messages.join.html_safe + end + + redirect_to action: :index + end + + def update + if hook.update(hook_params) + flash[:notice] = _('Hook was successfully updated.') + redirect_to action: :index + else + render 'edit' + end + end + + def destroy + destroy_hook(hook) + + redirect_to action: :index, status: :found + end + + def edit + redirect_to(action: :index) unless hook + end + private + def hook_params + permitted = hook_param_names + trigger_values + permitted << { url_variables: [:key, :value] } + + ps = params.require(:hook).permit(*permitted).to_h + + ps[:url_variables] = ps[:url_variables].to_h { [_1[:key], _1[:value].presence] } if ps.key?(:url_variables) + + if action_name == 'update' && ps.key?(:url_variables) + supplied = ps[:url_variables] + ps[:url_variables] = hook.url_variables.merge(supplied).compact + end + + ps + end + + def hook_param_names + %i[enable_ssl_verification token url push_events_branch_filter] + end + def destroy_hook(hook) result = WebHooks::DestroyService.new(current_user).execute(hook) diff --git a/app/controllers/concerns/integrations/params.rb b/app/controllers/concerns/integrations/params.rb index d256b331174..c3ad9d3dff3 100644 --- a/app/controllers/concerns/integrations/params.rb +++ b/app/controllers/concerns/integrations/params.rb @@ -6,7 +6,6 @@ module Integrations ALLOWED_PARAMS_CE = [ :active, - :add_pusher, :alert_events, :api_key, :api_token, diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 07850acd23d..a5e49b1b16a 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -184,7 +184,6 @@ module IssuableActions def paginated_discussions return if params[:per_page].blank? - return if issuable.instance_of?(Issue) && Feature.disabled?(:paginated_issue_discussions, project) return if issuable.instance_of?(MergeRequest) && Feature.disabled?(:paginated_mr_discussions, project) strong_memoize(:paginated_discussions) do diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 4841225de08..de38d26e3fe 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -24,12 +24,9 @@ module IssuableCollections show_alert_if_search_is_disabled @issuables = issuables_collection + set_pagination - unless pagination_disabled? - set_pagination - - return if redirect_out_of_range(@issuables, @total_pages) - end + return if redirect_out_of_range(@issuables, @total_pages) if params[:label_name].present? && @project labels_params = { project_id: @project.id, title: params[:label_name] } @@ -59,10 +56,6 @@ module IssuableCollections end # rubocop:enable Gitlab/ModuleWithInstanceVariables - def pagination_disabled? - false - end - # rubocop: disable CodeReuse/ActiveRecord def issuables_collection finder.execute.preload(preload_for_collection) diff --git a/app/controllers/concerns/product_analytics_tracking.rb b/app/controllers/concerns/product_analytics_tracking.rb index 0b51b3dd380..dc7ba8295b9 100644 --- a/app/controllers/concerns/product_analytics_tracking.rb +++ b/app/controllers/concerns/product_analytics_tracking.rb @@ -28,7 +28,10 @@ module ProductAnalyticsTracking def event_enabled?(event) events_to_ff = { g_analytics_valuestream: :route_hll_to_snowplow, - i_search_paid: :route_hll_to_snowplow_phase2 + + i_search_paid: :route_hll_to_snowplow_phase2, + i_search_total: :route_hll_to_snowplow_phase2, + i_search_advanced: :route_hll_to_snowplow_phase2 } Feature.enabled?(events_to_ff[event.to_sym], tracking_namespace_source) diff --git a/app/controllers/concerns/verifies_with_email.rb b/app/controllers/concerns/verifies_with_email.rb new file mode 100644 index 00000000000..1a3e7136481 --- /dev/null +++ b/app/controllers/concerns/verifies_with_email.rb @@ -0,0 +1,194 @@ +# frozen_string_literal: true + +# == VerifiesWithEmail +# +# Controller concern to handle verification by email +module VerifiesWithEmail + extend ActiveSupport::Concern + include ActionView::Helpers::DateHelper + + TOKEN_LENGTH = 6 + TOKEN_VALID_FOR_MINUTES = 60 + + included do + prepend_before_action :verify_with_email, only: :create, unless: -> { two_factor_enabled? } + end + + def verify_with_email + return unless user = find_user || find_verification_user + + if session[:verification_user_id] && token = verification_params[:verification_token].presence + # The verification token is submitted, verify it + verify_token(user, token) + elsif require_email_verification_enabled?(user) + # Limit the amount of password guesses, since we now display the email verification page + # when the password is correct, which could be a giveaway when brute-forced. + return render_sign_in_rate_limited if check_rate_limit!(:user_sign_in, scope: user) { true } + + if user.valid_password?(user_params[:password]) + # The user has logged in successfully. + if user.unlock_token + # Prompt for the token if it already has been set + prompt_for_email_verification(user) + elsif user.access_locked? || !AuthenticationEvent.initial_login_or_known_ip_address?(user, request.ip) + # require email verification if: + # - their account has been locked because of too many failed login attempts, or + # - they have logged in before, but never from the current ip address + send_verification_instructions(user) + prompt_for_email_verification(user) + end + end + end + end + + def resend_verification_code + return unless user = find_verification_user + + send_verification_instructions(user) + prompt_for_email_verification(user) + end + + def successful_verification + session.delete(:verification_user_id) + @redirect_url = after_sign_in_path_for(current_user) # rubocop:disable Gitlab/ModuleWithInstanceVariables + + render layout: 'minimal' + end + + private + + def find_verification_user + return unless session[:verification_user_id] + + User.find_by_id(session[:verification_user_id]) + end + + # After successful verification and calling sign_in, devise redirects the + # user to this path. Override it to show the successful verified page. + def after_sign_in_path_for(resource) + if action_name == 'create' && session[:verification_user_id] + return users_successful_verification_path + end + + super + end + + def send_verification_instructions(user) + return if send_rate_limited?(user) + + raw_token, encrypted_token = generate_token + user.unlock_token = encrypted_token + user.lock_access!({ send_instructions: false }) + send_verification_instructions_email(user, raw_token) + end + + def send_verification_instructions_email(user, token) + return unless user.can?(:receive_notifications) + + Notify.verification_instructions_email( + user.id, + token: token, + expires_in: TOKEN_VALID_FOR_MINUTES).deliver_later + + log_verification(user, :instructions_sent) + end + + def verify_token(user, token) + return handle_verification_failure(user, :rate_limited) if verification_rate_limited?(user) + return handle_verification_failure(user, :invalid) unless valid_token?(user, token) + return handle_verification_failure(user, :expired) if expired_token?(user) + + handle_verification_success(user) + end + + def generate_token + raw_token = SecureRandom.random_number(10**TOKEN_LENGTH).to_s.rjust(TOKEN_LENGTH, '0') + encrypted_token = digest_token(raw_token) + [raw_token, encrypted_token] + end + + def digest_token(token) + Devise.token_generator.digest(User, :unlock_token, token) + end + + def render_sign_in_rate_limited + message = s_('IdentityVerification|Maximum login attempts exceeded. '\ + 'Wait %{interval} and try again.') % { interval: user_sign_in_interval } + redirect_to new_user_session_path, alert: message + end + + def user_sign_in_interval + interval_in_seconds = Gitlab::ApplicationRateLimiter.rate_limits[:user_sign_in][:interval] + distance_of_time_in_words(interval_in_seconds) + end + + def verification_rate_limited?(user) + Gitlab::ApplicationRateLimiter.throttled?(:email_verification, scope: user.unlock_token) + end + + def send_rate_limited?(user) + Gitlab::ApplicationRateLimiter.throttled?(:email_verification_code_send, scope: user) + end + + def expired_token?(user) + user.locked_at < (Time.current - TOKEN_VALID_FOR_MINUTES.minutes) + end + + def valid_token?(user, token) + user.unlock_token == digest_token(token) + end + + def handle_verification_failure(user, reason) + message = case reason + when :rate_limited + s_("IdentityVerification|You've reached the maximum amount of tries. "\ + 'Wait %{interval} or resend a new code and try again.') % { interval: email_verification_interval } + when :expired + s_('IdentityVerification|The code has expired. Resend a new code and try again.') + when :invalid + s_('IdentityVerification|The code is incorrect. Enter it again, or resend a new code.') + end + + user.errors.add(:base, message) + log_verification(user, :failed_attempt, reason) + + prompt_for_email_verification(user) + end + + def email_verification_interval + interval_in_seconds = Gitlab::ApplicationRateLimiter.rate_limits[:email_verification][:interval] + distance_of_time_in_words(interval_in_seconds) + end + + def handle_verification_success(user) + user.unlock_access! + log_verification(user, :successful) + + sign_in(user) + end + + def prompt_for_email_verification(user) + session[:verification_user_id] = user.id + self.resource = user + + render 'devise/sessions/email_verification' + end + + def verification_params + params.require(:user).permit(:verification_token) + end + + def log_verification(user, event, reason = nil) + Gitlab::AppLogger.info( + message: 'Email Verification', + event: event.to_s.titlecase, + username: user.username, + ip: request.ip, + reason: reason.to_s + ) + end + + def require_email_verification_enabled?(user) + Feature.enabled?(:require_email_verification, user) + end +end |