Skip to content

Protected packages: Adding new scope for_package_name

What does this MR do and why?

  • This MR adds the following scopes to the model Packages::Protection::Rule:
    • .for_package_name => the scope filters Packages::Protection::Rule for a given package name (with optional wildcard characters)
    • .for_package_type => the scope filters protection rules based on a given package type
    • .push_protection_applicable_for_access_level => filter the protection rules based on a given access level
    • .protected_against_push => combination of the three scopes as a utility
  • Additionally, the method push_protected_from?(user) in Packages::Package constructs the complete DB query to check if a package protection rule for this package and user exists
  • Note: These scopes are needed for the implementation of the feature protected packages, see &5574
  • The implementation was already discussed in a previous MR. This MR intends to isolate the implemenation and other discussions related to this necessary scope.

🛠 with at Siemens

Implementation thoughts

We want package protection rules to allow wildcard character (*) as used in the feature protected branches. This needs to be considered for the implementation of this scope.

As discussed in a previous MR and the initial concept, @10io sees the possibility and the need for performing the evaluation of the packages protection rules in the database in order to have an efficient way of evaluating different package protection rules at once.

In terms of implementation, @10io proposes to use the psql operator ILIKE to perform the evaluation in the database. As the the field package_name_pattern accepts wildcard characters (only *), the value of package_name_pattern has to be preprocessed before being applied to the ILIKE query, e.g. convert the character * to %. For this preprocessing, @10io also proposes to introduce a new column that contains the preprocessed value for package_name_pattern that can be easioly applied to the ILIKE query.

This MR is intended to finalize the discussion and the implementation on this matter.

DB Query changes

Old query

New query

The following queries are used to find and evaluate the package protection rules for a given package, e.g. package.push_protected_from?(project_owner)

ProjectAuthorization Maximum (0.9ms) SELECT MAX("project_authorizations"."access_level") AS "maximum_access_level", "project_authorizations"."project_id" AS "project_authorizations_project_id" FROM "project_authorizations" WHERE "project_authorizations"."user_id" = 1 AND "project_authorizations"."project_id" = 7 GROUP BY "project_authorizations"."project_id" /*application:console,db_config_name:main,console_hostname:Gerardos-MacBook-Pro.local,console_username:client-siemens,line:/app/models/user.rb:2038:in `block in max_member_access_for_project_ids'*/

Packages::Protection::Rule Exists? (0.4ms) SELECT 1 AS one FROM "packages_protection_rules" WHERE "packages_protection_rules"."project_id" = 7 AND ('@gitlab-org/npm-package-1' ILIKE package_name_pattern_ilike_query) AND "packages_protection_rules"."package_type" = 2 AND "packages_protection_rules"."push_protected_up_to_access_level" >= 50 LIMIT 1 /*application:console,db_config_name:main,console_hostname:Gerardos-MacBook-Pro.local,console_username:client-siemens,line:/app/models/packages/package.rb:416:in `push_protected_from?'*/

Screenshots or screen recordings

  • There are no specific frontend changes in this MR => it focusses only on backend

How to set up and validate locally

  1. Create one Packages::Protection::Rule
Packages::Protection::Rule.create(project: Project.find(7), package_type: :npm, push_protected_up_to_access_level: Gitlab::Access::DEVELOPER, package_name_pattern: "@gitlab-org/npm-package-*")
  1. Build a package object and check if a package protection rule exists for a project developer
package = Project.find(7).packages.with_package_type(:npm).with_name("@gitlab-org/npm-package-1").build()
project_developer = Project.find(7).team.developers.first
package.push_protected_from?(project_developer)
# => Returns true because developers are not allowed to push based on the created protection rule, see step 1.
  1. Build a package object and check if a package protection rule exists for a project owner (SQL query displayed above)
package = Project.find(7).packages.with_package_type(:npm).with_name("@gitlab-org/npm-package-1").build()
project_owner = Project.find(7).team.owners.first
package.push_protected_from?(project_owner)
# => Returns false because owners are still allowed to push because there is no protection rule, see step 1.

Todos

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #416382

💽 Database analysis

Edited by Gerardo Navarro

Merge request reports