Allow anyone to pull public NuGet packages on group level
🔭 Context
ّIn Feature specific permissions for the Package R... (#329253 - closed), it was suggested to have a way to have a public package registry in a non-public (private or internal) projects.
We quickly discovered challenges for endpoints on the group-level. Thus, we decided to scope that issue to project-level endpoints only (see #329253 (comment 1120283554)).
🔥 Problem
The problem is twofold here:
- User might want to be able to pull packages from a public package registry (in a non-public project) at the group-level endpoint.
- Some formats don't have project-level endpoints, for example Composer. As such, the
Allow anyone to pull
toggle will not work for Composer packages.
🚒 Solution
We have two aspects to consider in the path to the solution:
-
On the group level endpoints, the requests to pull packages are authorized by checking the permission
:read_group
. The:read_group
permission is granted to a request based on some conditions.Now we have an additional condition that we need to introduce: if a group has projects with public registries, grant the
:read_group
permission to the request.However, we cannot simply do that, because adding a new condition will make it subject to be evaluated by any request. While in fact, we only need it in a narrow, specific context: when pulling a package on the group-level. And given that the
:read_group
permission is used across all the codebase by many requests, we cannot add such a package-scoped condition to it.The solution of this issue would be by introducing a new permission. The new permission will be used exclusively in the context of pulling packages on the group-level endpoints, and will live in the
Packages::Policies::GroupPolicy
class. In this policy, we have all rules we want to apply on packages on the group level. -
At the group level, we have finders to collect all the projects that a user can access to find a package. This part uses the user access level.
In short, we have a SQL query that says: within this group, collect all the public projects + all the projects where the user has
reporter
access.We will need to update that to: within this group, collect all the public projects + all the projects where the user has
reporter
access + all the projects that have a public package registry.
What does this MR do and why?
-
Introducing a new permission
read_package_in_group
to authorize a request to pull a package on the group level endpoint. The permission lives in thePackages::Policies::GroupPolicy
class and will be evaluated only after checking theread_group
permission. If the request was granted theread_group
permission, then no need to check theread_package_in_group
one. Otherwise, it's evaluated. -
Modify the SQL query used in the package finder to be: within this group, collect all the public projects + all the projects where the user has
reporter
access + all the projects that have a public package registry. -
Modify authorization methods to be ready to evaluate the
read_package_in_group
new permission on a group. -
Add the needed tests.
-
Scope the changes to only NuGet Repository's metadata group level endpoints behind a feature flag. It would be easier & debug if the changes are scoped to one format. After the solution proves to be working fine, we can extend it to the other package formats.
MR acceptance checklist
Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Screenshots or screen recordings
N/A
How to set up and validate locally
- Make sure you have a private project in a private group to use to publish the package in.
- Open rails console:
# Enable the ~"feature flag" Feature.enable(:allow_anyone_to_pull_public_nuget_packages_on_group_level) # Enable `package_registry_allow_anyone_to_pull_option` application setting ApplicationSetting.last.update(package_registry_allow_anyone_to_pull_option: true) # Enable Allow anyone to pull from Package Registry in the private project from step 1 Project.find(<id>).project_feature.update(package_registry_access_level: ::ProjectFeature::PUBLIC) # Create an external user that we are sure they dont have access to the group or project user = FactoryBot.create(:user, :external) # Keep the username of the user, we will use it later user.username # Create PAT for the external user, we will use it later pat = FactoryBot.create(:personal_access_token, user: ext).token # stub file upload def fixture_file_upload(*args, **kwargs) Rack::Test::UploadedFile.new(*args, **kwargs) end # Create a nuget package in the private project from step 1 package = FactoryBot.create(:nuget_package, project_id: <private_project_id>) # Keep the package name, we will use it later package.name
- We can now try installing the package using NuGet CLI:
nuget install NugetPackage1 -OutputDirectory <output_directory> -Source "http://gdk.test:3000/api/v4/groups/<private_group_id>/-/packages/nuget/index.json"
nuget install
will show a prompt asking for username and password, so paste what we got from rails console (the external username & pat) - The package should be installed successfully.
-
Clear NuGet cache, and delete the installed package from the local output directory on your machine, and disable Allow anyone to pull from Package Registry in the private project:
Project.find(<id>).project_feature.update(package_registry_access_level: ::ProjectFeature::ENABLED)
- Repeat step 3:
➡ 404 Not found
Related to #383537 (closed) & #468058 (closed)