Class: Packages::Protection::Rule

Inherits:
ApplicationRecord show all
Defined in:
app/models/packages/protection/rule.rb

Constant Summary collapse

NPM_PACKAGE_NAME_FORMAT =
{
  with: Gitlab::Regex::Packages::Protection::Rules.protection_rules_npm_package_name_pattern_regex,
  message: ->(_object, _data) { _('should be a valid NPM package name with optional wildcard characters.') }
}.freeze
PYPI_PACKAGE_NAME_FORMAT =
{
  with: Gitlab::Regex::Packages::Protection::Rules.protection_rules_pypi_package_name_pattern_regex,
  message: ->(_object, _data) { _('should be a valid PyPI package name with optional wildcard characters.') }
}.freeze

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Constants included from HasCheckConstraints

HasCheckConstraints::NOT_NULL_CHECK_PATTERN

Constants included from ResetOnColumnErrors

ResetOnColumnErrors::MAX_RESET_PERIOD

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

===, cached_column_list, #create_or_load_association, current_transaction, declarative_enum, default_select_columns, delete_all_returning, #deleted_from_database?, id_in, id_not_in, iid_in, nullable_column?, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from Organizations::Sharding

#sharding_organization

Methods included from ResetOnColumnErrors

#reset_on_union_error, #reset_on_unknown_attribute_error

Methods included from Gitlab::SensitiveSerializableHash

#serializable_hash

Class Method Details

.for_action_exists?(action:, access_level:, package_name:, package_type:) ⇒ Boolean

Returns:

  • (Boolean)


63
64
65
66
67
68
69
70
71
72
# File 'app/models/packages/protection/rule.rb', line 63

def self.for_action_exists?(action:, access_level:, package_name:, package_type:)
  return false if [access_level, package_name, package_type].any?(&:blank?)

  minimum_access_level_column = "minimum_access_level_for_#{action}"

  for_package_type(package_type)
    .where(":access_level < #{minimum_access_level_column}", access_level: access_level)
    .for_package_name(package_name)
    .exists?
end

.for_delete_exists?(access_level:, package_name:, package_type:) ⇒ Boolean

Returns:

  • (Boolean)


58
59
60
61
# File 'app/models/packages/protection/rule.rb', line 58

def self.for_delete_exists?(access_level:, package_name:, package_type:)
  for_action_exists?(action: :delete, access_level: access_level, package_name: package_name,
    package_type: package_type)
end

.for_push_exists_for_projects_and_packages(projects_and_packages) ⇒ ActiveRecord::Result

Accepts a list of projects and packages and returns a result set indicating whether the package name is protected.

the project id (bigint), the package name (string) and the package type (smallint). is protected.

Example:

Packages::Protection::Rule.for_push_exists_for_projects_and_packages([
  [1, '@my_group/my_project_1/package_1', 2],
  [1, '@my_group/my_project_1/package_2', 2],
  [2, '@my_group/my_project_2/package_1', 3],
  ...
])

[
  {'project_id' => 1, 'package_name' => '@my_group/my_project_1/package_1', 'package_type' => 2,
   'protected' => true},
  {'project_id' => 1, 'package_name' => '@my_group/my_project_1/package_2', 'package_type' => 2,
   'protected' => false},
  {'project_id' => 2, 'package_name' => '@my_group/my_project_2/package_1', 'package_type' => 3,
   'protected' => true},
  ...
]

Parameters:

  • projects_and_packages (Array<Array>)

    an array of arrays where each sub-array contains

Returns:

  • (ActiveRecord::Result)

    a result set indicating whether each project, package name and package type



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'app/models/packages/protection/rule.rb', line 101

def self.for_push_exists_for_projects_and_packages(projects_and_packages)
  return none if projects_and_packages.blank?

  project_ids, package_names, package_types = projects_and_packages.transpose

  cte_query_sql = <<~SQL
    unnest(
      ARRAY[:project_ids]::bigint[],
      ARRAY[:package_names]::text[],
      ARRAY[:package_types]::smallint[]
    ) AS projects_and_packages(project_id, package_name, package_type)
  SQL

  cte_query =
    select('*').from(sanitize_sql_array(
      [cte_query_sql, { project_ids: project_ids, package_names: package_names, package_types: package_types }]
    ))

  cte_name = :projects_and_packages_cte
  cte = Gitlab::SQL::CTE.new(cte_name, cte_query)

  rules_cte_project_id = "#{cte_name}.#{adapter_class.quote_column_name('project_id')}"
  rules_cte_package_name = "#{cte_name}.#{adapter_class.quote_column_name('package_name')}"
  rules_cte_package_type = "#{cte_name}.#{adapter_class.quote_column_name('package_type')}"

  protection_rule_exsits_subquery = select(1)
    .where("#{rules_cte_project_id} = project_id")
    .where(arel_table[:package_type].eq(Arel.sql(rules_cte_package_type)))
    .where("#{rules_cte_package_name} ILIKE #{::Gitlab::SQL::Glob.to_like('package_name_pattern')}")

  query = select(
    rules_cte_project_id,
    rules_cte_package_type,
    rules_cte_package_name,
    sanitize_sql_array(['EXISTS(?) AS protected', protection_rule_exsits_subquery])
  ).from(Arel.sql(cte_name.to_s))

  connection.exec_query(query.with(cte.to_arel).to_sql)
end

Instance Method Details

#at_least_one_minimum_access_level_must_be_presentObject



141
142
143
144
145
# File 'app/models/packages/protection/rule.rb', line 141

def at_least_one_minimum_access_level_must_be_present
  return unless minimum_access_level_for_delete.blank? && minimum_access_level_for_push.blank?

  errors.add(:base, _('A rule must have at least a minimum access role for push or delete.'))
end

#ensure_pattern_type_and_target_fieldObject



147
148
149
150
# File 'app/models/packages/protection/rule.rb', line 147

def ensure_pattern_type_and_target_field
  self.pattern_type ||= Packages::Protection::Rule.pattern_types[:wildcard]
  self.target_field ||= Packages::Protection::Rule.target_fields[:package_name]
end