提交 71221554 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 b41cd8cb
......@@ -883,7 +883,7 @@
.time-tracking-help-state {
background: $white-light;
margin: 16px -20px 0;
margin: 16px -20px -20px;
padding: 16px 20px;
border-top: 1px solid $border-gray-light;
border-bottom: 1px solid $border-gray-light;
......
......@@ -112,20 +112,20 @@ module EmailsHelper
end
end
# "You are receiving this email because #{reason}"
# "You are receiving this email because #{reason} on #{gitlab_host}."
def notification_reason_text(reason)
string = case reason
when NotificationReason::OWN_ACTIVITY
'of your activity'
when NotificationReason::ASSIGNED
'you have been assigned an item'
when NotificationReason::MENTIONED
'you have been mentioned'
else
'of your account'
end
"#{string} on #{Gitlab.config.gitlab.host}"
gitlab_host = Gitlab.config.gitlab.host
case reason
when NotificationReason::OWN_ACTIVITY
_("You're receiving this email because of your activity on %{host}.") % { host: gitlab_host }
when NotificationReason::ASSIGNED
_("You're receiving this email because you have been assigned an item on %{host}.") % { host: gitlab_host }
when NotificationReason::MENTIONED
_("You're receiving this email because you have been mentioned on %{host}.") % { host: gitlab_host }
else
_("You're receiving this email because of your account on %{host}.") % { host: gitlab_host }
end
end
def create_list_id_string(project, list_id_max_length = 255)
......
......@@ -146,8 +146,9 @@ class ActiveSession
# remove sessions if there are more than ALLOWED_NUMBER_OF_ACTIVE_SESSIONS.
sessions = active_session_entries(session_ids, user.id, redis)
sessions.sort_by! {|session| session.updated_at }.reverse!
sessions = sessions[ALLOWED_NUMBER_OF_ACTIVE_SESSIONS..-1].map { |session| session.session_id }
destroy_sessions(redis, user, sessions)
sessions = sessions.drop(ALLOWED_NUMBER_OF_ACTIVE_SESSIONS)
sessions = sessions.map { |session| session.session_id }
destroy_sessions(redis, user, sessions) if sessions.any?
end
def self.cleaned_up_lookup_entries(redis, user)
......
......@@ -271,6 +271,21 @@ module Clusters
kubernetes_namespaces.delete_all(:delete_all)
end
def clusterable
return unless cluster_type
case cluster_type
when 'project_type'
project
when 'group_type'
group
when 'instance_type'
instance
else
raise NotImplementedError
end
end
private
def unique_management_project_environment_scope
......
# frozen_string_literal: true
# rubocop: disable CodeReuse/ActiveRecord
module Clusters
module Applications
##
# This service measures usage of the Modsecurity Web Application Firewall across the entire
# instance's deployed environments.
#
# The default configuration is`AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE=DetectionOnly` so we
# measure non-default values via definition of either ci_variables or ci_pipeline_variables.
# Since both these values are encrypted, we must decrypt and count them in memory.
#
# NOTE: this service is an approximation as it does not yet take into account `environment_scope` or `ci_group_variables`.
##
class IngressModsecurityUsageService
ADO_MODSEC_KEY = "AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE"
def initialize(blocking_count: 0, disabled_count: 0)
@blocking_count = blocking_count
@disabled_count = disabled_count
end
def execute
conditions = -> { merge(::Environment.available).merge(::Deployment.success).where(key: ADO_MODSEC_KEY) }
ci_pipeline_var_enabled =
::Ci::PipelineVariable
.joins(pipeline: { environments: :last_visible_deployment })
.merge(conditions)
.order('deployments.environment_id, deployments.id DESC')
ci_var_enabled =
::Ci::Variable
.joins(project: { environments: :last_visible_deployment })
.merge(conditions)
.merge(
# Give priority to pipeline variables by excluding from dataset
::Ci::Variable.joins(project: :environments).where.not(
environments: { id: ci_pipeline_var_enabled.select('DISTINCT ON (deployments.environment_id) deployments.environment_id') }
)
).select('DISTINCT ON (deployments.environment_id) ci_variables.*')
sum_modsec_config_counts(
ci_pipeline_var_enabled.select('DISTINCT ON (deployments.environment_id) ci_pipeline_variables.*')
)
sum_modsec_config_counts(ci_var_enabled)
{
ingress_modsecurity_blocking: @blocking_count,
ingress_modsecurity_disabled: @disabled_count
}
end
private
# These are encrypted so we must decrypt and count in memory
def sum_modsec_config_counts(dataset)
dataset.each do |var|
case var.value
when "On" then @blocking_count += 1
when "Off" then @disabled_count += 1
# `else` could be default or any unsupported user input
end
end
end
end
end
end
......@@ -20,7 +20,7 @@
#{link_to _("View it on GitLab"), @target_url}.
%br
-# Don't link the host in the line below, one link in the email is easier to quickly click than two.
= _("You're receiving this email because %{reason}.") % { reason: notification_reason_text(@reason) }
= notification_reason_text(@reason)
If you'd like to receive fewer emails, you can
- if @labels_url
adjust your #{link_to 'label subscriptions', @labels_url}.
......
......@@ -11,7 +11,7 @@
<% end -%>
<% end -%>
<%= "You're receiving this email because #{notification_reason_text(@reason)}." %>
<%= notification_reason_text(@reason) %>
<%= render_if_exists 'layouts/mailer/additional_text' %>
<%= text_footer_message -%>
---
title: Add modsecurity deployment counts to usage ping
merge_request: 20196
author:
type: added
---
title: Validate unique environment scope for instance clusters
merge_request: 20886
author:
type: fixed
---
title: Remove extra spacing below sidebar time tracking info
merge_request: 20657
author: Lee Tickett
type: other
# frozen_string_literal: true
# rubocop: disable Cop/PutGroupRoutesUnderScope
resources :groups, only: [:index, :new, :create] do
post :preview_markdown
end
# rubocop: enable Cop/PutGroupRoutesUnderScope
constraints(::Constraints::GroupUrlConstrainer.new) do
scope(path: 'groups/*id',
......
# frozen_string_literal: true
class AddIndexToModSecCiVariables < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :ci_variables, :project_id, where: "key = 'AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE'"
end
def down
remove_concurrent_index :ci_variables, :project_id
end
end
# frozen_string_literal: true
class AddIndexToModSecCiPipelineVariables < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :ci_pipeline_variables, :pipeline_id, where: "key = 'AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE'"
end
def down
remove_concurrent_index :ci_pipeline_variables, :pipeline_id
end
end
# frozen_string_literal: true
class DropOperationsFeatureFlagsClientsToken < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
# Ignored in 12.5 - https://gitlab.com/gitlab-org/gitlab/merge_requests/18923
remove_column :operations_feature_flags_clients, :token
end
def down
unless column_exists?(:operations_feature_flags_clients, :token)
add_column :operations_feature_flags_clients, :token, :string # rubocop:disable Migration/AddLimitToStringColumns
end
add_concurrent_index :operations_feature_flags_clients, [:project_id, :token], unique: true,
name: 'index_operations_feature_flags_clients_on_project_id_and_token'
end
end
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_11_25_140458) do
ActiveRecord::Schema.define(version: 2019_12_02_031812) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
......@@ -821,6 +821,7 @@ ActiveRecord::Schema.define(version: 2019_11_25_140458) do
t.integer "pipeline_id", null: false
t.integer "variable_type", limit: 2, default: 1, null: false
t.index ["pipeline_id", "key"], name: "index_ci_pipeline_variables_on_pipeline_id_and_key", unique: true
t.index ["pipeline_id"], name: "index_ci_pipeline_variables_on_pipeline_id", where: "((key)::text = 'AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE'::text)"
end
create_table "ci_pipelines", id: :serial, force: :cascade do |t|
......@@ -979,6 +980,7 @@ ActiveRecord::Schema.define(version: 2019_11_25_140458) do
t.boolean "masked", default: false, null: false
t.integer "variable_type", limit: 2, default: 1, null: false
t.index ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true
t.index ["project_id"], name: "index_ci_variables_on_project_id", where: "((key)::text = 'AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE'::text)"
end
create_table "cluster_groups", id: :serial, force: :cascade do |t|
......@@ -2774,9 +2776,7 @@ ActiveRecord::Schema.define(version: 2019_11_25_140458) do
create_table "operations_feature_flags_clients", force: :cascade do |t|
t.integer "project_id", null: false
t.string "token"
t.string "token_encrypted"
t.index ["project_id", "token"], name: "index_operations_feature_flags_clients_on_project_id_and_token", unique: true
t.index ["project_id", "token_encrypted"], name: "index_feature_flags_clients_on_project_id_and_token_encrypted", unique: true
end
......
......@@ -108,7 +108,8 @@ module Gitlab
services_usage,
approximate_counts,
usage_counters,
user_preferences_usage
user_preferences_usage,
ingress_modsecurity_usage
)
}
end
......@@ -170,6 +171,10 @@ module Gitlab
}
end
def ingress_modsecurity_usage
::Clusters::Applications::IngressModsecurityUsageService.new.execute
end
# rubocop: disable CodeReuse/ActiveRecord
def services_usage
types = {
......
......@@ -20306,15 +20306,21 @@ msgstr ""
msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
msgstr ""
msgid "You're receiving this email because %{reason}."
msgstr ""
msgid "You're receiving this email because of your account on %{host}."
msgstr ""
msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
msgstr ""
msgid "You're receiving this email because of your activity on %{host}."
msgstr ""
msgid "You're receiving this email because you have been assigned an item on %{host}."
msgstr ""
msgid "You're receiving this email because you have been mentioned on %{host}."
msgstr ""
msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication."
msgstr ""
......
# frozen_string_literal: true
module RuboCop
module Cop
# Checks for a group routes outside '/-/' scope.
# For more information see: https://gitlab.com/gitlab-org/gitlab/issues/29572
class PutGroupRoutesUnderScope < RuboCop::Cop::Cop
MSG = 'Put new group routes under /-/ scope'
def_node_matcher :dash_scope?, <<~PATTERN
(:send nil? :scope (hash <(pair (sym :path)(str "groups/*group_id/-")) ...>))
PATTERN
def on_send(node)
return unless in_group_routes?(node)
return unless resource?(node)
return unless outside_scope?(node)
add_offense(node)
end
def outside_scope?(node)
node.each_ancestor(:block).none? do |parent|
dash_scope?(parent.to_a.first)
end
end
def in_group_routes?(node)
path = node.location.expression.source_buffer.name
dirname = File.dirname(path)
filename = File.basename(path)
dirname.end_with?('config/routes') &&
filename.end_with?('group.rb')
end
def resource?(node)
node.method_name == :resource ||
node.method_name == :resources
end
end
end
end
......@@ -15,6 +15,7 @@ require_relative 'cop/avoid_route_redirect_leading_slash'
require_relative 'cop/line_break_around_conditional_block'
require_relative 'cop/prefer_class_methods_over_module'
require_relative 'cop/put_project_routes_under_scope'
require_relative 'cop/put_group_routes_under_scope'
require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
......
......@@ -74,6 +74,28 @@ describe EmailsHelper do
end
end
describe 'notification_reason_text' do
subject { helper.notification_reason_text(reason_code) }
using RSpec::Parameterized::TableSyntax
where(:reason_code, :reason_text) do
NotificationReason::OWN_ACTIVITY | ' of your activity '
NotificationReason::ASSIGNED | ' you have been assigned an item '
NotificationReason::MENTIONED | ' you have been mentioned '
"" | ' of your account '
nil | ' of your account '
end
with_them do
it { is_expected.to start_with "You're receiving this email because" }
it { is_expected.to include reason_text }
it { is_expected.to end_with "on #{Gitlab.config.gitlab.host}." }
end
end
describe 'sanitize_name' do
context 'when name contains a valid URL string' do
it 'returns name with `.` replaced with `_` to prevent mail clients from auto-linking URLs' do
......
......@@ -297,6 +297,24 @@ describe Gitlab::UsageData do
end
end
describe '#ingress_modsecurity_usage' do
subject { described_class.ingress_modsecurity_usage }
it 'gathers variable data' do
allow_any_instance_of(
::Clusters::Applications::IngressModsecurityUsageService
).to receive(:execute).and_return(
{
ingress_modsecurity_blocking: 1,
ingress_modsecurity_disabled: 2
}
)
expect(subject[:ingress_modsecurity_blocking]).to eq(1)
expect(subject[:ingress_modsecurity_disabled]).to eq(2)
end
end
describe '#license_usage_data' do
subject { described_class.license_usage_data }
......
......@@ -329,6 +329,35 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
)
end
end
context 'when the number of active sessions is lower than the limit' do
before do
Gitlab::Redis::SharedState.with do |redis|
((max_number_of_sessions_plus_two - 4)..max_number_of_sessions_plus_two).each do |number|
redis.del("session:user:gitlab:#{user.id}:#{number}")
end
end
end
it 'does not remove active session entries, but removes lookup entries' do
lookup_entries_before_cleanup = Gitlab::Redis::SharedState.with do |redis|
redis.smembers("session:lookup:user:gitlab:#{user.id}")
end
sessions_before_cleanup = Gitlab::Redis::SharedState.with do |redis|
redis.scan_each(match: "session:user:gitlab:#{user.id}:*").to_a
end
ActiveSession.cleanup(user)
Gitlab::Redis::SharedState.with do |redis|
lookup_entries = redis.smembers("session:lookup:user:gitlab:#{user.id}")
sessions = redis.scan_each(match: "session:user:gitlab:#{user.id}:*").to_a
expect(sessions.count).to eq(sessions_before_cleanup.count)
expect(lookup_entries.count).to be < lookup_entries_before_cleanup.count
end
end
end
end
end
end
......@@ -976,4 +976,38 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
expect(cluster.kubernetes_namespaces).to be_empty
end
end
describe '#clusterable' do
subject { cluster.clusterable }
context 'project type' do
let(:cluster) { create(:cluster, :project) }
it { is_expected.to eq(cluster.project) }
end
context 'group type' do
let(:cluster) { create(:cluster, :group) }
it { is_expected.to eq(cluster.group) }
end
context 'instance type' do
let(:cluster) { create(:cluster, :instance) }
it { is_expected.to be_a(Clusters::Instance) }
end
context 'unknown type' do
let(:cluster) { create(:cluster, :project) }
before do
allow(cluster).to receive(:cluster_type).and_return('unknown_type')
end
it 'raises NotImplementedError' do
expect { subject }.to raise_error(NotImplementedError)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require 'rubocop'
require_relative '../../../rubocop/cop/put_group_routes_under_scope'
describe RuboCop::Cop::PutGroupRoutesUnderScope do
include CopHelper
subject(:cop) { described_class.new }
before do
allow(cop).to receive(:in_group_routes?).and_return(true)
end
it 'registers an offense when route is outside scope' do
expect_offense(<<~PATTERN.strip_indent)
scope(path: 'groups/*group_id/-', module: :groups) do
resource :issues
end
resource :notes
^^^^^^^^^^^^^^^ Put new group routes under /-/ scope
PATTERN
end
it 'does not register an offense when resource inside the scope' do
expect_no_offenses(<<~PATTERN.strip_indent)
scope(path: 'groups/*group_id/-', module: :groups) do
resource :issues
resource :notes
end
PATTERN
end
it 'does not register an offense when resource is deep inside the scope' do
expect_no_offenses(<<~PATTERN.strip_indent)
scope(path: 'groups/*group_id/-', module: :groups) do
resource :issues
resource :projects do
resource :issues do
resource :notes
end
end
end
PATTERN
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Applications::IngressModsecurityUsageService do
describe '#execute' do
ADO_MODSEC_KEY = Clusters::Applications::IngressModsecurityUsageService::ADO_MODSEC_KEY
let(:project_with_ci_var) { create(:environment).project }
let(:project_with_pipeline_var) { create(:environment).project }
subject { described_class.new.execute }
context 'with multiple projects' do
let(:pipeline1) { create(:ci_pipeline, :with_job, project: project_with_pipeline_var) }
let(:pipeline2) { create(:ci_pipeline, :with_job, project: project_with_ci_var) }
let!(:deployment_with_pipeline_var) do
create(
:deployment,
:success,
environment: project_with_pipeline_var.environments.first,
project: project_with_pipeline_var,
deployable: pipeline1.builds.last
)
end
let!(:deployment_with_project_var) do
create(
:deployment,
:success,
environment: project_with_ci_var.environments.first,
project: project_with_ci_var,
deployable: pipeline2.builds.last
)
end
context 'mixed data' do
let!(:ci_variable) { create(:ci_variable, project: project_with_ci_var, key: ADO_MODSEC_KEY, value: "On") }
let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline1, key: ADO_MODSEC_KEY, value: "Off") }
it 'gathers variable data' do
expect(subject[:ingress_modsecurity_blocking]).to eq(1)
expect(subject[:ingress_modsecurity_disabled]).to eq(1)
end
end
context 'blocking' do
let(:modsec_values) { { key: ADO_MODSEC_KEY, value: "On" } }
let!(:ci_variable) { create(:ci_variable, project: project_with_ci_var, **modsec_values) }
let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline1, **modsec_values) }
it 'gathers variable data' do
expect(subject[:ingress_modsecurity_blocking]).to eq(2)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
end
end
context 'disabled' do
let(:modsec_values) { { key: ADO_MODSEC_KEY, value: "Off" } }
let!(:ci_variable) { create(:ci_variable, project: project_with_ci_var, **modsec_values) }
let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline1, **modsec_values) }
it 'gathers variable data' do
expect(subject[:ingress_modsecurity_blocking]).to eq(0)
expect(subject[:ingress_modsecurity_disabled]).to eq(2)
end
end
end
context 'when set as both ci and pipeline variables' do
let(:modsec_values) { { key: ADO_MODSEC_KEY, value: "Off" } }
let(:pipeline) { create(:ci_pipeline, :with_job, project: project_with_ci_var) }
let!(:deployment) do
create(
:deployment,
:success,
environment: project_with_ci_var.environments.first,
project: project_with_ci_var,
deployable: pipeline.builds.last
)
end
let!(:ci_variable) { create(:ci_variable, project: project_with_ci_var, **modsec_values) }
let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline, **modsec_values) }
it 'wont double-count projects' do
expect(subject[:ingress_modsecurity_blocking]).to eq(0)
expect(subject[:ingress_modsecurity_disabled]).to eq(1)
end
it 'gives precedence to pipeline variable' do
pipeline_variable.update(value: "On")
expect(subject[:ingress_modsecurity_blocking]).to eq(1)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
end
end
context 'when a project has multiple environments' do
let(:modsec_values) { { key: ADO_MODSEC_KEY, value: "On" } }
let!(:env1) { project_with_pipeline_var.environments.first }
let!(:env2) { create(:environment, project: project_with_pipeline_var) }
let!(:pipeline_with_2_deployments) do
create(:ci_pipeline, :with_job, project: project_with_ci_var).tap do |pip|
pip.builds << build(:ci_build, pipeline: pip, project: project_with_pipeline_var)
end
end
let!(:deployment1) do
create(
:deployment,
:success,
environment: env1,
project: project_with_pipeline_var,
deployable: pipeline_with_2_deployments.builds.last
)
end
let!(:deployment2) do
create(
:deployment,
:success,
environment: env2,
project: project_with_pipeline_var,
deployable: pipeline_with_2_deployments.builds.last
)
end
context 'when set as ci variable' do
let!(:ci_variable) { create(:ci_variable, project: project_with_pipeline_var, **modsec_values) }
it 'gathers variable data' do
expect(subject[:ingress_modsecurity_blocking]).to eq(2)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
end
end
context 'when set as pipeline variable' do
let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline_with_2_deployments, **modsec_values) }
it 'gathers variable data' do
expect(subject[:ingress_modsecurity_blocking]).to eq(2)
expect(subject[:ingress_modsecurity_disabled]).to eq(0)
end
end
end
context 'when an environment has multiple deployments' do
let!(:env) { project_with_pipeline_var.environments.first }
let!(:pipeline_first) do
create(:ci_pipeline, :with_job, project: project_with_pipeline_var).tap do |pip|
pip.builds << build(:ci_build, pipeline: pip, project: project_with_pipeline_var)
end
end
let!(:pipeline_last) do
create(:ci_pipeline, :with_job, project: project_with_pipeline_var).tap do |pip|
pip.builds << build(:ci_build, pipeline: pip, project: project_with_pipeline_var)
end
end
let!(:deployment_first) do
create(
:deployment,
:success,
environment: env,
project: project_with_pipeline_var,
deployable: pipeline_first.builds.last
)
end
let!(:deployment_last) do
create(
:deployment,
:success,
environment: env,
project: project_with_pipeline_var,
deployable: pipeline_last.builds.last
)
end
context 'when set as pipeline variable' do
let!(:first_pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline_first, key: ADO_MODSEC_KEY, value: "On") }
let!(:last_pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline_last, key: ADO_MODSEC_KEY, value: "Off") }
it 'gives precedence to latest deployment' do
expect(subject[:ingress_modsecurity_blocking]).to eq(0)
expect(subject[:ingress_modsecurity_disabled]).to eq(1)
end
end
end
end
end
文件模式从 100644 更改为 100755
文件模式从 100644 更改为 100755
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册