From 040b483be5941b713d9a478aa7d36634d8bf827c Mon Sep 17 00:00:00 2001 From: pulpbot Date: Sun, 4 Feb 2024 02:32:18 +0000 Subject: [PATCH 1/3] Update CI files [noissue] --- .github/template_gitref | 2 +- .github/workflows/create-branch.yml | 2 +- docs/template_gitref | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/template_gitref b/.github/template_gitref index 6cfa2261..443d675e 100644 --- a/.github/template_gitref +++ b/.github/template_gitref @@ -1 +1 @@ -2021.08.26-312-ge121aa6 +2021.08.26-313-g0528507 diff --git a/.github/workflows/create-branch.yml b/.github/workflows/create-branch.yml index a8767817..677eaaf1 100644 --- a/.github/workflows/create-branch.yml +++ b/.github/workflows/create-branch.yml @@ -33,7 +33,7 @@ jobs: - name: "Install python dependencies" run: | echo ::group::PYDEPS - pip install bump2version jinja2 pyyaml + pip install bump2version jinja2 pyyaml packaging echo ::endgroup:: - name: "Setting secrets" diff --git a/docs/template_gitref b/docs/template_gitref index 6cfa2261..443d675e 100644 --- a/docs/template_gitref +++ b/docs/template_gitref @@ -1 +1 @@ -2021.08.26-312-ge121aa6 +2021.08.26-313-g0528507 From ea2222214211f171adb697f12f506e16efb624c6 Mon Sep 17 00:00:00 2001 From: Lubos Mjachky Date: Fri, 2 Feb 2024 12:27:44 +0100 Subject: [PATCH 2/3] Add role-based access control closes #331 --- CHANGES/331.feature | 1 + .../migrations/0007_add_model_permissions.py | 25 ++ pulp_ostree/app/models.py | 20 +- pulp_ostree/app/viewsets.py | 402 +++++++++++++++++- pulp_ostree/tests/functional/api/test_rbac.py | 80 ++++ 5 files changed, 513 insertions(+), 15 deletions(-) create mode 100644 CHANGES/331.feature create mode 100644 pulp_ostree/app/migrations/0007_add_model_permissions.py create mode 100644 pulp_ostree/tests/functional/api/test_rbac.py diff --git a/CHANGES/331.feature b/CHANGES/331.feature new file mode 100644 index 00000000..cb5b07e3 --- /dev/null +++ b/CHANGES/331.feature @@ -0,0 +1 @@ +Added role-based access control. diff --git a/pulp_ostree/app/migrations/0007_add_model_permissions.py b/pulp_ostree/app/migrations/0007_add_model_permissions.py new file mode 100644 index 00000000..de6a0981 --- /dev/null +++ b/pulp_ostree/app/migrations/0007_add_model_permissions.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.9 on 2024-02-02 12:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ostree', '0006_alter_pointers_to_related_models_globally'), + ] + + operations = [ + migrations.AlterModelOptions( + name='ostreedistribution', + options={'default_related_name': '%(app_label)s_%(model_name)s', 'permissions': [('manage_roles_ostreedistribution', 'Can manage roles on ostree distributions')]}, + ), + migrations.AlterModelOptions( + name='ostreeremote', + options={'default_related_name': '%(app_label)s_%(model_name)s', 'permissions': [('manage_roles_ostreeremote', 'Can manage roles on ostree remotes')]}, + ), + migrations.AlterModelOptions( + name='ostreerepository', + options={'default_related_name': '%(app_label)s_%(model_name)s', 'permissions': [('sync_ostreerepository', 'Can start a sync task'), ('modify_ostreerepository', 'Can modify content of the repository'), ('manage_roles_ostreerepository', 'Can manage roles on ostree repositories'), ('repair_ostreerepository', 'Can repair repository versions'), ('import_commits_ostreerepository', 'Can import commits into a repository')]}, + ), + ] diff --git a/pulp_ostree/app/models.py b/pulp_ostree/app/models.py index 68a83352..b691edae 100755 --- a/pulp_ostree/app/models.py +++ b/pulp_ostree/app/models.py @@ -4,6 +4,7 @@ from django.contrib.postgres.fields import ArrayField from pulpcore.plugin.models import ( + AutoAddObjPermsMixin, Content, Remote, Repository, @@ -123,7 +124,7 @@ class Meta: unique_together = [["sha256", "relative_path"]] -class OstreeRemote(Remote): +class OstreeRemote(Remote, AutoAddObjPermsMixin): """A remote model for OSTree content.""" TYPE = "ostree" @@ -134,9 +135,12 @@ class OstreeRemote(Remote): class Meta: default_related_name = "%(app_label)s_%(model_name)s" + permissions = [ + ("manage_roles_ostreeremote", "Can manage roles on ostree remotes"), + ] -class OstreeRepository(Repository): +class OstreeRepository(Repository, AutoAddObjPermsMixin): """A repository model for OSTree content.""" TYPE = "ostree" @@ -155,6 +159,13 @@ class OstreeRepository(Repository): class Meta: default_related_name = "%(app_label)s_%(model_name)s" + permissions = [ + ("sync_ostreerepository", "Can start a sync task"), + ("modify_ostreerepository", "Can modify content of the repository"), + ("manage_roles_ostreerepository", "Can manage roles on ostree repositories"), + ("repair_ostreerepository", "Can repair repository versions"), + ("import_commits_ostreerepository", "Can import commits into a repository"), + ] def finalize_new_version(self, new_version): """Handle repository duplicates.""" @@ -162,10 +173,13 @@ def finalize_new_version(self, new_version): validate_duplicate_content(new_version) -class OstreeDistribution(Distribution): +class OstreeDistribution(Distribution, AutoAddObjPermsMixin): """A distribution model for OSTree content.""" TYPE = "ostree" class Meta: default_related_name = "%(app_label)s_%(model_name)s" + permissions = [ + ("manage_roles_ostreedistribution", "Can manage roles on ostree distributions"), + ] diff --git a/pulp_ostree/app/viewsets.py b/pulp_ostree/app/viewsets.py index 4147c2cf..12efbf53 100755 --- a/pulp_ostree/app/viewsets.py +++ b/pulp_ostree/app/viewsets.py @@ -5,7 +5,12 @@ from rest_framework.decorators import action from rest_framework.serializers import ValidationError -from pulpcore.plugin.viewsets import ReadOnlyContentViewSet, ContentFilter, NAME_FILTER_OPTIONS +from pulpcore.plugin.viewsets import ( + ReadOnlyContentViewSet, + ContentFilter, + NAME_FILTER_OPTIONS, + SingleArtifactContentUploadViewSet, +) from pulpcore.plugin import viewsets as core from pulpcore.plugin.models import RepositoryVersion from pulpcore.plugin.actions import ModifyRepositoryActionMixin @@ -15,24 +20,187 @@ RepositorySyncURLSerializer, ) from pulpcore.plugin.tasking import dispatch +from pulpcore.plugin.util import get_objects_for_user from . import models, serializers, tasks -class OstreeRemoteViewSet(core.RemoteViewSet): +REPO_VIEW_PERM = "ostree.view_ostreerepository" + + +class OstreeRemoteViewSet(core.RemoteViewSet, core.RolesMixin): """A ViewSet class for OSTree remote repositories.""" endpoint_name = "ostree" queryset = models.OstreeRemote.objects.all() serializer_class = serializers.OstreeRemoteSerializer - - -class OstreeRepositoryViewSet(core.RepositoryViewSet, ModifyRepositoryActionMixin): + queryset_filtering_required_permission = "ostree.view_ostreeremote" + + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "my_permissions"], + "principal": "authenticated", + "effect": "allow", + }, + { + "action": ["create"], + "principal": "authenticated", + "effect": "allow", + "condition": "has_model_perms:ostree.add_ostreeremote", + }, + { + "action": ["retrieve"], + "principal": "authenticated", + "effect": "allow", + "condition": "has_model_or_obj_perms:ostree.view_ostreeremote", + }, + { + "action": ["update", "partial_update", "set_label", "unset_label"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_obj_perms:ostree.change_ostreeremote", + ], + }, + { + "action": ["destroy"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_obj_perms:ostree.delete_ostreeremote", + ], + }, + { + "action": ["list_roles", "add_role", "remove_role"], + "principal": "authenticated", + "effect": "allow", + "condition": ["has_model_or_obj_perms:ostree.manage_roles_ostreeremote"], + }, + ], + "creation_hooks": [ + { + "function": "add_roles_for_object_creator", + "parameters": {"roles": "ostree.ostreeremote_owner"}, + }, + ], + "queryset_scoping": {"function": "scope_queryset"}, + } + LOCKED_ROLES = { + "ostree.ostreeremote_creator": ["ostree.add_ostreeremote"], + "ostree.ostreeremote_owner": [ + "ostree.view_ostreeremote", + "ostree.change_ostreeremote", + "ostree.delete_ostreeremote", + "ostree.manage_roles_ostreeremote", + ], + "ostree.ostreeremote_viewer": ["ostree.view_ostreeremote"], + } + + +class OstreeRepositoryViewSet(core.RepositoryViewSet, ModifyRepositoryActionMixin, core.RolesMixin): """A ViewSet class for OSTree repositories.""" endpoint_name = "ostree" queryset = models.OstreeRepository.objects.all() serializer_class = serializers.OstreeRepositorySerializer + queryset_filtering_required_permission = "ostree.view_ostreerepository" + + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "my_permissions"], + "principal": "authenticated", + "effect": "allow", + }, + { + "action": ["create"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_perms:ostree.add_ostreerepository", + "has_remote_param_model_or_obj_perms:ostree.view_ostreeremote", + ], + }, + { + "action": ["retrieve"], + "principal": "authenticated", + "effect": "allow", + "condition": "has_model_or_obj_perms:ostree.view_ostreerepository", + }, + { + "action": ["destroy"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_obj_perms:ostree.delete_ostreerepository", + ], + }, + { + "action": ["update", "partial_update", "set_label", "unset_label"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_obj_perms:ostree.change_ostreerepository", + "has_remote_param_model_or_obj_perms:ostree.view_ostreeremote", + ], + }, + { + "action": ["sync"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_obj_perms:ostree.sync_ostreerepository", + "has_remote_param_model_or_obj_perms:ostree.view_ostreeremote", + "has_model_or_obj_perms:ostree.view_ostreerepository", + ], + }, + { + "action": ["import_all", "import_commits"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_obj_perms:ostree.import_commits_ostreerepository" + "has_model_or_obj_perms:ostree.view_ostreerepository", + ], + }, + { + "action": ["modify"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_obj_perms:ostree.modify_ostreerepository", + ], + }, + { + "action": ["list_roles", "add_role", "remove_role"], + "principal": "authenticated", + "effect": "allow", + "condition": ["has_model_or_obj_perms:ostree.manage_roles_ostreerepository"], + }, + ], + "creation_hooks": [ + { + "function": "add_roles_for_object_creator", + "parameters": {"roles": "ostree.ostreerepository_owner"}, + }, + ], + "queryset_scoping": {"function": "scope_queryset"}, + } + LOCKED_ROLES = { + "ostree.ostreerepository_creator": ["ostree.add_ostreerepository"], + "ostree.ostreerepository_owner": [ + "ostree.view_ostreerepository", + "ostree.change_ostreerepository", + "ostree.delete_ostreerepository", + "ostree.modify_ostreerepository", + "ostree.sync_ostreerepository", + "ostree.manage_roles_ostreerepository", + "ostree.repair_ostreerepository", + "ostree.import_commits_ostreerepository", + ], + "ostree.ostreerepository_viewer": ["ostree.view_ostreerepository"], + } @extend_schema( description="Trigger an asynchronous task to sync content.", @@ -177,13 +345,110 @@ class OstreeRepositoryVersionViewSet(core.RepositoryVersionViewSet): parent_viewset = OstreeRepositoryViewSet + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "retrieve"], + "principal": "authenticated", + "effect": "allow", + "condition": "has_repository_model_or_obj_perms:ostree.view_ostreerepository", + }, + { + "action": ["destroy"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_repository_model_or_obj_perms:ostree.delete_ostreerepository", + "has_repository_model_or_obj_perms:ostree.delete_ostreerepository_version", + "has_repository_model_or_obj_perms:ostree.view_ostreerepository", + ], + }, + { + "action": ["repair"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_repository_model_or_obj_perms:ostree.repair_ostreerepository", + ], + }, + ], + } + -class OstreeDistributionViewSet(core.DistributionViewSet): +class OstreeDistributionViewSet(core.DistributionViewSet, core.RolesMixin): """A ViewSet class for OSTree distributions.""" endpoint_name = "ostree" queryset = models.OstreeDistribution.objects.all() serializer_class = serializers.OstreeDistributionSerializer + queryset_filtering_required_permission = "ostree.view_ostreedistribution" + + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "my_permissions"], + "principal": "authenticated", + "effect": "allow", + }, + { + "action": ["create"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_perms:ostree.add_ostreedistribution", + "has_repo_or_repo_ver_param_model_or_obj_perms:" "ostree.view_ostreerepository", + "has_publication_param_model_or_obj_perms:ostree.view_ostreepublication", + ], + }, + { + "action": ["retrieve"], + "principal": "authenticated", + "effect": "allow", + "condition": "has_model_or_obj_perms:ostree.view_ostreedistribution", + }, + { + "action": ["update", "partial_update", "set_label", "unset_label"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_obj_perms:ostree.change_ostreedistribution", + "has_repo_or_repo_ver_param_model_or_obj_perms:" "ostree.view_ostreerepository", + "has_publication_param_model_or_obj_perms:ostree.view_ostreepublication", + ], + }, + { + "action": ["destroy"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_model_or_obj_perms:ostree.delete_ostreedistribution", + ], + }, + { + "action": ["list_roles", "add_role", "remove_role"], + "principal": "authenticated", + "effect": "allow", + "condition": ["has_model_or_obj_perms:ostree.manage_roles_ostreedistribution"], + }, + ], + "creation_hooks": [ + { + "function": "add_roles_for_object_creator", + "parameters": {"roles": "ostree.ostreedistribution_owner"}, + }, + ], + "queryset_scoping": {"function": "scope_queryset"}, + } + LOCKED_ROLES = { + "ostree.ostreedistribution_creator": ["ostree.add_ostreedistribution"], + "ostree.ostreedistribution_owner": [ + "ostree.view_ostreedistribution", + "ostree.change_ostreedistribution", + "ostree.delete_ostreedistribution", + "ostree.manage_roles_ostreedistribution", + ], + "ostree.ostreedistribution_viewer": ["ostree.view_ostreedistribution"], + } class OstreeRefFilter(ContentFilter): @@ -196,7 +461,45 @@ class Meta: fields = {"name": NAME_FILTER_OPTIONS} -class OstreeRefViewSet(ReadOnlyContentViewSet): +class OstreeContentQuerySetMixin: + """ + A mixin that filters content units based on their object-level permissions. + """ + + def _scope_repos_by_repo_version(self, repo_version_href): + repo_version = core.NamedModelViewSet.get_resource(repo_version_href, RepositoryVersion) + repo = repo_version.repository.cast() + + has_model_perm = self.request.user.has_perm(REPO_VIEW_PERM) + has_object_perm = self.request.user.has_perm(REPO_VIEW_PERM, repo) + + if has_model_perm or has_object_perm: + return [repo] + else: + return [] + + def get_content_qs(self, qs): + """ + Get a filtered QuerySet based on the current request's scope. + + This method returns only content units a user is allowed to preview. The user with the + global import and mirror permissions (i.e., having the "ostree.view_ostreerepository") + can see orphaned content too. + """ + if self.request.user.has_perm(REPO_VIEW_PERM): + return qs + + if repo_version_href := self.request.query_params.get("repository_version"): + allowed_repos = self._scope_repos_by_repo_version(repo_version_href) + else: + allowed_repos = get_objects_for_user( + self.request.user, REPO_VIEW_PERM, models.OstreeRepository.objects.all() + ).only("pk") + + return qs.model.objects.filter(repositories__in=allowed_repos) + + +class OstreeRefViewSet(OstreeContentQuerySetMixin, ReadOnlyContentViewSet): """A ViewSet class for OSTree head commits.""" endpoint_name = "refs" @@ -204,6 +507,17 @@ class OstreeRefViewSet(ReadOnlyContentViewSet): serializer_class = serializers.OstreeRefSerializer filterset_class = OstreeRefFilter + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "retrieve"], + "principal": "authenticated", + "effect": "allow", + }, + ], + "queryset_scoping": {"function": "get_content_qs"}, + } + class OstreeCommitFilter(ContentFilter): """A filterset class for commits.""" @@ -213,7 +527,7 @@ class Meta: fields = {"checksum": ["exact"]} -class OstreeCommitViewSet(ReadOnlyContentViewSet): +class OstreeCommitViewSet(OstreeContentQuerySetMixin, ReadOnlyContentViewSet): """A ViewSet class for OSTree commits.""" endpoint_name = "commits" @@ -221,6 +535,17 @@ class OstreeCommitViewSet(ReadOnlyContentViewSet): serializer_class = serializers.OstreeCommitSerializer filterset_class = OstreeCommitFilter + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "retrieve"], + "principal": "authenticated", + "effect": "allow", + }, + ], + "queryset_scoping": {"function": "get_content_qs"}, + } + class OstreeObjectFilter(ContentFilter): """A filterset class for objects.""" @@ -230,7 +555,7 @@ class Meta: fields = {"checksum": ["exact"]} -class OstreeObjectViewSet(ReadOnlyContentViewSet): +class OstreeObjectViewSet(OstreeContentQuerySetMixin, ReadOnlyContentViewSet): """A ViewSet class for OSTree objects (e.g., dirtree, dirmeta, file).""" endpoint_name = "objects" @@ -238,26 +563,79 @@ class OstreeObjectViewSet(ReadOnlyContentViewSet): serializer_class = serializers.OstreeObjectSerializer filterset_class = OstreeObjectFilter + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "retrieve"], + "principal": "authenticated", + "effect": "allow", + }, + ], + "queryset_scoping": {"function": "get_content_qs"}, + } + -class OstreeContentViewSet(ReadOnlyContentViewSet): +class OstreeContentViewSet(OstreeContentQuerySetMixin, SingleArtifactContentUploadViewSet): """A ViewSet class for uncategorized content units (e.g., static deltas).""" endpoint_name = "content" queryset = models.OstreeContent.objects.all() serializer_class = serializers.OstreeContentSerializer + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "retrieve"], + "principal": "authenticated", + "effect": "allow", + }, + { + "action": ["create"], + "principal": "authenticated", + "effect": "allow", + "condition": [ + "has_required_repo_perms_on_upload:ostree.modify_ostreerepository", + "has_upload_param_model_or_obj_perms:core.change_upload", + ], + }, + ], + "queryset_scoping": {"function": "get_content_qs"}, + } -class OstreeConfigViewSet(ReadOnlyContentViewSet): + +class OstreeConfigViewSet(OstreeContentQuerySetMixin, ReadOnlyContentViewSet): """A ViewSet class for OSTree repository configurations.""" endpoint_name = "configs" queryset = models.OstreeConfig.objects.all() serializer_class = serializers.OstreeConfigSerializer + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "retrieve"], + "principal": "authenticated", + "effect": "allow", + }, + ], + "queryset_scoping": {"function": "get_content_qs"}, + } -class OstreeSummaryViewSet(ReadOnlyContentViewSet): + +class OstreeSummaryViewSet(OstreeContentQuerySetMixin, ReadOnlyContentViewSet): """A ViewSet class for OSTree repository summary files.""" endpoint_name = "summaries" queryset = models.OstreeSummary.objects.all() serializer_class = serializers.OstreeSummarySerializer + + DEFAULT_ACCESS_POLICY = { + "statements": [ + { + "action": ["list", "retrieve"], + "principal": "authenticated", + "effect": "allow", + }, + ], + "queryset_scoping": {"function": "get_content_qs"}, + } diff --git a/pulp_ostree/tests/functional/api/test_rbac.py b/pulp_ostree/tests/functional/api/test_rbac.py new file mode 100644 index 00000000..58dee32d --- /dev/null +++ b/pulp_ostree/tests/functional/api/test_rbac.py @@ -0,0 +1,80 @@ +import pytest + +from pulpcore.client.pulp_ostree.exceptions import ApiException + + +@pytest.mark.parallel +def test_crud_remotes(gen_user, ostree_remote_factory, ostree_remotes_api_client, monitor_task): + """Verify if users with different permissions can(not) perform CRUD operations on remotes.""" + user_creator = gen_user(model_roles=["ostree.ostreeremote_creator"]) + user_viewer = gen_user(model_roles=["ostree.ostreeremote_viewer"]) + user_anon = gen_user() + + with user_anon, pytest.raises(ApiException): + ostree_remote_factory() + with user_viewer, pytest.raises(ApiException): + ostree_remote_factory() + with user_creator: + remote = ostree_remote_factory() + + with user_anon: + assert 0 == ostree_remotes_api_client.list(name=remote.name).count + with user_viewer: + assert 1 == ostree_remotes_api_client.list(name=remote.name).count + with user_creator: + assert 1 == ostree_remotes_api_client.list(name=remote.name).count + + with user_anon, pytest.raises(ApiException): + ostree_remotes_api_client.read(remote.pulp_href) + with user_viewer: + ostree_remotes_api_client.read(remote.pulp_href) + with user_creator: + ostree_remotes_api_client.read(remote.pulp_href) + + with user_anon, pytest.raises(ApiException): + ostree_remotes_api_client.partial_update(remote.pulp_href, {"url": "https://redhat.com"}) + with user_viewer, pytest.raises(ApiException): + ostree_remotes_api_client.partial_update(remote.pulp_href, {"url": "https://redhat.com"}) + with user_creator: + ostree_remotes_api_client.partial_update(remote.pulp_href, {"url": "https://redhat.com"}) + + with user_anon, pytest.raises(ApiException): + ostree_remotes_api_client.delete(remote.pulp_href) + with user_viewer, pytest.raises(ApiException): + ostree_remotes_api_client.delete(remote.pulp_href) + with user_creator: + monitor_task(ostree_remotes_api_client.delete(remote.pulp_href).task) + + +@pytest.mark.parallel +def test_ref_content_access(gen_user, sync_repo_version, ostree_content_refs_api_client): + """Verify if users with different access scopes can(not) preview refs.""" + user_creator = gen_user( + model_roles=["ostree.ostreerepository_creator", "ostree.ostreeremote_creator"] + ) + user_viewer = gen_user(model_roles=["ostree.ostreerepository_viewer"]) + user_anon = gen_user() + + with user_anon, pytest.raises(ApiException): + sync_repo_version() + with user_viewer, pytest.raises(ApiException): + sync_repo_version() + with user_creator: + version, _, _ = sync_repo_version() + + repo_version = version.pulp_href + with user_anon: + assert 0 == ostree_content_refs_api_client.list(repository_version=repo_version).count + with user_viewer: + assert 2 == ostree_content_refs_api_client.list(repository_version=repo_version).count + with user_creator: + assert 2 == ostree_content_refs_api_client.list(repository_version=repo_version).count + + ref = ostree_content_refs_api_client.list(repository_version=repo_version).results[0] + + with user_anon, pytest.raises(ApiException): + ostree_content_refs_api_client.read(ref.pulp_href) + with user_viewer: + ostree_content_refs_api_client.read(ref.pulp_href) + with user_creator: + ostree_content_refs_api_client.read(ref.pulp_href) From a94298cd8e1cce172df2030d0484cbbd15559412 Mon Sep 17 00:00:00 2001 From: git-hyagi <45576767+git-hyagi@users.noreply.github.com> Date: Mon, 5 Feb 2024 15:37:27 -0300 Subject: [PATCH 3/3] Add domain support closes: #321 --- .ci/ansible/Containerfile.j2 | 4 +- .github/template_gitref | 2 +- .github/workflows/scripts/install.sh | 6 +- CHANGES/321.feature | 1 + pulp_ostree/app/__init__.py | 1 + .../app/migrations/0008_add_domain_support.py | 94 +++++++++++++++++++ pulp_ostree/app/models.py | 20 ++-- pulp_ostree/app/tasks/importing.py | 20 ++-- pulp_ostree/app/tasks/modifying.py | 26 +++-- pulp_ostree/app/tasks/stages.py | 10 +- pulp_ostree/app/tasks/synchronizing.py | 8 +- pulp_ostree/app/viewsets.py | 72 +++++++------- .../tests/functional/api/test_domain.py | 36 +++++++ pulp_ostree/tests/functional/conftest.py | 15 ++- requirements.txt | 2 +- template_config.yml | 6 +- 16 files changed, 247 insertions(+), 76 deletions(-) create mode 100644 CHANGES/321.feature create mode 100644 pulp_ostree/app/migrations/0008_add_domain_support.py create mode 100644 pulp_ostree/tests/functional/api/test_domain.py diff --git a/.ci/ansible/Containerfile.j2 b/.ci/ansible/Containerfile.j2 index c6c21fdb..338ff697 100644 --- a/.ci/ansible/Containerfile.j2 +++ b/.ci/ansible/Containerfile.j2 @@ -1,4 +1,4 @@ -FROM {{ ci_base | default("ghcr.io/pulp/pulp-ci-centos:" + pulp_container_tag) }} +FROM {{ ci_base | default(pulp_default_container) }} # Add source directories to container {% for item in plugins %} @@ -11,7 +11,7 @@ ADD ./{{ item.name }} ./{{ item.name }} RUN pip3 install {%- if s3_test | default(false) -%} -{{ " " }}git+https://github.com/fabricio-aguiar/botocore.git@fix-100-continue +{{ " " }}git+https://github.com/gerrod3/botocore.git@fix-100-continue {%- endif -%} {%- for item in plugins -%} {{ " " }}{{ item.source }} diff --git a/.github/template_gitref b/.github/template_gitref index 443d675e..2b533f2f 100644 --- a/.github/template_gitref +++ b/.github/template_gitref @@ -1 +1 @@ -2021.08.26-313-g0528507 +2021.08.26-315-g8ecb63d diff --git a/.github/workflows/scripts/install.sh b/.github/workflows/scripts/install.sh index caf96837..82320809 100755 --- a/.github/workflows/scripts/install.sh +++ b/.github/workflows/scripts/install.sh @@ -80,9 +80,7 @@ cat >> vars/main.yaml << VARSYAML pulp_env: {} pulp_settings: null pulp_scheme: https - -pulp_container_tag: "latest" - +pulp_default_container: ghcr.io/pulp/pulp-ci-centos:latest VARSYAML if [ "$TEST" = "s3" ]; then @@ -98,7 +96,7 @@ if [ "$TEST" = "s3" ]; then sed -i -e '$a s3_test: true\ minio_access_key: "'$MINIO_ACCESS_KEY'"\ minio_secret_key: "'$MINIO_SECRET_KEY'"\ -pulp_scenario_settings: null\ +pulp_scenario_settings: {"domain_enabled": true}\ pulp_scenario_env: {}\ ' vars/main.yaml export PULP_API_ROOT="/rerouted/djnd/" diff --git a/CHANGES/321.feature b/CHANGES/321.feature new file mode 100644 index 00000000..ef90ad80 --- /dev/null +++ b/CHANGES/321.feature @@ -0,0 +1 @@ +Added support for domains. diff --git a/pulp_ostree/app/__init__.py b/pulp_ostree/app/__init__.py index 8715c3a9..2496fd73 100755 --- a/pulp_ostree/app/__init__.py +++ b/pulp_ostree/app/__init__.py @@ -8,3 +8,4 @@ class PulpOstreePluginAppConfig(PulpPluginAppConfig): label = "ostree" version = "2.3.0.dev" python_package_name = "pulp-ostree" + domain_compatible = True diff --git a/pulp_ostree/app/migrations/0008_add_domain_support.py b/pulp_ostree/app/migrations/0008_add_domain_support.py new file mode 100644 index 00000000..ceb6a982 --- /dev/null +++ b/pulp_ostree/app/migrations/0008_add_domain_support.py @@ -0,0 +1,94 @@ +# Generated by Django 4.2.9 on 2024-02-06 10:31 + +from django.db import migrations, models +import django.db.models.deletion +import pulpcore.app.util + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0116_alter_remoteartifact_md5_alter_remoteartifact_sha1_and_more'), + ('ostree', '0007_add_model_permissions'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='ostreecommit', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='ostreeconfig', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='ostreecontent', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='ostreeobject', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='ostreeref', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='ostreesummary', + unique_together=set(), + ), + migrations.AddField( + model_name='ostreecommit', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, to='core.domain'), + ), + migrations.AddField( + model_name='ostreeconfig', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, to='core.domain'), + ), + migrations.AddField( + model_name='ostreecontent', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, to='core.domain'), + ), + migrations.AddField( + model_name='ostreeobject', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, to='core.domain'), + ), + migrations.AddField( + model_name='ostreeref', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, to='core.domain'), + ), + migrations.AddField( + model_name='ostreesummary', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, to='core.domain'), + ), + migrations.AlterUniqueTogether( + name='ostreecommit', + unique_together={('checksum', 'relative_path', '_pulp_domain')}, + ), + migrations.AlterUniqueTogether( + name='ostreeconfig', + unique_together={('sha256', 'relative_path', '_pulp_domain')}, + ), + migrations.AlterUniqueTogether( + name='ostreecontent', + unique_together={('relative_path', 'digest', '_pulp_domain')}, + ), + migrations.AlterUniqueTogether( + name='ostreeobject', + unique_together={('checksum', 'relative_path', '_pulp_domain')}, + ), + migrations.AlterUniqueTogether( + name='ostreeref', + unique_together={('name', 'commit', 'relative_path', '_pulp_domain')}, + ), + migrations.AlterUniqueTogether( + name='ostreesummary', + unique_together={('sha256', 'relative_path', '_pulp_domain')}, + ), + ] diff --git a/pulp_ostree/app/models.py b/pulp_ostree/app/models.py index b691edae..e28222c5 100755 --- a/pulp_ostree/app/models.py +++ b/pulp_ostree/app/models.py @@ -12,6 +12,8 @@ ) from pulpcore.plugin.repo_version_utils import remove_duplicates, validate_duplicate_content +from pulpcore.plugin.util import get_domain_pk + logger = getLogger(__name__) @@ -32,13 +34,14 @@ class OstreeObject(Content): TYPE = "object" + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) typ = models.IntegerField(choices=OstreeObjectType.choices) checksum = models.CharField(max_length=64, db_index=True) relative_path = models.TextField(null=False) class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = [["checksum", "relative_path"]] + unique_together = [["checksum", "relative_path", "_pulp_domain"]] class OstreeCommit(Content): @@ -46,6 +49,7 @@ class OstreeCommit(Content): TYPE = "commit" + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) parent_commit = models.ForeignKey("self", null=True, blank=True, on_delete=models.CASCADE) checksum = models.CharField(max_length=64, db_index=True) relative_path = models.TextField(null=False) @@ -53,7 +57,7 @@ class OstreeCommit(Content): class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = [["checksum", "relative_path"]] + unique_together = [["checksum", "relative_path", "_pulp_domain"]] class OstreeRef(Content): @@ -62,6 +66,7 @@ class OstreeRef(Content): TYPE = "refs" repo_key_fields = ("name",) + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) commit = models.ForeignKey( OstreeCommit, related_name="refs_commit", null=True, on_delete=models.CASCADE ) @@ -70,7 +75,7 @@ class OstreeRef(Content): class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = [["name", "commit", "relative_path"]] + unique_together = [["name", "commit", "relative_path", "_pulp_domain"]] class OstreeCommitObject(models.Model): @@ -88,12 +93,13 @@ class OstreeContent(Content): repo_key_fields = ("relative_path",) + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) relative_path = models.TextField(null=False) digest = models.CharField(max_length=64, null=False) class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = ("relative_path", "digest") + unique_together = ("relative_path", "digest", "_pulp_domain") class OstreeConfig(Content): @@ -102,12 +108,13 @@ class OstreeConfig(Content): TYPE = "config" repo_key_fields = ("relative_path",) + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) sha256 = models.CharField(max_length=64, db_index=True) relative_path = models.TextField(null=False) class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = [["sha256", "relative_path"]] + unique_together = [["sha256", "relative_path", "_pulp_domain"]] class OstreeSummary(Content): @@ -116,12 +123,13 @@ class OstreeSummary(Content): TYPE = "summary" repo_key_fields = ("relative_path",) + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) sha256 = models.CharField(max_length=64, db_index=True) relative_path = models.TextField(null=False) class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = [["sha256", "relative_path"]] + unique_together = [["sha256", "relative_path", "_pulp_domain"]] class OstreeRemote(Remote, AutoAddObjPermsMixin): diff --git a/pulp_ostree/app/tasks/importing.py b/pulp_ostree/app/tasks/importing.py index 31e89ff0..ad1ccc2d 100644 --- a/pulp_ostree/app/tasks/importing.py +++ b/pulp_ostree/app/tasks/importing.py @@ -92,7 +92,7 @@ async def parse_ref(self, name, ref_commit_checksum, has_referenced_parent=False parent_checksum = OSTree.commit_get_parent(ref_commit) if not parent_checksum: # there are not any parent commits, return and continue parsing the next ref - commit = OstreeCommit(checksum=ref_commit_checksum) + commit = OstreeCommit(checksum=ref_commit_checksum, _pulp_domain=self.domain) commit_dc = self.create_dc(relative_path, commit) await self.put(commit_dc) @@ -103,7 +103,7 @@ async def parse_ref(self, name, ref_commit_checksum, has_referenced_parent=False return checksum = ref_commit_checksum - ref_commit = OstreeCommit(checksum=checksum) + ref_commit = OstreeCommit(checksum=checksum, _pulp_domain=self.domain) ref_commit_dc = self.create_dc(relative_path, ref_commit) self.commit_dcs.append(ref_commit_dc) @@ -118,7 +118,9 @@ async def parse_ref(self, name, ref_commit_checksum, has_referenced_parent=False return parent_checksum, ref_commit_dc else: try: - parent_commit = await OstreeCommit.objects.aget(checksum=parent_checksum) + parent_commit = await OstreeCommit.objects.aget( + checksum=parent_checksum, _pulp_domain=self.domain + ) except OstreeCommit.DoesNotExist: raise ValueError( gettext("The parent commit '{}' could not be loaded").format( @@ -138,7 +140,7 @@ async def load_next_commits(self, parent_commit, checksum, has_referenced_parent parent_checksum = OSTree.commit_get_parent(parent_commit) while parent_checksum: - commit = OstreeCommit(checksum=checksum) + commit = OstreeCommit(checksum=checksum, _pulp_domain=self.domain) commit_dc = self.create_dc(relative_path, commit) self.commit_dcs.append(commit_dc) @@ -161,7 +163,7 @@ async def load_next_commits(self, parent_commit, checksum, has_referenced_parent ) parent_checksum = OSTree.commit_get_parent(parent_commit) - commit = OstreeCommit(checksum=checksum) + commit = OstreeCommit(checksum=checksum, _pulp_domain=self.domain) commit_dc = self.create_dc(relative_path, commit) self.commit_dcs.append(commit_dc) @@ -263,7 +265,9 @@ async def run(self): parent_commit = None try: - parent_commit = await OstreeCommit.objects.aget(checksum=parent_checksum) + parent_commit = await OstreeCommit.objects.aget( + checksum=parent_checksum, _pulp_domain=self.domain + ) except OstreeCommit.DoesNotExist: pass else: @@ -332,7 +336,7 @@ async def run(self): num_of_parsed_commits = len(self.commit_dcs) commit = await OstreeCommit.objects.select_related("parent_commit").aget( - checksum=ref_commit_checksum + checksum=ref_commit_checksum, _pulp_domain=self.domain ) parent_commit = commit.parent_commit if parent_commit and num_of_parsed_commits == 1: @@ -364,7 +368,7 @@ async def run(self): ref_file = await ref._artifacts.aget() copy_to_local_storage(ref_file.file, file_path) - commit = await OstreeCommit.objects.aget(refs_commit=ref) + commit = await OstreeCommit.objects.aget(refs_commit=ref, _pulp_domain=self.domain) await self.copy_from_storage_to_tmp(commit, OstreeObject.objects.none()) self.repo.regenerate_summary() diff --git a/pulp_ostree/app/tasks/modifying.py b/pulp_ostree/app/tasks/modifying.py index 03fe91d6..60f12acf 100644 --- a/pulp_ostree/app/tasks/modifying.py +++ b/pulp_ostree/app/tasks/modifying.py @@ -3,6 +3,7 @@ from django.db.models import Q from pulpcore.plugin.models import Repository, RepositoryVersion, Content +from pulpcore.plugin.util import get_domain from pulp_ostree.app.models import ( OstreeCommit, @@ -69,16 +70,20 @@ def modify_content(repository_pk, add_content_units, remove_content_units, base_ def get_content_data_by_model(model_type, add_content_units, remove_content_units): """Return an object that holds a reference to querysets of added and removed content.""" - to_add = model_type.objects.filter(pk__in=add_content_units) - to_remove = model_type.objects.filter(pk__in=remove_content_units) + curr_domain = get_domain() + to_add = model_type.objects.filter(pk__in=add_content_units, _pulp_domain=curr_domain) + to_remove = model_type.objects.filter(pk__in=remove_content_units, _pulp_domain=curr_domain) return ModifyContentData(to_add, to_remove) def recursively_get_add_content(commit_data, ref_data): """Get all the content required for addition that the passed objects reference.""" + curr_domain = get_domain() ref_commits_pks = ref_data.values_list("commit", flat=True) - commit_data = commit_data.union(OstreeCommit.objects.filter(pk__in=ref_commits_pks)) + commit_data = commit_data.union( + OstreeCommit.objects.filter(pk__in=ref_commits_pks, _pulp_domain=curr_domain) + ) objects_pks = commit_data.values_list("objs", flat=True) commit_data_pks = commit_data.values_list("pk", flat=True) @@ -89,9 +94,12 @@ def recursively_get_add_content(commit_data, ref_data): def recursively_get_remove_content(commit_data, ref_data, latest_content): """Get all the content required for removal that the passed objects reference.""" + curr_domain = get_domain() ref_commits_pks = ref_data.values_list("commit", flat=True) - commit_data = commit_data.union(OstreeCommit.objects.filter(pk__in=ref_commits_pks)) + commit_data = commit_data.union( + OstreeCommit.objects.filter(pk__in=ref_commits_pks, _pulp_domain=curr_domain) + ) commit_data_pks = commit_data.values_list("pk", flat=True) # we do not want to get removed objects that are referenced by other commits in the repository @@ -100,17 +108,17 @@ def recursively_get_remove_content(commit_data, ref_data, latest_content): ).values_list("pk", flat=True) if remaining_commits_pks: remaining_objects_pks = OstreeCommit.objects.filter( - ~Q(pk__in=remaining_commits_pks) + ~Q(pk__in=remaining_commits_pks), _pulp_domain=curr_domain ).values_list("objs", flat=True) objects_pks = ( - OstreeCommit.objects.filter(pk__in=commit_data_pks) + OstreeCommit.objects.filter(pk__in=commit_data_pks, _pulp_domain=curr_domain) .values_list("objs", flat=True) .difference(remaining_objects_pks) ) else: - objects_pks = OstreeCommit.objects.filter(pk__in=commit_data_pks).values_list( - "objs", flat=True - ) + objects_pks = OstreeCommit.objects.filter( + pk__in=commit_data_pks, _pulp_domain=curr_domain + ).values_list("objs", flat=True) return Content.objects.filter( Q(pk__in=commit_data_pks) | Q(pk__in=ref_data) | Q(pk__in=objects_pks) diff --git a/pulp_ostree/app/tasks/stages.py b/pulp_ostree/app/tasks/stages.py index 7dcb5aaa..676e2190 100644 --- a/pulp_ostree/app/tasks/stages.py +++ b/pulp_ostree/app/tasks/stages.py @@ -36,7 +36,7 @@ async def submit_related_objects(self, commit_dc): if obj_checksum == commit_dc.content.checksum: continue - obj = OstreeObject(typ=obj_type, checksum=obj_checksum) + obj = OstreeObject(typ=obj_type, checksum=obj_checksum, _pulp_domain=self.domain) obj_relative_path = get_checksum_filepath(obj_checksum, obj_type) object_dc = self.create_object_dc_func(obj_relative_path, obj) object_dc.extra_data["commit_relation"] = await commit_dc.resolution() @@ -44,7 +44,7 @@ async def submit_related_objects(self, commit_dc): def init_ref_object(self, name, relative_path, commit_dc): """Initialize a DeclarativeContent object for a ref object.""" - ref = OstreeRef(name=name) + ref = OstreeRef(name=name, _pulp_domain=self.domain) ref_dc = self.create_dc(relative_path, ref) ref_dc.extra_data["ref_commit"] = commit_dc self.refs_dcs.append(ref_dc) @@ -120,7 +120,11 @@ async def compute_static_delta(self, ref_commit_checksum, parent_checksum=None): full_path = os.path.join(dirpath, filename) relative_path = os.path.relpath(full_path, self.repo_path) - content = OstreeContent(relative_path=relative_path, digest=compute_hash(full_path)) + content = OstreeContent( + relative_path=relative_path, + digest=compute_hash(full_path), + _pulp_domain=self.domain, + ) content_dc = self.create_dc(relative_path, content) await self.put(content_dc) diff --git a/pulp_ostree/app/tasks/synchronizing.py b/pulp_ostree/app/tasks/synchronizing.py index f8ad955d..2cf61316 100755 --- a/pulp_ostree/app/tasks/synchronizing.py +++ b/pulp_ostree/app/tasks/synchronizing.py @@ -119,7 +119,7 @@ async def run(self): ref_parent_checksum = parent_checksum = OSTree.commit_get_parent(ref_commit) if not parent_checksum or self.remote.depth == 0: # there are not any parent commits, continue parsing the next head branch - commit = OstreeCommit(checksum=ref_commit_checksum) + commit = OstreeCommit(checksum=ref_commit_checksum, _pulp_domain=self.domain) commit_dc = self.create_dc(relative_path, commit) await self.put(commit_dc) @@ -130,7 +130,7 @@ async def run(self): continue checksum = ref_commit_checksum - ref_commit = OstreeCommit(checksum=checksum) + ref_commit = OstreeCommit(checksum=checksum, _pulp_domain=self.domain) ref_commit_dc = self.create_dc(relative_path, ref_commit) self.commit_dcs.append(ref_commit_dc) @@ -144,7 +144,7 @@ async def run(self): max_depth = self.remote.depth while parent_checksum and max_depth > 0: - commit = OstreeCommit(checksum=checksum) + commit = OstreeCommit(checksum=checksum, _pulp_domain=self.domain) commit_dc = self.create_dc(relative_path, commit) self.commit_dcs.append(commit_dc) @@ -158,7 +158,7 @@ async def run(self): max_depth -= 1 - commit = OstreeCommit(checksum=checksum) + commit = OstreeCommit(checksum=checksum, _pulp_domain=self.domain) commit_dc = self.create_dc(relative_path, commit) self.commit_dcs.append(commit_dc) diff --git a/pulp_ostree/app/viewsets.py b/pulp_ostree/app/viewsets.py index 12efbf53..b2c692c1 100755 --- a/pulp_ostree/app/viewsets.py +++ b/pulp_ostree/app/viewsets.py @@ -47,20 +47,20 @@ class OstreeRemoteViewSet(core.RemoteViewSet, core.RolesMixin): "action": ["create"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_perms:ostree.add_ostreeremote", + "condition": "has_model_or_domain_perms:ostree.add_ostreeremote", }, { "action": ["retrieve"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:ostree.view_ostreeremote", + "condition": "has_model_or_domain_or_obj_perms:ostree.view_ostreeremote", }, { "action": ["update", "partial_update", "set_label", "unset_label"], "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:ostree.change_ostreeremote", + "has_model_or_domain_or_obj_perms:ostree.change_ostreeremote", ], }, { @@ -68,14 +68,14 @@ class OstreeRemoteViewSet(core.RemoteViewSet, core.RolesMixin): "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:ostree.delete_ostreeremote", + "has_model_or_domain_or_obj_perms:ostree.delete_ostreeremote", ], }, { "action": ["list_roles", "add_role", "remove_role"], "principal": "authenticated", "effect": "allow", - "condition": ["has_model_or_obj_perms:ostree.manage_roles_ostreeremote"], + "condition": ["has_model_or_domain_or_obj_perms:ostree.manage_roles_ostreeremote"], }, ], "creation_hooks": [ @@ -118,22 +118,22 @@ class OstreeRepositoryViewSet(core.RepositoryViewSet, ModifyRepositoryActionMixi "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_perms:ostree.add_ostreerepository", - "has_remote_param_model_or_obj_perms:ostree.view_ostreeremote", + "has_model_or_domain_perms:ostree.add_ostreerepository", + "has_remote_param_model_or_domain_or_obj_perms:ostree.view_ostreeremote", ], }, { "action": ["retrieve"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:ostree.view_ostreerepository", + "condition": "has_model_or_domain_or_obj_perms:ostree.view_ostreerepository", }, { "action": ["destroy"], "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:ostree.delete_ostreerepository", + "has_model_or_domain_or_obj_perms:ostree.delete_ostreerepository", ], }, { @@ -141,8 +141,8 @@ class OstreeRepositoryViewSet(core.RepositoryViewSet, ModifyRepositoryActionMixi "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:ostree.change_ostreerepository", - "has_remote_param_model_or_obj_perms:ostree.view_ostreeremote", + "has_model_or_domain_or_obj_perms:ostree.change_ostreerepository", + "has_remote_param_model_or_domain_or_obj_perms:ostree.view_ostreeremote", ], }, { @@ -150,9 +150,9 @@ class OstreeRepositoryViewSet(core.RepositoryViewSet, ModifyRepositoryActionMixi "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:ostree.sync_ostreerepository", - "has_remote_param_model_or_obj_perms:ostree.view_ostreeremote", - "has_model_or_obj_perms:ostree.view_ostreerepository", + "has_model_or_domain_or_obj_perms:ostree.sync_ostreerepository", + "has_remote_param_model_or_domain_or_obj_perms:ostree.view_ostreeremote", + "has_model_or_domain_or_obj_perms:ostree.view_ostreerepository", ], }, { @@ -160,8 +160,8 @@ class OstreeRepositoryViewSet(core.RepositoryViewSet, ModifyRepositoryActionMixi "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:ostree.import_commits_ostreerepository" - "has_model_or_obj_perms:ostree.view_ostreerepository", + "has_model_or_domain_or_obj_perms:ostree.import_commits_ostreerepository" + "has_model_or_domain_or_obj_perms:ostree.view_ostreerepository", ], }, { @@ -169,14 +169,16 @@ class OstreeRepositoryViewSet(core.RepositoryViewSet, ModifyRepositoryActionMixi "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:ostree.modify_ostreerepository", + "has_model_or_domain_or_obj_perms:ostree.modify_ostreerepository", ], }, { "action": ["list_roles", "add_role", "remove_role"], "principal": "authenticated", "effect": "allow", - "condition": ["has_model_or_obj_perms:ostree.manage_roles_ostreerepository"], + "condition": [ + "has_model_or_domain_or_obj_perms:ostree.manage_roles_ostreerepository" + ], }, ], "creation_hooks": [ @@ -351,16 +353,16 @@ class OstreeRepositoryVersionViewSet(core.RepositoryVersionViewSet): "action": ["list", "retrieve"], "principal": "authenticated", "effect": "allow", - "condition": "has_repository_model_or_obj_perms:ostree.view_ostreerepository", + "condition": "has_repository_model_or_domain_or_obj_perms:ostree.view_ostreerepository", # noqa: E501 }, { "action": ["destroy"], "principal": "authenticated", "effect": "allow", "condition": [ - "has_repository_model_or_obj_perms:ostree.delete_ostreerepository", - "has_repository_model_or_obj_perms:ostree.delete_ostreerepository_version", - "has_repository_model_or_obj_perms:ostree.view_ostreerepository", + "has_repository_model_or_domain_or_obj_perms:ostree.delete_ostreerepository", + "has_repository_model_or_domain_or_obj_perms:ostree.delete_ostreerepository_version", # noqa: E501 + "has_repository_model_or_domain_or_obj_perms:ostree.view_ostreerepository", ], }, { @@ -368,7 +370,7 @@ class OstreeRepositoryVersionViewSet(core.RepositoryVersionViewSet): "principal": "authenticated", "effect": "allow", "condition": [ - "has_repository_model_or_obj_perms:ostree.repair_ostreerepository", + "has_repository_model_or_domain_or_obj_perms:ostree.repair_ostreerepository", ], }, ], @@ -395,25 +397,27 @@ class OstreeDistributionViewSet(core.DistributionViewSet, core.RolesMixin): "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_perms:ostree.add_ostreedistribution", - "has_repo_or_repo_ver_param_model_or_obj_perms:" "ostree.view_ostreerepository", - "has_publication_param_model_or_obj_perms:ostree.view_ostreepublication", + "has_model_or_domain_perms:ostree.add_ostreedistribution", + "has_repo_or_repo_ver_param_model_or_domain_or_obj_perms:" + "ostree.view_ostreerepository", + "has_publication_param_model_or_domain_or_obj_perms:ostree.view_ostreepublication", # noqa: E501 ], }, { "action": ["retrieve"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:ostree.view_ostreedistribution", + "condition": "has_model_or_domain_or_obj_perms:ostree.view_ostreedistribution", }, { "action": ["update", "partial_update", "set_label", "unset_label"], "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:ostree.change_ostreedistribution", - "has_repo_or_repo_ver_param_model_or_obj_perms:" "ostree.view_ostreerepository", - "has_publication_param_model_or_obj_perms:ostree.view_ostreepublication", + "has_model_or_domain_or_obj_perms:ostree.change_ostreedistribution", + "has_repo_or_repo_ver_param_model_or_domain_or_obj_perms:" + "ostree.view_ostreerepository", + "has_publication_param_model_or_domain_or_obj_perms:ostree.view_ostreepublication", # noqa: E501 ], }, { @@ -421,14 +425,16 @@ class OstreeDistributionViewSet(core.DistributionViewSet, core.RolesMixin): "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:ostree.delete_ostreedistribution", + "has_model_or_domain_or_obj_perms:ostree.delete_ostreedistribution", ], }, { "action": ["list_roles", "add_role", "remove_role"], "principal": "authenticated", "effect": "allow", - "condition": ["has_model_or_obj_perms:ostree.manage_roles_ostreedistribution"], + "condition": [ + "has_model_or_domain_or_obj_perms:ostree.manage_roles_ostreedistribution" + ], }, ], "creation_hooks": [ @@ -595,7 +601,7 @@ class OstreeContentViewSet(OstreeContentQuerySetMixin, SingleArtifactContentUplo "effect": "allow", "condition": [ "has_required_repo_perms_on_upload:ostree.modify_ostreerepository", - "has_upload_param_model_or_obj_perms:core.change_upload", + "has_upload_param_model_or_domain_or_obj_perms:core.change_upload", ], }, ], diff --git a/pulp_ostree/tests/functional/api/test_domain.py b/pulp_ostree/tests/functional/api/test_domain.py new file mode 100644 index 00000000..e6447408 --- /dev/null +++ b/pulp_ostree/tests/functional/api/test_domain.py @@ -0,0 +1,36 @@ +import pytest +from django.conf import settings + +if not settings.DOMAIN_ENABLED: + pytest.skip("Domains not enabled.", allow_module_level=True) + + +@pytest.mark.parallel +def test_domains( + domain_factory, + ostree_remote_factory, + ostree_remotes_api_client, + ostree_repository_factory, + ostree_repositories_api_client, + ostree_distribution_factory, + ostree_distributions_api_client, +): + domain = domain_factory() + domain_name = domain.name + + remote = ostree_remote_factory(pulp_domain=domain_name) + assert domain_name in remote.pulp_href + result = ostree_remotes_api_client.list(pulp_domain=domain_name) + assert result.count == 1 + + repository = ostree_repository_factory(pulp_domain=domain_name, remote=remote.pulp_href) + assert domain_name in repository.pulp_href + result = ostree_repositories_api_client.list(pulp_domain=domain_name) + assert result.count == 1 + + distribution = ostree_distribution_factory( + pulp_domain=domain_name, repository=repository.pulp_href + ) + assert domain_name in distribution.pulp_href + result = ostree_distributions_api_client.list(pulp_domain=domain_name) + assert result.count == 1 diff --git a/pulp_ostree/tests/functional/conftest.py b/pulp_ostree/tests/functional/conftest.py index 2b63bb93..ae2754c3 100644 --- a/pulp_ostree/tests/functional/conftest.py +++ b/pulp_ostree/tests/functional/conftest.py @@ -89,9 +89,12 @@ def ostree_repository_factory(ostree_repositories_api_client, gen_object_with_cl """A factory to generate an ostree Repository with auto-deletion after the test run.""" def _ostree_repository_factory(**kwargs): + extra_args = {} + if pulp_domain := kwargs.pop("pulp_domain", None): + extra_args["pulp_domain"] = pulp_domain data = {"name": str(uuid.uuid4())} data.update(kwargs) - return gen_object_with_cleanup(ostree_repositories_api_client, data) + return gen_object_with_cleanup(ostree_repositories_api_client, data, **extra_args) return _ostree_repository_factory @@ -101,9 +104,12 @@ def ostree_remote_factory(ostree_remotes_api_client, gen_object_with_cleanup): """A factory to generate an ostree Remote with auto-deletion after the test run.""" def _ostree_remote_factory(*, url=OSTREE_FIXTURE_URL, policy="immediate", **kwargs): + extra_args = {} + if pulp_domain := kwargs.pop("pulp_domain", None): + extra_args["pulp_domain"] = pulp_domain data = {"url": url, "policy": policy, "name": str(uuid.uuid4())} data.update(kwargs) - return gen_object_with_cleanup(ostree_remotes_api_client, data) + return gen_object_with_cleanup(ostree_remotes_api_client, data, **extra_args) return _ostree_remote_factory @@ -113,9 +119,12 @@ def ostree_distribution_factory(ostree_distributions_api_client, gen_object_with """A factory to generate an ostree Distribution with auto-deletion after the test run.""" def _ostree_distribution_factory(**body): + extra_args = {} + if pulp_domain := body.pop("pulp_domain", None): + extra_args["pulp_domain"] = pulp_domain data = {"base_path": str(uuid.uuid4()), "name": str(uuid.uuid4())} data.update(body) - return gen_object_with_cleanup(ostree_distributions_api_client, data) + return gen_object_with_cleanup(ostree_distributions_api_client, data, **extra_args) return _ostree_distribution_factory diff --git a/requirements.txt b/requirements.txt index 79ab6b0a..2b775ab7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -pulpcore>=3.40.1,<3.55 +pulpcore>=3.45.1,<3.55 PyGObject~=3.40.1 diff --git a/template_config.yml b/template_config.yml index 6998615a..7a08f8cf 100644 --- a/template_config.yml +++ b/template_config.yml @@ -1,7 +1,7 @@ # This config represents the latest values used when running the plugin-template. Any settings that # were not present before running plugin-template have been added with their default values. -# generated with plugin_template@2021.08.26-310-g59cd732 +# generated with plugin_template@2021.08.26-315-g8ecb63d api_root: /pulp/ black: true @@ -9,6 +9,7 @@ check_commit_message: true check_gettext: true check_manifest: true check_stray_pulpcore_imports: true +ci_base_image: ghcr.io/pulp/pulp-ci-centos ci_env: {} ci_trigger: '{pull_request: {branches: [''*'']}}' ci_update_docs: true @@ -49,7 +50,8 @@ pulp_scheme: https pulp_settings: null pulp_settings_azure: null pulp_settings_gcp: null -pulp_settings_s3: null +pulp_settings_s3: + domain_enabled: true pulpprojectdotorg_key_id: null pydocstyle: true python_version: '3.8'